| Index: chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java
|
| diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..91508d4a0646e1d2609b293b58647de1cf4b7045
|
| --- /dev/null
|
| +++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java
|
| @@ -0,0 +1,359 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.chrome.browser.compositor.layouts;
|
| +
|
| +import android.content.Context;
|
| +import android.content.res.Resources;
|
| +import android.graphics.Rect;
|
| +import android.view.animation.DecelerateInterpolator;
|
| +import android.view.animation.Interpolator;
|
| +
|
| +import com.google.android.apps.chrome.R;
|
| +
|
| +import org.chromium.base.metrics.RecordUserAction;
|
| +import org.chromium.chrome.browser.Tab;
|
| +import org.chromium.chrome.browser.compositor.LayerTitleCache;
|
| +import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animatable;
|
| +import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab;
|
| +import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
|
| +import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeEventFilter.ScrollDirection;
|
| +import org.chromium.chrome.browser.compositor.layouts.eventfilter.EventFilter;
|
| +import org.chromium.chrome.browser.compositor.scene_layer.SceneLayer;
|
| +import org.chromium.chrome.browser.compositor.scene_layer.TabListSceneLayer;
|
| +import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
|
| +import org.chromium.chrome.browser.tabmodel.TabModel;
|
| +import org.chromium.chrome.browser.util.MathUtils;
|
| +import org.chromium.ui.base.LocalizationUtils;
|
| +import org.chromium.ui.resources.ResourceManager;
|
| +
|
| +import java.util.ArrayList;
|
| +import java.util.List;
|
| +
|
| +/**
|
| + * Layout defining the animation and positioning of the tabs during the edge swipe effect.
|
| + */
|
| +public class ToolbarSwipeLayout extends Layout implements Animatable<ToolbarSwipeLayout.Property> {
|
| + /**
|
| + * Animation properties
|
| + */
|
| + public enum Property {
|
| + OFFSET,
|
| + }
|
| +
|
| + private static final boolean ANONYMIZE_NON_FOCUSED_TAB = true;
|
| +
|
| + // Unit is millisecond / screen.
|
| + private static final float ANIMATION_SPEED_SCREEN = 500.0f;
|
| +
|
| + // This is the time step used to move the offset based on fling
|
| + private static final float FLING_TIME_STEP = 1.0f / 30.0f;
|
| +
|
| + // This is the max contribution from fling in screen size percentage.
|
| + private static final float FLING_MAX_CONTRIBUTION = 0.5f;
|
| +
|
| + private LayoutTab mLeftTab;
|
| + private LayoutTab mRightTab;
|
| + private LayoutTab mFromTab; // Set to either mLeftTab or mRightTab.
|
| + private LayoutTab mToTab; // Set to mLeftTab or mRightTab or null if it is not determined.
|
| +
|
| + // Whether or not to show the toolbar.
|
| + private boolean mMoveToolbar;
|
| +
|
| + // Offsets are in pixels [0, width].
|
| + private float mOffsetStart;
|
| + private float mOffset;
|
| + private float mOffsetTarget;
|
| +
|
| + // These will be set from dimens.xml
|
| + private final float mSpaceBetweenTabs;
|
| + private final float mCommitDistanceFromEdge;
|
| +
|
| + private final TabListSceneLayer mSceneLayer;
|
| +
|
| + private final Interpolator mEdgeInterpolator = new DecelerateInterpolator();
|
| +
|
| + /**
|
| + * @param context The current Android's context.
|
| + * @param updateHost The {@link LayoutUpdateHost} view for this layout.
|
| + * @param renderHost The {@link LayoutRenderHost} view for this layout.
|
| + * @param eventFilter The {@link EventFilter} that is needed for this view.
|
| + */
|
| + public ToolbarSwipeLayout(Context context, LayoutUpdateHost updateHost,
|
| + LayoutRenderHost renderHost, EventFilter eventFilter) {
|
| + super(context, updateHost, renderHost, eventFilter);
|
| + Resources res = context.getResources();
|
| + final float pxToDp = 1.0f / res.getDisplayMetrics().density;
|
| + mCommitDistanceFromEdge = res.getDimension(R.dimen.toolbar_swipe_commit_distance) * pxToDp;
|
| + mSpaceBetweenTabs = res.getDimension(R.dimen.toolbar_swipe_space_between_tabs) * pxToDp;
|
| + mSceneLayer = new TabListSceneLayer();
|
| + }
|
| +
|
| + /**
|
| + * @param moveToolbar Whether or not swiping this layout should also move the toolbar as well as
|
| + * the content.
|
| + */
|
| + public void setMovesToolbar(boolean moveToolbar) {
|
| + mMoveToolbar = moveToolbar;
|
| + }
|
| +
|
| + @Override
|
| + public int getSizingFlags() {
|
| + return mMoveToolbar ? SizingFlags.HELPER_HIDE_TOOLBAR_IMMEDIATE
|
| + : SizingFlags.HELPER_NO_FULLSCREEN_SUPPORT;
|
| + }
|
| +
|
| + @Override
|
| + public void show(long time, boolean animate) {
|
| + super.show(time, animate);
|
| + init();
|
| + if (mTabModelSelector == null) return;
|
| + Tab tab = mTabModelSelector.getCurrentTab();
|
| + if (tab != null && tab.isNativePage()) mTabContentManager.cacheTabThumbnail(tab);
|
| +
|
| + TabModel model = mTabModelSelector.getCurrentModel();
|
| + if (model == null) return;
|
| + int fromTabId = mTabModelSelector.getCurrentTabId();
|
| + if (fromTabId == TabModel.INVALID_TAB_INDEX) return;
|
| + mFromTab = createLayoutTab(fromTabId, model.isIncognito(), NO_CLOSE_BUTTON, NEED_TITLE);
|
| + prepareLayoutTabForSwipe(mFromTab, false);
|
| + }
|
| +
|
| + @Override
|
| + public void swipeStarted(long time, ScrollDirection direction, float x, float y) {
|
| + if (mTabModelSelector == null || mToTab != null || direction == ScrollDirection.DOWN) {
|
| + return;
|
| + }
|
| +
|
| + boolean dragFromLeftEdge = direction == ScrollDirection.RIGHT;
|
| + // Finish off any other animations.
|
| + forceAnimationToFinish();
|
| +
|
| + // Determine which tabs we're showing.
|
| + TabModel model = mTabModelSelector.getCurrentModel();
|
| + if (model == null) return;
|
| + int fromIndex = model.index();
|
| + if (fromIndex == TabModel.INVALID_TAB_INDEX) return;
|
| +
|
| + // On RTL, edge-dragging to the left is the next tab.
|
| + int toIndex = (LocalizationUtils.isLayoutRtl() ^ dragFromLeftEdge) ? fromIndex - 1
|
| + : fromIndex + 1;
|
| + int leftIndex = dragFromLeftEdge ? toIndex : fromIndex;
|
| + int rightIndex = !dragFromLeftEdge ? toIndex : fromIndex;
|
| +
|
| + List<Integer> visibleTabs = new ArrayList<Integer>();
|
| + if (0 <= leftIndex && leftIndex < model.getCount()) {
|
| + int leftTabId = model.getTabAt(leftIndex).getId();
|
| + mLeftTab = createLayoutTab(leftTabId, model.isIncognito(), NO_CLOSE_BUTTON, NEED_TITLE);
|
| + prepareLayoutTabForSwipe(mLeftTab, leftIndex != fromIndex);
|
| + visibleTabs.add(leftTabId);
|
| + }
|
| + if (0 <= rightIndex && rightIndex < model.getCount()) {
|
| + int rightTabId = model.getTabAt(rightIndex).getId();
|
| + mRightTab =
|
| + createLayoutTab(rightTabId, model.isIncognito(), NO_CLOSE_BUTTON, NEED_TITLE);
|
| + prepareLayoutTabForSwipe(mRightTab, rightIndex != fromIndex);
|
| + visibleTabs.add(rightTabId);
|
| + }
|
| +
|
| + updateCacheVisibleIds(visibleTabs);
|
| +
|
| + mToTab = null;
|
| +
|
| + // Reset the tab offsets.
|
| + mOffsetStart = dragFromLeftEdge ? 0 : getWidth();
|
| + mOffset = 0;
|
| + mOffsetTarget = 0;
|
| +
|
| + if (mLeftTab != null && mRightTab != null) {
|
| + mLayoutTabs = new LayoutTab[] {mLeftTab, mRightTab};
|
| + } else if (mLeftTab != null) {
|
| + mLayoutTabs = new LayoutTab[] {mLeftTab};
|
| + } else if (mRightTab != null) {
|
| + mLayoutTabs = new LayoutTab[] {mRightTab};
|
| + } else {
|
| + mLayoutTabs = null;
|
| + }
|
| +
|
| + requestUpdate();
|
| + }
|
| +
|
| + private void prepareLayoutTabForSwipe(LayoutTab layoutTab, boolean anonymizeToolbar) {
|
| + assert layoutTab != null;
|
| + if (layoutTab.shouldStall()) layoutTab.setSaturation(0.0f);
|
| + layoutTab.setScale(1.f);
|
| + layoutTab.setBorderScale(1.f);
|
| + layoutTab.setDecorationAlpha(0.f);
|
| + layoutTab.setY(0.f);
|
| + layoutTab.setShowToolbar(mMoveToolbar);
|
| + layoutTab.setAnonymizeToolbar(anonymizeToolbar && ANONYMIZE_NON_FOCUSED_TAB);
|
| + }
|
| +
|
| + @Override
|
| + public void swipeUpdated(long time, float x, float y, float dx, float dy, float tx, float ty) {
|
| + mOffsetTarget = MathUtils.clamp(mOffsetStart + tx, 0, getWidth()) - mOffsetStart;
|
| + requestUpdate();
|
| + }
|
| +
|
| + @Override
|
| + public void swipeFlingOccurred(
|
| + long time, float x, float y, float tx, float ty, float vx, float vy) {
|
| + // Use the velocity to add on final step which simulate a fling.
|
| + final float kickRangeX = getWidth() * FLING_MAX_CONTRIBUTION;
|
| + final float kickRangeY = getHeight() * FLING_MAX_CONTRIBUTION;
|
| + final float kickX = MathUtils.clamp(vx * FLING_TIME_STEP, -kickRangeX, kickRangeX);
|
| + final float kickY = MathUtils.clamp(vy * FLING_TIME_STEP, -kickRangeY, kickRangeY);
|
| + swipeUpdated(time, x, y, 0, 0, tx + kickX, ty + kickY);
|
| + }
|
| +
|
| + @Override
|
| + public void swipeFinished(long time) {
|
| + if (mFromTab == null || mTabModelSelector == null) return;
|
| +
|
| + // Figures out the tab to snap to and how to animate to it.
|
| + float commitDistance = Math.min(mCommitDistanceFromEdge, getWidth() / 3);
|
| + float offsetTo = 0;
|
| + mToTab = mFromTab;
|
| + if (mOffsetTarget > commitDistance && mLeftTab != null) {
|
| + mToTab = mLeftTab;
|
| + offsetTo += getWidth();
|
| + } else if (mOffsetTarget < -commitDistance && mRightTab != null) {
|
| + mToTab = mRightTab;
|
| + offsetTo -= getWidth();
|
| + }
|
| +
|
| + if (mToTab != mFromTab) {
|
| + RecordUserAction.record("MobileSideSwipeFinished");
|
| + }
|
| +
|
| + startHiding(mToTab.getId(), false);
|
| +
|
| + // Animate gracefully the end of the swiping effect.
|
| + forceAnimationToFinish();
|
| + float start = mOffsetTarget;
|
| + float end = offsetTo;
|
| + long duration = (long) (ANIMATION_SPEED_SCREEN * Math.abs(start - end) / getWidth());
|
| + if (duration > 0) {
|
| + addToAnimation(this, Property.OFFSET, start, end, duration, 0);
|
| + }
|
| +
|
| + requestRender();
|
| + }
|
| +
|
| + @Override
|
| + public void swipeCancelled(long time) {
|
| + swipeFinished(time);
|
| + }
|
| +
|
| + @Override
|
| + protected void updateLayout(long time, long dt) {
|
| + super.updateLayout(time, dt);
|
| +
|
| + if (mFromTab == null) return;
|
| + // In case the draw function get called before swipeStarted()
|
| + if (mLeftTab == null && mRightTab == null) mRightTab = mFromTab;
|
| +
|
| + mOffset = smoothInput(mOffset, mOffsetTarget);
|
| + boolean needUpdate = Math.abs(mOffset - mOffsetTarget) >= 0.1f;
|
| +
|
| + float rightX = 0.0f;
|
| + float leftX = 0.0f;
|
| +
|
| + final boolean doEdge = mLeftTab != null ^ mRightTab != null;
|
| +
|
| + if (doEdge) {
|
| + float progress = mOffset / getWidth();
|
| + float direction = Math.signum(progress);
|
| + float smoothedProgress = mEdgeInterpolator.getInterpolation(Math.abs(progress));
|
| +
|
| + float maxSlide = getWidth() / 5.f;
|
| + rightX = direction * smoothedProgress * maxSlide;
|
| + leftX = rightX;
|
| + } else {
|
| + float progress = mOffset / getWidth();
|
| + progress += mOffsetStart == 0.0f ? 0.0f : 1.0f;
|
| + progress = MathUtils.clamp(progress, 0.0f, 1.0f);
|
| +
|
| + assert mLeftTab != null;
|
| + assert mRightTab != null;
|
| + rightX = MathUtils.interpolate(0.0f, getWidth() + mSpaceBetweenTabs, progress);
|
| + // The left tab must be aligned on the right if the image is smaller than the screen.
|
| + leftX = rightX - mSpaceBetweenTabs
|
| + - Math.min(getWidth(), mLeftTab.getOriginalContentWidth());
|
| + // Compute final x post scale and ensure the tab's center point never passes the
|
| + // center point of the screen.
|
| + float screenCenterX = getWidth() / 2;
|
| + rightX = Math.max(screenCenterX - mRightTab.getFinalContentWidth() / 2, rightX);
|
| + leftX = Math.min(screenCenterX - mLeftTab.getFinalContentWidth() / 2, leftX);
|
| + }
|
| +
|
| + if (mLeftTab != null) {
|
| + mLeftTab.setX(leftX);
|
| + needUpdate = mLeftTab.updateSnap(dt) || needUpdate;
|
| + }
|
| +
|
| + if (mRightTab != null) {
|
| + mRightTab.setX(rightX);
|
| + needUpdate = mRightTab.updateSnap(dt) || needUpdate;
|
| + }
|
| + if (needUpdate) requestUpdate();
|
| + }
|
| +
|
| + /**
|
| + * Smoothes input signal. The definition of the input is lower than the
|
| + * pixel density of the screen so we need to smooth the input to give the illusion of smooth
|
| + * animation on screen from chunky inputs.
|
| + * The combination of 30 pixels and 0.8f ensures that the output is not more than 6 pixels away
|
| + * from the target.
|
| + * TODO(dtrainor): This has nothing to do with time, just draw rate.
|
| + * Is this okay or do we want to have the interpolation based on the time elapsed?
|
| + * @param current The current value of the signal.
|
| + * @param input The raw input value.
|
| + * @return The smoothed signal.
|
| + */
|
| + private float smoothInput(float current, float input) {
|
| + current = MathUtils.clamp(current, input - 30, input + 30);
|
| + return MathUtils.interpolate(current, input, 0.8f);
|
| + }
|
| +
|
| + private void init() {
|
| + mLayoutTabs = null;
|
| + mFromTab = null;
|
| + mLeftTab = null;
|
| + mRightTab = null;
|
| + mToTab = null;
|
| + mOffsetStart = 0;
|
| + mOffset = 0;
|
| + mOffsetTarget = 0;
|
| + }
|
| +
|
| + /**
|
| + * Sets a property for an animation.
|
| + * @param prop The property to update
|
| + * @param value New value of the property
|
| + */
|
| + @Override
|
| + public void setProperty(Property prop, float value) {
|
| + if (prop == Property.OFFSET) {
|
| + mOffset = value;
|
| + mOffsetTarget = mOffset;
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + protected SceneLayer getSceneLayer() {
|
| + return mSceneLayer;
|
| + }
|
| +
|
| + @Override
|
| + protected void updateSceneLayer(Rect viewport, Rect contentViewport,
|
| + LayerTitleCache layerTitleCache, TabContentManager tabContentManager,
|
| + ResourceManager resourceManager, ChromeFullscreenManager fullscreenManager) {
|
| + super.updateSceneLayer(viewport, contentViewport, layerTitleCache, tabContentManager,
|
| + resourceManager, fullscreenManager);
|
| + assert mSceneLayer != null;
|
| + mSceneLayer.pushLayers(getContext(), viewport, contentViewport, this, layerTitleCache,
|
| + tabContentManager, resourceManager);
|
| + }
|
| +}
|
|
|