OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.compositor.layouts.phone; |
| 6 |
| 7 import android.content.Context; |
| 8 import android.graphics.Rect; |
| 9 import android.graphics.RectF; |
| 10 import android.view.ViewConfiguration; |
| 11 import android.view.ViewGroup; |
| 12 import android.view.ViewGroup.LayoutParams; |
| 13 import android.widget.FrameLayout; |
| 14 |
| 15 import org.chromium.base.VisibleForTesting; |
| 16 import org.chromium.chrome.browser.Tab; |
| 17 import org.chromium.chrome.browser.compositor.LayerTitleCache; |
| 18 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animatable
; |
| 19 import org.chromium.chrome.browser.compositor.layouts.Layout; |
| 20 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost; |
| 21 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost; |
| 22 import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; |
| 23 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; |
| 24 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeEvent
Filter.ScrollDirection; |
| 25 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EventFilter; |
| 26 import org.chromium.chrome.browser.compositor.layouts.phone.stack.Stack; |
| 27 import org.chromium.chrome.browser.compositor.layouts.phone.stack.StackTab; |
| 28 import org.chromium.chrome.browser.compositor.scene_layer.SceneLayer; |
| 29 import org.chromium.chrome.browser.compositor.scene_layer.TabListSceneLayer; |
| 30 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; |
| 31 import org.chromium.chrome.browser.partnercustomizations.HomepageManager; |
| 32 import org.chromium.chrome.browser.tabmodel.TabModel; |
| 33 import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| 34 import org.chromium.chrome.browser.tabmodel.TabModelUtils; |
| 35 import org.chromium.chrome.browser.util.MathUtils; |
| 36 import org.chromium.ui.base.LocalizationUtils; |
| 37 import org.chromium.ui.resources.ResourceManager; |
| 38 |
| 39 import java.io.Serializable; |
| 40 import java.util.ArrayList; |
| 41 import java.util.Arrays; |
| 42 import java.util.Comparator; |
| 43 |
| 44 /** |
| 45 * Defines the layout for 2 stacks and manages the events to switch between |
| 46 * them. |
| 47 */ |
| 48 |
| 49 public class StackLayout extends Layout implements Animatable<StackLayout.Proper
ty> { |
| 50 public enum Property { |
| 51 INNER_MARGIN_PERCENT, |
| 52 STACK_SNAP, |
| 53 STACK_OFFSET_Y_PERCENT, |
| 54 } |
| 55 |
| 56 private enum SwipeMode { NONE, SEND_TO_STACK, SWITCH_STACK } |
| 57 |
| 58 private static final String TAG = "StackLayout"; |
| 59 // Width of the partially shown stack when there are multiple stacks. |
| 60 private static final int MIN_INNER_MARGIN_PERCENT_DP = 55; |
| 61 private static final float INNER_MARGIN_PERCENT_PERCENT = 0.17f; |
| 62 |
| 63 // Speed of the automatic fling in dp/ms |
| 64 private static final float FLING_SPEED_DP = 1.5f; // dp / ms |
| 65 private static final int FLING_MIN_DURATION = 100; // ms |
| 66 |
| 67 private static final float THRESHOLD_TO_SWITCH_STACK = 0.4f; |
| 68 private static final float THRESHOLD_TIME_TO_SWITCH_STACK_INPUT_MODE = 200; |
| 69 |
| 70 /** |
| 71 * The delta time applied on the velocity from the fling. This is to compute
the kick to help |
| 72 * switching the stack. |
| 73 */ |
| 74 private static final float SWITCH_STACK_FLING_DT = 1.0f / 30.0f; |
| 75 |
| 76 /** The array of potentially visible stacks. The code works for only 2 stack
s. */ |
| 77 private final Stack[] mStacks; |
| 78 |
| 79 /** Rectangles that defines the area where each stack need to be laid out. *
/ |
| 80 private final RectF[] mStackRects; |
| 81 |
| 82 private int mStackAnimationCount; |
| 83 |
| 84 private float mFlingSpeed = 0; // pixel/ms |
| 85 |
| 86 /** Whether the current fling animation is the result of switching stacks. *
/ |
| 87 private boolean mFlingFromModelChange; |
| 88 |
| 89 private boolean mClicked; |
| 90 |
| 91 // If not overscroll, then mRenderedScrollIndex == mScrollIndex; |
| 92 // Otherwise, mRenderedScrollIndex is updated with the actual index passed i
n |
| 93 // from the event handler; and mRenderedScrollIndex is the value we get |
| 94 // after map mScrollIndex through a decelerate function. |
| 95 // Here we use float as index so we can smoothly animate the transition betw
een stack. |
| 96 private float mRenderedScrollOffset = 0.0f; |
| 97 private float mScrollIndexOffset = 0.0f; |
| 98 |
| 99 private final int mMinMaxInnerMargin; |
| 100 private float mInnerMarginPercent; |
| 101 private float mStackOffsetYPercent; |
| 102 |
| 103 private SwipeMode mInputMode = SwipeMode.NONE; |
| 104 private float mLastOnDownX; |
| 105 private float mLastOnDownY; |
| 106 private long mLastOnDownTimeStamp; |
| 107 private final float mMinShortPressThresholdSqr; // Computed from Android Vie
wConfiguration |
| 108 private final float mMinDirectionThreshold; // Computed from Android ViewCon
figuration |
| 109 |
| 110 // Pre-allocated temporary arrays that store id of visible tabs. |
| 111 // They can be used to call populatePriorityVisibilityList. |
| 112 // We use StackTab[] instead of ArrayList<StackTab> because the sorting func
tion does |
| 113 // an allocation to iterate over the elements. |
| 114 // Do not use out of the context of {@link #updateTabPriority}. |
| 115 private StackTab[] mSortedPriorityArray = null; |
| 116 |
| 117 private final ArrayList<Integer> mVisibilityArray = new ArrayList<Integer>()
; |
| 118 private final VisibilityComparator mVisibilityComparator = new VisibilityCom
parator(); |
| 119 private final OrderComparator mOrderComparator = new OrderComparator(); |
| 120 private Comparator<StackTab> mSortingComparator = mVisibilityComparator; |
| 121 |
| 122 private static final int LAYOUTTAB_ASYNCHRONOUS_INITIALIZATION_BATCH_SIZE =
4; |
| 123 private boolean mDelayedLayoutTabInitRequired = false; |
| 124 |
| 125 private Boolean mTemporarySelectedStack; |
| 126 |
| 127 // Orientation Variables |
| 128 private PortraitViewport mCachedPortraitViewport = null; |
| 129 private PortraitViewport mCachedLandscapeViewport = null; |
| 130 |
| 131 private final ViewGroup mViewContainer; |
| 132 |
| 133 private final TabListSceneLayer mSceneLayer; |
| 134 |
| 135 /** |
| 136 * @param context The current Android's context. |
| 137 * @param updateHost The {@link LayoutUpdateHost} view for this layout. |
| 138 * @param renderHost The {@link LayoutRenderHost} view for this layout. |
| 139 * @param eventFilter The {@link EventFilter} that is needed for this view. |
| 140 */ |
| 141 public StackLayout(Context context, LayoutUpdateHost updateHost, LayoutRende
rHost renderHost, |
| 142 EventFilter eventFilter) { |
| 143 super(context, updateHost, renderHost, eventFilter); |
| 144 |
| 145 final ViewConfiguration configuration = ViewConfiguration.get(context); |
| 146 mMinDirectionThreshold = configuration.getScaledTouchSlop(); |
| 147 mMinShortPressThresholdSqr = |
| 148 configuration.getScaledPagingTouchSlop() * configuration.getScal
edPagingTouchSlop(); |
| 149 |
| 150 mMinMaxInnerMargin = (int) (MIN_INNER_MARGIN_PERCENT_DP + 0.5); |
| 151 mFlingSpeed = FLING_SPEED_DP; |
| 152 mStacks = new Stack[2]; |
| 153 mStacks[0] = new Stack(context, this); |
| 154 mStacks[1] = new Stack(context, this); |
| 155 mStackRects = new RectF[2]; |
| 156 mStackRects[0] = new RectF(); |
| 157 mStackRects[1] = new RectF(); |
| 158 |
| 159 mViewContainer = new FrameLayout(getContext()); |
| 160 mSceneLayer = new TabListSceneLayer(); |
| 161 } |
| 162 |
| 163 @Override |
| 164 public int getSizingFlags() { |
| 165 return SizingFlags.ALLOW_TOOLBAR_SHOW | SizingFlags.REQUIRE_FULLSCREEN_S
IZE; |
| 166 } |
| 167 |
| 168 @Override |
| 169 public void setTabModelSelector(TabModelSelector modelSelector, TabContentMa
nager manager) { |
| 170 super.setTabModelSelector(modelSelector, manager); |
| 171 mStacks[0].setTabModel(modelSelector.getModel(false)); |
| 172 mStacks[1].setTabModel(modelSelector.getModel(true)); |
| 173 resetScrollData(); |
| 174 } |
| 175 |
| 176 /** |
| 177 * Get the tab stack state for the specified mode. |
| 178 * |
| 179 * @param incognito Whether the TabStackState to be returned should be the o
ne for incognito. |
| 180 * @return The tab stack state for the given mode. |
| 181 * @VisibleForTesting |
| 182 */ |
| 183 public Stack getTabStack(boolean incognito) { |
| 184 return mStacks[incognito ? 1 : 0]; |
| 185 } |
| 186 |
| 187 /** |
| 188 * Get the tab stack state. |
| 189 * @return The tab stack index for the given tab id. |
| 190 */ |
| 191 private int getTabStackIndex() { |
| 192 return getTabStackIndex(Tab.INVALID_TAB_ID); |
| 193 } |
| 194 |
| 195 /** |
| 196 * Get the tab stack state for the specified tab id. |
| 197 * |
| 198 * @param tabId The id of the tab to lookup. |
| 199 * @return The tab stack index for the given tab id. |
| 200 * @VisibleForTesting |
| 201 */ |
| 202 protected int getTabStackIndex(int tabId) { |
| 203 if (tabId == Tab.INVALID_TAB_ID) { |
| 204 boolean incognito = mTemporarySelectedStack != null |
| 205 ? mTemporarySelectedStack |
| 206 : mTabModelSelector.isIncognitoSelected(); |
| 207 return incognito ? 1 : 0; |
| 208 } else { |
| 209 return TabModelUtils.getTabById(mTabModelSelector.getModel(true), ta
bId) != null ? 1 |
| 210
: 0; |
| 211 } |
| 212 } |
| 213 |
| 214 /** |
| 215 * Get the tab stack state for the specified tab id. |
| 216 * |
| 217 * @param tabId The id of the tab to lookup. |
| 218 * @return The tab stack state for the given tab id. |
| 219 * @VisibleForTesting |
| 220 */ |
| 221 protected Stack getTabStack(int tabId) { |
| 222 return mStacks[getTabStackIndex(tabId)]; |
| 223 } |
| 224 |
| 225 @Override |
| 226 public void onTabSelecting(long time, int tabId) { |
| 227 mStacks[1].ensureCleaningUpDyingTabs(time); |
| 228 mStacks[0].ensureCleaningUpDyingTabs(time); |
| 229 if (tabId == Tab.INVALID_TAB_ID) tabId = mTabModelSelector.getCurrentTab
Id(); |
| 230 super.onTabSelecting(time, tabId); |
| 231 mStacks[getTabStackIndex()].tabSelectingEffect(time, tabId); |
| 232 startMarginAnimation(false); |
| 233 startYOffsetAnimation(false); |
| 234 finishScrollStacks(); |
| 235 } |
| 236 |
| 237 @Override |
| 238 public void onTabClosing(long time, int id) { |
| 239 Stack stack = getTabStack(id); |
| 240 if (stack == null) return; |
| 241 stack.tabClosingEffect(time, id); |
| 242 |
| 243 // Just in case we closed the last tab of a stack we need to trigger the
overlap animation. |
| 244 startMarginAnimation(true); |
| 245 |
| 246 // Animate the stack to leave incognito mode. |
| 247 if (!mStacks[1].isDisplayable()) uiPreemptivelySelectTabModel(false); |
| 248 } |
| 249 |
| 250 @Override |
| 251 public void onTabsAllClosing(long time, boolean incognito) { |
| 252 super.onTabsAllClosing(time, incognito); |
| 253 getTabStack(incognito).tabsAllClosingEffect(time); |
| 254 // trigger the overlap animation. |
| 255 startMarginAnimation(true); |
| 256 // Animate the stack to leave incognito mode. |
| 257 if (!mStacks[1].isDisplayable()) uiPreemptivelySelectTabModel(false); |
| 258 } |
| 259 |
| 260 @Override |
| 261 public void onTabClosureCancelled(long time, int id, boolean incognito) { |
| 262 super.onTabClosureCancelled(time, id, incognito); |
| 263 getTabStack(incognito).undoClosure(time, id); |
| 264 } |
| 265 |
| 266 @Override |
| 267 public boolean handlesCloseAll() { |
| 268 return true; |
| 269 } |
| 270 |
| 271 @Override |
| 272 public boolean handlesTabCreating() { |
| 273 return true; |
| 274 } |
| 275 |
| 276 @Override |
| 277 public boolean handlesTabClosing() { |
| 278 return true; |
| 279 } |
| 280 |
| 281 @Override |
| 282 public void attachViews(ViewGroup container) { |
| 283 // TODO(dtrainor): This is a hack. We're attaching to the parent of the
view container |
| 284 // which is the content container of the Activity. |
| 285 ((ViewGroup) container.getParent()) |
| 286 .addView(mViewContainer, |
| 287 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams
.MATCH_PARENT)); |
| 288 } |
| 289 |
| 290 @Override |
| 291 public void detachViews() { |
| 292 if (mViewContainer.getParent() != null) { |
| 293 ((ViewGroup) mViewContainer.getParent()).removeView(mViewContainer); |
| 294 } |
| 295 mViewContainer.removeAllViews(); |
| 296 } |
| 297 |
| 298 /** |
| 299 * @return A {@link ViewGroup} that {@link Stack}s can use to interact with
the Android view |
| 300 * hierarchy. |
| 301 */ |
| 302 public ViewGroup getViewContainer() { |
| 303 return mViewContainer; |
| 304 } |
| 305 |
| 306 @Override |
| 307 public void onTabCreated(long time, int id, int tabIndex, int sourceId, bool
ean newIsIncognito, |
| 308 boolean background, float originX, float originY) { |
| 309 super.onTabCreated( |
| 310 time, id, tabIndex, sourceId, newIsIncognito, background, origin
X, originY); |
| 311 startHiding(id, false); |
| 312 mStacks[getTabStackIndex(id)].tabCreated(time, id); |
| 313 startMarginAnimation(false); |
| 314 uiPreemptivelySelectTabModel(newIsIncognito); |
| 315 } |
| 316 |
| 317 @Override |
| 318 public void onTabModelSwitched(boolean toIncognitoTabModel) { |
| 319 flingStacks(toIncognitoTabModel); |
| 320 mFlingFromModelChange = true; |
| 321 } |
| 322 |
| 323 @Override |
| 324 public boolean onUpdateAnimation(long time, boolean jumpToEnd) { |
| 325 boolean animationsWasDone = super.onUpdateAnimation(time, jumpToEnd); |
| 326 boolean finishedView0 = mStacks[0].onUpdateViewAnimation(time, jumpToEnd
); |
| 327 boolean finishedView1 = mStacks[1].onUpdateViewAnimation(time, jumpToEnd
); |
| 328 boolean finishedCompositor0 = mStacks[0].onUpdateCompositorAnimations(ti
me, jumpToEnd); |
| 329 boolean finishedCompositor1 = mStacks[1].onUpdateCompositorAnimations(ti
me, jumpToEnd); |
| 330 if (animationsWasDone && finishedView0 && finishedView1 && finishedCompo
sitor0 |
| 331 && finishedCompositor1) { |
| 332 return true; |
| 333 } else { |
| 334 if (!animationsWasDone || !finishedCompositor0 || !finishedComposito
r1) { |
| 335 requestStackUpdate(); |
| 336 } |
| 337 |
| 338 return false; |
| 339 } |
| 340 } |
| 341 |
| 342 @Override |
| 343 protected void onAnimationStarted() { |
| 344 if (mStackAnimationCount == 0) super.onAnimationStarted(); |
| 345 } |
| 346 |
| 347 @Override |
| 348 protected void onAnimationFinished() { |
| 349 mFlingFromModelChange = false; |
| 350 if (mTemporarySelectedStack != null) { |
| 351 mTabModelSelector.selectModel(mTemporarySelectedStack); |
| 352 mTemporarySelectedStack = null; |
| 353 } |
| 354 if (mStackAnimationCount == 0) super.onAnimationFinished(); |
| 355 } |
| 356 |
| 357 /** |
| 358 * Called when a UI element is attempting to select a tab. This will perfor
m the animation |
| 359 * and then actually propagate the action. This starts hiding this layout w
hich, when complete, |
| 360 * will actually select the tab. |
| 361 * @param time The current time of the app in ms. |
| 362 * @param id The id of the tab to select. |
| 363 */ |
| 364 public void uiSelectingTab(long time, int id) { |
| 365 onTabSelecting(time, id); |
| 366 } |
| 367 |
| 368 /** |
| 369 * Called when a UI element is attempting to close a tab. This will perform
the required close |
| 370 * animations. When the UI is ready to actually close the tab |
| 371 * {@link #uiDoneClosingTab(long, int, boolean, boolean)} should be called t
o actually propagate |
| 372 * the event to the model. |
| 373 * @param time The current time of the app in ms. |
| 374 * @param id The id of the tab to close. |
| 375 */ |
| 376 public void uiRequestingCloseTab(long time, int id) { |
| 377 // Start the tab closing effect if necessary. |
| 378 getTabStack(id).tabClosingEffect(time, id); |
| 379 |
| 380 int incognitoCount = mTabModelSelector.getModel(true).getCount(); |
| 381 TabModel model = mTabModelSelector.getModelForTabId(id); |
| 382 if (model != null && model.isIncognito()) incognitoCount--; |
| 383 boolean incognitoVisible = incognitoCount > 0; |
| 384 |
| 385 // Make sure we show/hide both stacks depending on which tab we're closi
ng. |
| 386 startMarginAnimation(true, incognitoVisible); |
| 387 if (!incognitoVisible) uiPreemptivelySelectTabModel(false); |
| 388 } |
| 389 |
| 390 /** |
| 391 * Called when a UI element is done animating the close tab effect started b
y |
| 392 * {@link #uiRequestingCloseTab(long, int)}. This actually pushes the close
event to the model. |
| 393 * @param time The current time of the app in ms. |
| 394 * @param id The id of the tab to close. |
| 395 * @param canUndo Whether or not this close can be undone. |
| 396 * @param incognito Whether or not this was for the incognito stack or not. |
| 397 */ |
| 398 public void uiDoneClosingTab(long time, int id, boolean canUndo, boolean inc
ognito) { |
| 399 // If homepage is enabled and there is a maximum of 1 tab in both models |
| 400 // (this is the last tab), the tab closure cannot be undone. |
| 401 canUndo &= !(HomepageManager.isHomepageEnabled(getContext()) |
| 402 && (mTabModelSelector.getModel(true).getCount() |
| 403 + mTabModelSelector.getModel(false
).getCount() |
| 404 < 2)); |
| 405 |
| 406 // Propagate the tab closure to the model. |
| 407 TabModelUtils.closeTabById(mTabModelSelector.getModel(incognito), id, ca
nUndo); |
| 408 } |
| 409 |
| 410 public void uiDoneClosingAllTabs(boolean incognito) { |
| 411 // Propagate the tab closure to the model. |
| 412 mTabModelSelector.getModel(incognito).closeAllTabs(false, false); |
| 413 } |
| 414 |
| 415 /** |
| 416 * Called when a {@link Stack} instance is done animating the stack enter ef
fect. |
| 417 */ |
| 418 public void uiDoneEnteringStack() { |
| 419 mSortingComparator = mVisibilityComparator; |
| 420 doneShowing(); |
| 421 } |
| 422 |
| 423 private void uiPreemptivelySelectTabModel(boolean incognito) { |
| 424 onTabModelSwitched(incognito); |
| 425 } |
| 426 |
| 427 /** |
| 428 * Starts the animation for the opposite stack to slide in or out when enter
ing |
| 429 * or leaving stack view. The animation should be super fast to match more
or less |
| 430 * the fling animation. |
| 431 * @param enter True if the stack view is being entered, false if the stack
view |
| 432 * is being left. |
| 433 */ |
| 434 private void startMarginAnimation(boolean enter) { |
| 435 startMarginAnimation(enter, mStacks[1].isDisplayable()); |
| 436 } |
| 437 |
| 438 private void startMarginAnimation(boolean enter, boolean showIncognito) { |
| 439 float start = mInnerMarginPercent; |
| 440 float end = enter && showIncognito ? 1.0f : 0.0f; |
| 441 if (start != end) { |
| 442 addToAnimation(this, Property.INNER_MARGIN_PERCENT, start, end, 200,
0); |
| 443 } |
| 444 } |
| 445 |
| 446 private void startYOffsetAnimation(boolean enter) { |
| 447 float start = mStackOffsetYPercent; |
| 448 float end = enter ? 1.f : 0.f; |
| 449 if (start != end) { |
| 450 addToAnimation(this, Property.STACK_OFFSET_Y_PERCENT, start, end, 30
0, 0); |
| 451 } |
| 452 } |
| 453 |
| 454 @Override |
| 455 public void show(long time, boolean animate) { |
| 456 super.show(time, animate); |
| 457 |
| 458 Tab tab = mTabModelSelector.getCurrentTab(); |
| 459 if (tab != null && tab.isNativePage()) mTabContentManager.cacheTabThumbn
ail(tab); |
| 460 |
| 461 // Remove any views in case we're getting another call to show before we
hide (quickly |
| 462 // toggling the tab switcher button). |
| 463 mViewContainer.removeAllViews(); |
| 464 |
| 465 for (int i = mStacks.length - 1; i >= 0; --i) { |
| 466 mStacks[i].reset(); |
| 467 if (mStacks[i].isDisplayable()) { |
| 468 mStacks[i].show(); |
| 469 } else { |
| 470 mStacks[i].cleanupTabs(); |
| 471 } |
| 472 } |
| 473 // Initialize the animation and the positioning of all the elements |
| 474 mSortingComparator = mOrderComparator; |
| 475 resetScrollData(); |
| 476 for (int i = mStacks.length - 1; i >= 0; --i) { |
| 477 if (mStacks[i].isDisplayable()) { |
| 478 boolean offscreen = (i != getTabStackIndex()); |
| 479 mStacks[i].stackEntered(time, !offscreen); |
| 480 } |
| 481 } |
| 482 startMarginAnimation(true); |
| 483 startYOffsetAnimation(true); |
| 484 flingStacks(getTabStackIndex() == 1); |
| 485 |
| 486 if (!animate) onUpdateAnimation(time, true); |
| 487 |
| 488 // We will render before we get a call to updateLayout. Need to make su
re all of the tabs |
| 489 // we need to render are up to date. |
| 490 updateLayout(time, 0); |
| 491 } |
| 492 |
| 493 @Override |
| 494 public void swipeStarted(long time, ScrollDirection direction, float x, floa
t y) { |
| 495 mStacks[getTabStackIndex()].swipeStarted(time, direction, x, y); |
| 496 } |
| 497 |
| 498 @Override |
| 499 public void swipeUpdated(long time, float x, float y, float dx, float dy, fl
oat tx, float ty) { |
| 500 mStacks[getTabStackIndex()].swipeUpdated(time, x, y, dx, dy, tx, ty); |
| 501 } |
| 502 |
| 503 @Override |
| 504 public void swipeFinished(long time) { |
| 505 mStacks[getTabStackIndex()].swipeFinished(time); |
| 506 } |
| 507 |
| 508 @Override |
| 509 public void swipeCancelled(long time) { |
| 510 mStacks[getTabStackIndex()].swipeCancelled(time); |
| 511 } |
| 512 |
| 513 @Override |
| 514 public void swipeFlingOccurred( |
| 515 long time, float x, float y, float tx, float ty, float vx, float vy)
{ |
| 516 mStacks[getTabStackIndex()].swipeFlingOccurred(time, x, y, tx, ty, vx, v
y); |
| 517 } |
| 518 |
| 519 private void requestStackUpdate() { |
| 520 // TODO(jgreenwald): It isn't always necessary to invalidate both |
| 521 // stacks. |
| 522 mStacks[0].requestUpdate(); |
| 523 mStacks[1].requestUpdate(); |
| 524 } |
| 525 |
| 526 @Override |
| 527 public void notifySizeChanged(float width, float height, int orientation) { |
| 528 mCachedLandscapeViewport = null; |
| 529 mCachedPortraitViewport = null; |
| 530 mStacks[0].notifySizeChanged(width, height, orientation); |
| 531 mStacks[1].notifySizeChanged(width, height, orientation); |
| 532 resetScrollData(); |
| 533 requestStackUpdate(); |
| 534 } |
| 535 |
| 536 @Override |
| 537 public void contextChanged(Context context) { |
| 538 super.contextChanged(context); |
| 539 StackTab.resetDimensionConstants(context); |
| 540 mStacks[0].contextChanged(context); |
| 541 mStacks[1].contextChanged(context); |
| 542 requestStackUpdate(); |
| 543 } |
| 544 |
| 545 @Override |
| 546 public void drag(long time, float x, float y, float amountX, float amountY)
{ |
| 547 SwipeMode oldInputMode = mInputMode; |
| 548 mInputMode = computeInputMode(time, x, y, amountX, amountY); |
| 549 |
| 550 if (oldInputMode == SwipeMode.SEND_TO_STACK && mInputMode == SwipeMode.S
WITCH_STACK) { |
| 551 mStacks[getTabStackIndex()].onUpOrCancel(time); |
| 552 } else if (oldInputMode == SwipeMode.SWITCH_STACK |
| 553 && mInputMode == SwipeMode.SEND_TO_STACK) { |
| 554 onUpOrCancel(time); |
| 555 } |
| 556 |
| 557 if (mInputMode == SwipeMode.SEND_TO_STACK) { |
| 558 mStacks[getTabStackIndex()].drag(time, x, y, amountX, amountY); |
| 559 } else if (mInputMode == SwipeMode.SWITCH_STACK) { |
| 560 scrollStacks(getOrientation() == Orientation.PORTRAIT ? amountX : am
ountY); |
| 561 } |
| 562 } |
| 563 |
| 564 /** |
| 565 * Computes the input mode for drag and fling based on the first event posit
ion. |
| 566 * @param time The current time of the app in ms. |
| 567 * @param x The x layout position of the mouse (without the displacement)
. |
| 568 * @param y The y layout position of the mouse (without the displacement)
. |
| 569 * @param dx The x displacement happening this frame. |
| 570 * @param dy The y displacement happening this frame. |
| 571 * @return The input mode to select. |
| 572 */ |
| 573 private SwipeMode computeInputMode(long time, float x, float y, float dx, fl
oat dy) { |
| 574 if (!mStacks[1].isDisplayable()) return SwipeMode.SEND_TO_STACK; |
| 575 int currentIndex = getTabStackIndex(); |
| 576 if (currentIndex != getViewportParameters().getStackIndexAt(x, y)) { |
| 577 return SwipeMode.SWITCH_STACK; |
| 578 } |
| 579 float relativeX = mLastOnDownX - (x + dx); |
| 580 float relativeY = mLastOnDownY - (y + dy); |
| 581 float distanceToDownSqr = dx * dx + dy * dy; |
| 582 float switchDelta = getOrientation() == Orientation.PORTRAIT ? relativeX
: relativeY; |
| 583 float otherDelta = getOrientation() == Orientation.PORTRAIT ? relativeY
: relativeX; |
| 584 |
| 585 // Dragging in the opposite direction of the stack switch |
| 586 if (distanceToDownSqr > mMinDirectionThreshold * mMinDirectionThreshold |
| 587 && Math.abs(otherDelta) > Math.abs(switchDelta)) { |
| 588 return SwipeMode.SEND_TO_STACK; |
| 589 } |
| 590 // Dragging in a direction the stack cannot switch |
| 591 if (Math.abs(switchDelta) > mMinDirectionThreshold) { |
| 592 if ((currentIndex == 0) ^ (switchDelta > 0) |
| 593 ^ (getOrientation() == Orientation.PORTRAIT |
| 594 && LocalizationUtils.isLayoutRtl())) { |
| 595 return SwipeMode.SEND_TO_STACK; |
| 596 } |
| 597 } |
| 598 if (isDraggingStackInWrongDirection( |
| 599 mLastOnDownX, mLastOnDownY, x, y, dx, dy, getOrientation(),
currentIndex)) { |
| 600 return SwipeMode.SWITCH_STACK; |
| 601 } |
| 602 // Not moving the finger |
| 603 if (time - mLastOnDownTimeStamp > THRESHOLD_TIME_TO_SWITCH_STACK_INPUT_M
ODE) { |
| 604 return SwipeMode.SEND_TO_STACK; |
| 605 } |
| 606 // Dragging fast |
| 607 if (distanceToDownSqr > mMinShortPressThresholdSqr) { |
| 608 return SwipeMode.SWITCH_STACK; |
| 609 } |
| 610 return SwipeMode.NONE; |
| 611 } |
| 612 |
| 613 @Override |
| 614 public void fling(long time, float x, float y, float vx, float vy) { |
| 615 if (mInputMode == SwipeMode.NONE) { |
| 616 mInputMode = computeInputMode( |
| 617 time, x, y, vx * SWITCH_STACK_FLING_DT, vy * SWITCH_STACK_FL
ING_DT); |
| 618 } |
| 619 |
| 620 if (mInputMode == SwipeMode.SEND_TO_STACK) { |
| 621 mStacks[getTabStackIndex()].fling(time, x, y, vx, vy); |
| 622 } else if (mInputMode == SwipeMode.SWITCH_STACK) { |
| 623 final float velocity = getOrientation() == Orientation.PORTRAIT ? vx
: vy; |
| 624 final float origin = getOrientation() == Orientation.PORTRAIT ? x :
y; |
| 625 final float max = getOrientation() == Orientation.PORTRAIT ? getWidt
h() : getHeight(); |
| 626 final float predicted = origin + velocity * SWITCH_STACK_FLING_DT; |
| 627 final float delta = MathUtils.clamp(predicted, 0, max) - origin; |
| 628 scrollStacks(delta); |
| 629 } |
| 630 requestStackUpdate(); |
| 631 } |
| 632 |
| 633 class PortraitViewport { |
| 634 protected float mWidth, mHeight; |
| 635 PortraitViewport() { |
| 636 mWidth = StackLayout.this.getWidth(); |
| 637 mHeight = StackLayout.this.getHeightMinusTopControls(); |
| 638 } |
| 639 |
| 640 float getClampedRenderedScrollOffset() { |
| 641 if (mStacks[1].isDisplayable() || mFlingFromModelChange) { |
| 642 return MathUtils.clamp(mRenderedScrollOffset, 0, -1); |
| 643 } else { |
| 644 return 0; |
| 645 } |
| 646 } |
| 647 |
| 648 float getInnerMargin() { |
| 649 float margin = mInnerMarginPercent |
| 650 * Math.max(mMinMaxInnerMargin, mWidth * INNER_MARGIN_PERCENT
_PERCENT); |
| 651 return margin; |
| 652 } |
| 653 |
| 654 int getStackIndexAt(float x, float y) { |
| 655 if (LocalizationUtils.isLayoutRtl()) { |
| 656 // On RTL portrait mode, stack1 (incognito) is on the left. |
| 657 float separation = getStack0Left(); |
| 658 return x < separation ? 1 : 0; |
| 659 } else { |
| 660 float separation = getStack0Left() + getWidth(); |
| 661 return x < separation ? 0 : 1; |
| 662 } |
| 663 } |
| 664 |
| 665 float getStack0Left() { |
| 666 return LocalizationUtils.isLayoutRtl() |
| 667 ? getInnerMargin() - getClampedRenderedScrollOffset() * getF
ullScrollDistance() |
| 668 : getClampedRenderedScrollOffset() * getFullScrollDistance()
; |
| 669 } |
| 670 |
| 671 float getWidth() { |
| 672 return mWidth - getInnerMargin(); |
| 673 } |
| 674 |
| 675 float getHeight() { |
| 676 return mHeight; |
| 677 } |
| 678 |
| 679 float getStack0Top() { |
| 680 return getTopHeightOffset(); |
| 681 } |
| 682 |
| 683 float getStack0ToStack1TranslationX() { |
| 684 return Math.round(LocalizationUtils.isLayoutRtl() ? -mWidth + getInn
erMargin() : mWidth |
| 685 - getInnerMargin()); |
| 686 } |
| 687 |
| 688 float getStack0ToStack1TranslationY() { |
| 689 return 0.0f; |
| 690 } |
| 691 |
| 692 float getTopHeightOffset() { |
| 693 return (StackLayout.this.getHeight() - getHeightMinusTopControls()) |
| 694 * mStackOffsetYPercent; |
| 695 } |
| 696 } |
| 697 |
| 698 class LandscapeViewport extends PortraitViewport { |
| 699 LandscapeViewport() { |
| 700 // This is purposefully inverted. |
| 701 mWidth = StackLayout.this.getHeightMinusTopControls(); |
| 702 mHeight = StackLayout.this.getWidth(); |
| 703 } |
| 704 |
| 705 @Override |
| 706 float getInnerMargin() { |
| 707 float margin = mInnerMarginPercent |
| 708 * Math.max(mMinMaxInnerMargin, mWidth * INNER_MARGIN_PERCENT
_PERCENT); |
| 709 return margin; |
| 710 } |
| 711 |
| 712 @Override |
| 713 int getStackIndexAt(float x, float y) { |
| 714 float separation = getStack0Top() + getHeight(); |
| 715 return y < separation ? 0 : 1; |
| 716 } |
| 717 |
| 718 @Override |
| 719 float getStack0Left() { |
| 720 return 0.f; |
| 721 } |
| 722 |
| 723 @Override |
| 724 float getStack0Top() { |
| 725 return getClampedRenderedScrollOffset() * getFullScrollDistance() |
| 726 + getTopHeightOffset(); |
| 727 } |
| 728 |
| 729 @Override |
| 730 float getWidth() { |
| 731 return super.getHeight(); |
| 732 } |
| 733 |
| 734 @Override |
| 735 float getHeight() { |
| 736 return super.getWidth(); |
| 737 } |
| 738 |
| 739 @Override |
| 740 float getStack0ToStack1TranslationX() { |
| 741 return super.getStack0ToStack1TranslationY(); |
| 742 } |
| 743 |
| 744 @Override |
| 745 float getStack0ToStack1TranslationY() { |
| 746 return Math.round(mWidth - getInnerMargin()); |
| 747 } |
| 748 } |
| 749 |
| 750 private PortraitViewport getViewportParameters() { |
| 751 if (getOrientation() == Orientation.PORTRAIT) { |
| 752 if (mCachedPortraitViewport == null) { |
| 753 mCachedPortraitViewport = new PortraitViewport(); |
| 754 } |
| 755 return mCachedPortraitViewport; |
| 756 } else { |
| 757 if (mCachedLandscapeViewport == null) { |
| 758 mCachedLandscapeViewport = new LandscapeViewport(); |
| 759 } |
| 760 return mCachedLandscapeViewport; |
| 761 } |
| 762 } |
| 763 |
| 764 @Override |
| 765 public void click(long time, float x, float y) { |
| 766 // Click event happens before the up event. mClicked is set to mute the
up event. |
| 767 mClicked = true; |
| 768 PortraitViewport viewportParams = getViewportParameters(); |
| 769 int stackIndexAt = viewportParams.getStackIndexAt(x, y); |
| 770 if (stackIndexAt == getTabStackIndex()) { |
| 771 mStacks[getTabStackIndex()].click(time, x, y); |
| 772 } else { |
| 773 flingStacks(getTabStackIndex() == 0); |
| 774 } |
| 775 requestStackUpdate(); |
| 776 } |
| 777 |
| 778 /** |
| 779 * Check if we are dragging stack in a wrong direction. |
| 780 * |
| 781 * @param downX The X coordinate on the last down event. |
| 782 * @param downY The Y coordinate on the last down event. |
| 783 * @param x The current X coordinate. |
| 784 * @param y The current Y coordinate. |
| 785 * @param dx The amount of change in X coordinate. |
| 786 * @param dy The amount of change in Y coordinate. |
| 787 * @param orientation The device orientation (portrait / landscape). |
| 788 * @param stackIndex The index of stack tab. |
| 789 * @return True iff we are dragging stack in a wrong direction. |
| 790 */ |
| 791 @VisibleForTesting |
| 792 public static boolean isDraggingStackInWrongDirection(float downX, float dow
nY, float x, |
| 793 float y, float dx, float dy, int orientation, int stackIndex) { |
| 794 float switchDelta = orientation == Orientation.PORTRAIT ? x - downX : y
- downY; |
| 795 |
| 796 // Should not prevent scrolling even when switchDelta is in a wrong dire
ction. |
| 797 if (Math.abs(dx) < Math.abs(dy)) { |
| 798 return false; |
| 799 } |
| 800 return (stackIndex == 0 && switchDelta < 0) || (stackIndex == 1 && switc
hDelta > 0); |
| 801 } |
| 802 |
| 803 private void scrollStacks(float delta) { |
| 804 cancelAnimation(this, Property.STACK_SNAP); |
| 805 float fullDistance = getFullScrollDistance(); |
| 806 mScrollIndexOffset += MathUtils.flipSignIf(delta / fullDistance, |
| 807 getOrientation() == Orientation.PORTRAIT && LocalizationUtils.is
LayoutRtl()); |
| 808 if (canScrollLinearly(getTabStackIndex())) { |
| 809 mRenderedScrollOffset = mScrollIndexOffset; |
| 810 } else { |
| 811 mRenderedScrollOffset = (int) MathUtils.clamp( |
| 812 mScrollIndexOffset, 0, mStacks[1].isDisplayable() ? -1 : 0); |
| 813 } |
| 814 requestStackUpdate(); |
| 815 } |
| 816 |
| 817 private void flingStacks(boolean toIncognito) { |
| 818 // velocityX is measured in pixel per second. |
| 819 if (!canScrollLinearly(toIncognito ? 0 : 1)) return; |
| 820 setActiveStackState(toIncognito); |
| 821 finishScrollStacks(); |
| 822 requestStackUpdate(); |
| 823 } |
| 824 |
| 825 /** |
| 826 * Animate to the final position of the stack. Unfortunately, both touch-up |
| 827 * and fling can be called and this depends on fling always being called las
t. |
| 828 * If fling is called first, onUpOrCancel can override the fling position |
| 829 * with the opposite. For example, if the user does a very small fling from |
| 830 * incognito to non-incognito, which leaves the up event in the incognito si
de. |
| 831 */ |
| 832 private void finishScrollStacks() { |
| 833 cancelAnimation(this, Property.STACK_SNAP); |
| 834 final int currentModelIndex = getTabStackIndex(); |
| 835 float delta = Math.abs(currentModelIndex + mRenderedScrollOffset); |
| 836 float target = -currentModelIndex; |
| 837 if (delta != 0) { |
| 838 long duration = FLING_MIN_DURATION |
| 839 + (long) Math.abs(delta * getFullScrollDistance() / mFlingSp
eed); |
| 840 addToAnimation(this, Property.STACK_SNAP, mRenderedScrollOffset, tar
get, duration, 0); |
| 841 } else { |
| 842 setProperty(Property.STACK_SNAP, target); |
| 843 if (mTemporarySelectedStack != null) { |
| 844 mTabModelSelector.selectModel(mTemporarySelectedStack); |
| 845 mTemporarySelectedStack = null; |
| 846 } |
| 847 } |
| 848 } |
| 849 |
| 850 @Override |
| 851 public void onDown(long time, float x, float y) { |
| 852 mLastOnDownX = x; |
| 853 mLastOnDownY = y; |
| 854 mLastOnDownTimeStamp = time; |
| 855 mInputMode = computeInputMode(time, x, y, 0, 0); |
| 856 mStacks[getTabStackIndex()].onDown(time); |
| 857 } |
| 858 |
| 859 @Override |
| 860 public void onLongPress(long time, float x, float y) { |
| 861 mStacks[getTabStackIndex()].onLongPress(time, x, y); |
| 862 } |
| 863 |
| 864 @Override |
| 865 public void onUpOrCancel(long time) { |
| 866 int currentIndex = getTabStackIndex(); |
| 867 int nextIndex = 1 - currentIndex; |
| 868 if (!mClicked && Math.abs(currentIndex + mRenderedScrollOffset) > THRESH
OLD_TO_SWITCH_STACK |
| 869 && mStacks[nextIndex].isDisplayable()) { |
| 870 setActiveStackState(nextIndex == 1); |
| 871 } |
| 872 mClicked = false; |
| 873 finishScrollStacks(); |
| 874 mStacks[getTabStackIndex()].onUpOrCancel(time); |
| 875 mInputMode = SwipeMode.NONE; |
| 876 } |
| 877 |
| 878 /** |
| 879 * Pushes a rectangle to be drawn on the screen on top of everything. |
| 880 * |
| 881 * @param rect The rectangle to be drawn on screen |
| 882 * @param color The color of the rectangle |
| 883 */ |
| 884 public void pushDebugRect(Rect rect, int color) { |
| 885 if (rect.left > rect.right) { |
| 886 int tmp = rect.right; |
| 887 rect.right = rect.left; |
| 888 rect.left = tmp; |
| 889 } |
| 890 if (rect.top > rect.bottom) { |
| 891 int tmp = rect.bottom; |
| 892 rect.bottom = rect.top; |
| 893 rect.top = tmp; |
| 894 } |
| 895 mRenderHost.pushDebugRect(rect, color); |
| 896 } |
| 897 |
| 898 @Override |
| 899 public void onPinch(long time, float x0, float y0, float x1, float y1, boole
an firstEvent) { |
| 900 mStacks[getTabStackIndex()].onPinch(time, x0, y0, x1, y1, firstEvent); |
| 901 } |
| 902 |
| 903 @Override |
| 904 protected void updateLayout(long time, long dt) { |
| 905 super.updateLayout(time, dt); |
| 906 boolean needUpdate = false; |
| 907 |
| 908 final PortraitViewport viewport = getViewportParameters(); |
| 909 mStackRects[0].left = viewport.getStack0Left(); |
| 910 mStackRects[0].right = mStackRects[0].left + viewport.getWidth(); |
| 911 mStackRects[0].top = viewport.getStack0Top(); |
| 912 mStackRects[0].bottom = mStackRects[0].top + viewport.getHeight(); |
| 913 mStackRects[1].left = mStackRects[0].left + viewport.getStack0ToStack1Tr
anslationX(); |
| 914 mStackRects[1].right = mStackRects[1].left + viewport.getWidth(); |
| 915 mStackRects[1].top = mStackRects[0].top + viewport.getStack0ToStack1Tran
slationY(); |
| 916 mStackRects[1].bottom = mStackRects[1].top + viewport.getHeight(); |
| 917 |
| 918 mStacks[0].setStackFocusInfo(1.0f + mRenderedScrollOffset, |
| 919 mSortingComparator == mOrderComparator ? mTabModelSelector.getMo
del(false).index() |
| 920 : -1); |
| 921 mStacks[1].setStackFocusInfo(-mRenderedScrollOffset, mSortingComparator
== mOrderComparator |
| 922 ? mTabModelSelector.getModel(true).index() |
| 923 : -1); |
| 924 |
| 925 // Compute position and visibility |
| 926 mStacks[0].computeTabPosition(time, mStackRects[0]); |
| 927 mStacks[1].computeTabPosition(time, mStackRects[1]); |
| 928 |
| 929 // Pre-allocate/resize {@link #mLayoutTabs} before it get populated by |
| 930 // computeTabPositionAndAppendLayoutTabs. |
| 931 final int tabVisibleCount = mStacks[0].getVisibleCount() + mStacks[1].ge
tVisibleCount(); |
| 932 |
| 933 if (tabVisibleCount == 0) { |
| 934 mLayoutTabs = null; |
| 935 } else if (mLayoutTabs == null || mLayoutTabs.length != tabVisibleCount)
{ |
| 936 mLayoutTabs = new LayoutTab[tabVisibleCount]; |
| 937 } |
| 938 |
| 939 int index = 0; |
| 940 if (getTabStackIndex() == 1) { |
| 941 index = appendVisibleLayoutTabs(time, 0, mLayoutTabs, index); |
| 942 index = appendVisibleLayoutTabs(time, 1, mLayoutTabs, index); |
| 943 } else { |
| 944 index = appendVisibleLayoutTabs(time, 1, mLayoutTabs, index); |
| 945 index = appendVisibleLayoutTabs(time, 0, mLayoutTabs, index); |
| 946 } |
| 947 assert index == tabVisibleCount : "index should be incremented up to tab
VisibleCount"; |
| 948 |
| 949 // Update tab snapping |
| 950 for (int i = 0; i < tabVisibleCount; i++) { |
| 951 if (mLayoutTabs[i].updateSnap(dt)) needUpdate = true; |
| 952 } |
| 953 |
| 954 if (needUpdate) requestUpdate(); |
| 955 |
| 956 // Since we've updated the positions of the stacks and tabs, let's go ah
ead and update |
| 957 // the visible tabs. |
| 958 updateTabPriority(); |
| 959 } |
| 960 |
| 961 private int appendVisibleLayoutTabs(long time, int stackIndex, LayoutTab[] t
abs, int tabIndex) { |
| 962 final StackTab[] stackTabs = mStacks[stackIndex].getTabs(); |
| 963 if (stackTabs != null) { |
| 964 for (int i = 0; i < stackTabs.length; i++) { |
| 965 LayoutTab t = stackTabs[i].getLayoutTab(); |
| 966 if (t.isVisible()) tabs[tabIndex++] = t; |
| 967 } |
| 968 } |
| 969 return tabIndex; |
| 970 } |
| 971 |
| 972 /** |
| 973 * Sets the active tab stack. |
| 974 * |
| 975 * @param isIncognito True if the model to select is incognito. |
| 976 * @return Whether the tab stack index passed in differed from the currently
selected stack. |
| 977 */ |
| 978 public boolean setActiveStackState(boolean isIncognito) { |
| 979 if (isIncognito == mTabModelSelector.isIncognitoSelected()) return false
; |
| 980 mTemporarySelectedStack = isIncognito; |
| 981 return true; |
| 982 } |
| 983 |
| 984 private void resetScrollData() { |
| 985 mScrollIndexOffset = -getTabStackIndex(); |
| 986 mRenderedScrollOffset = mScrollIndexOffset; |
| 987 } |
| 988 |
| 989 /** |
| 990 * Based on the current position, determine if we will map mScrollDistance
linearly to |
| 991 * mRenderedScrollDistance. The logic is, if there is only stack, we will n
ot map linearly; |
| 992 * if we are scrolling two the boundary of either of the stacks, we will no
t map linearly; |
| 993 * otherwise yes. |
| 994 */ |
| 995 private boolean canScrollLinearly(int fromStackIndex) { |
| 996 int count = mStacks.length; |
| 997 if (!(mScrollIndexOffset <= 0 && -mScrollIndexOffset <= (count - 1))) { |
| 998 return false; |
| 999 } |
| 1000 // since we only have two stacks now, we have a shortcut to calculate |
| 1001 // empty stacks |
| 1002 return mStacks[fromStackIndex ^ 0x01].isDisplayable(); |
| 1003 } |
| 1004 |
| 1005 private float getFullScrollDistance() { |
| 1006 float distance = |
| 1007 getOrientation() == Orientation.PORTRAIT ? getWidth() : getHeigh
tMinusTopControls(); |
| 1008 return distance - 2 * getViewportParameters().getInnerMargin(); |
| 1009 } |
| 1010 |
| 1011 @Override |
| 1012 public void doneHiding() { |
| 1013 super.doneHiding(); |
| 1014 mTabModelSelector.commitAllTabClosures(); |
| 1015 } |
| 1016 |
| 1017 /** |
| 1018 * Extracts the tabs from a stack and append them into a list. |
| 1019 * @param stack The stack that contains the tabs. |
| 1020 * @param outList The output list where will be the tabs from the stack. |
| 1021 * @param index The current number of item in the outList. |
| 1022 * @return The updated index incremented by the number of tabs in the stack. |
| 1023 */ |
| 1024 private static int addAllTabs(Stack stack, StackTab[] outList, int index) { |
| 1025 StackTab[] stackTabs = stack.getTabs(); |
| 1026 if (stackTabs != null) { |
| 1027 for (int i = 0; i < stackTabs.length; ++i) { |
| 1028 outList[index++] = stackTabs[i]; |
| 1029 } |
| 1030 } |
| 1031 return index; |
| 1032 } |
| 1033 |
| 1034 /** |
| 1035 * Comparator that helps ordering StackTab's visibility sorting value in a d
ecreasing order. |
| 1036 */ |
| 1037 private static class VisibilityComparator implements Comparator<StackTab>, S
erializable { |
| 1038 @Override |
| 1039 public int compare(StackTab tab1, StackTab tab2) { |
| 1040 return (int) (tab2.getVisiblitySortingValue() - tab1.getVisiblitySor
tingValue()); |
| 1041 } |
| 1042 } |
| 1043 |
| 1044 /** |
| 1045 * Comparator that helps ordering StackTab's visibility sorting value in a d
ecreasing order. |
| 1046 */ |
| 1047 private static class OrderComparator implements Comparator<StackTab>, Serial
izable { |
| 1048 @Override |
| 1049 public int compare(StackTab tab1, StackTab tab2) { |
| 1050 return tab1.getOrderSortingValue() - tab2.getOrderSortingValue(); |
| 1051 } |
| 1052 } |
| 1053 |
| 1054 private boolean updateSortedPriorityArray(Comparator<StackTab> comparator) { |
| 1055 final int allTabsCount = mStacks[0].getCount() + mStacks[1].getCount(); |
| 1056 if (allTabsCount == 0) return false; |
| 1057 if (mSortedPriorityArray == null || mSortedPriorityArray.length != allTa
bsCount) { |
| 1058 mSortedPriorityArray = new StackTab[allTabsCount]; |
| 1059 } |
| 1060 int sortedOffset = 0; |
| 1061 sortedOffset = addAllTabs(mStacks[0], mSortedPriorityArray, sortedOffset
); |
| 1062 sortedOffset = addAllTabs(mStacks[1], mSortedPriorityArray, sortedOffset
); |
| 1063 assert sortedOffset == mSortedPriorityArray.length; |
| 1064 Arrays.sort(mSortedPriorityArray, comparator); |
| 1065 return true; |
| 1066 } |
| 1067 |
| 1068 /** |
| 1069 * Updates the priority list of the {@link LayoutTab} and sends it the syste
ms having processing |
| 1070 * to do on a per {@link LayoutTab} basis. Priority meaning may change based
on the current |
| 1071 * comparator stored in {@link #mSortingComparator}. |
| 1072 * |
| 1073 * Do not use {@link #mSortedPriorityArray} out side this context. It is onl
y a member to avoid |
| 1074 * doing an allocation every frames. |
| 1075 */ |
| 1076 private void updateTabPriority() { |
| 1077 if (!updateSortedPriorityArray(mSortingComparator)) return; |
| 1078 updateTabsVisibility(mSortedPriorityArray); |
| 1079 updateDelayedLayoutTabInit(mSortedPriorityArray); |
| 1080 } |
| 1081 |
| 1082 /** |
| 1083 * Updates the list of visible tab Id that the tab content manager is suppos
e to serve. The list |
| 1084 * is ordered by priority. The first ones must be in the manager, then the r
emaining ones should |
| 1085 * have at least approximations if possible. |
| 1086 * |
| 1087 * @param sortedPriorityArray The array of all the {@link StackTab} sorted b
y priority. |
| 1088 */ |
| 1089 private void updateTabsVisibility(StackTab[] sortedPriorityArray) { |
| 1090 mVisibilityArray.clear(); |
| 1091 for (int i = 0; i < sortedPriorityArray.length; i++) { |
| 1092 mVisibilityArray.add(sortedPriorityArray[i].getId()); |
| 1093 } |
| 1094 updateCacheVisibleIds(mVisibilityArray); |
| 1095 } |
| 1096 |
| 1097 /** |
| 1098 * Initializes the {@link LayoutTab} a few at a time. This function is to be
called once a |
| 1099 * frame. |
| 1100 * The logic of that function is not as trivial as it should be because the
input array we want |
| 1101 * to initialize the tab from keeps getting reordered from calls to call. Th
is is needed to |
| 1102 * get the highest priority tab initialized first. |
| 1103 * |
| 1104 * @param sortedPriorityArray The array of all the {@link StackTab} sorted b
y priority. |
| 1105 */ |
| 1106 private void updateDelayedLayoutTabInit(StackTab[] sortedPriorityArray) { |
| 1107 if (!mDelayedLayoutTabInitRequired) return; |
| 1108 |
| 1109 int initialized = 0; |
| 1110 final int count = sortedPriorityArray.length; |
| 1111 for (int i = 0; i < count; i++) { |
| 1112 if (initialized >= LAYOUTTAB_ASYNCHRONOUS_INITIALIZATION_BATCH_SIZE)
return; |
| 1113 |
| 1114 LayoutTab layoutTab = sortedPriorityArray[i].getLayoutTab(); |
| 1115 // The actual initialization is done by the parent class. |
| 1116 if (super.initLayoutTabFromHost(layoutTab)) { |
| 1117 initialized++; |
| 1118 } |
| 1119 } |
| 1120 if (initialized == 0) mDelayedLayoutTabInitRequired = false; |
| 1121 } |
| 1122 |
| 1123 @Override |
| 1124 protected boolean initLayoutTabFromHost(LayoutTab layoutTab) { |
| 1125 if (layoutTab.isInitFromHostNeeded()) mDelayedLayoutTabInitRequired = tr
ue; |
| 1126 return false; |
| 1127 } |
| 1128 |
| 1129 /** |
| 1130 * Sets properties for animations. |
| 1131 * @param prop The property to update |
| 1132 * @param p New value of the property |
| 1133 */ |
| 1134 @Override |
| 1135 public void setProperty(Property prop, float p) { |
| 1136 switch (prop) { |
| 1137 case STACK_SNAP: |
| 1138 mRenderedScrollOffset = p; |
| 1139 mScrollIndexOffset = p; |
| 1140 break; |
| 1141 case INNER_MARGIN_PERCENT: |
| 1142 mInnerMarginPercent = p; |
| 1143 break; |
| 1144 case STACK_OFFSET_Y_PERCENT: |
| 1145 mStackOffsetYPercent = p; |
| 1146 break; |
| 1147 } |
| 1148 } |
| 1149 |
| 1150 /** |
| 1151 * Called by the stacks whenever they start an animation. |
| 1152 */ |
| 1153 public void onStackAnimationStarted() { |
| 1154 if (mStackAnimationCount == 0) super.onAnimationStarted(); |
| 1155 mStackAnimationCount++; |
| 1156 } |
| 1157 |
| 1158 /** |
| 1159 * Called by the stacks whenever they finish their animations. |
| 1160 */ |
| 1161 public void onStackAnimationFinished() { |
| 1162 mStackAnimationCount--; |
| 1163 if (mStackAnimationCount == 0) super.onAnimationFinished(); |
| 1164 } |
| 1165 |
| 1166 @Override |
| 1167 protected SceneLayer getSceneLayer() { |
| 1168 return mSceneLayer; |
| 1169 } |
| 1170 |
| 1171 @Override |
| 1172 protected void updateSceneLayer(Rect viewport, Rect contentViewport, |
| 1173 LayerTitleCache layerTitleCache, TabContentManager tabContentManager
, |
| 1174 ResourceManager resourceManager, ChromeFullscreenManager fullscreenM
anager) { |
| 1175 super.updateSceneLayer(viewport, contentViewport, layerTitleCache, tabCo
ntentManager, |
| 1176 resourceManager, fullscreenManager); |
| 1177 assert mSceneLayer != null; |
| 1178 mSceneLayer.pushLayers(getContext(), viewport, contentViewport, this, la
yerTitleCache, |
| 1179 tabContentManager, resourceManager); |
| 1180 } |
| 1181 } |
OLD | NEW |