Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(163)

Side by Side Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698