// Copyright 2015 The Chromium Authors
// 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.phone;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.RectF;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.compositor.layouts.Layout;
import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab;
import org.chromium.chrome.browser.compositor.layouts.eventfilter.BlackHoleEventFilter;
import org.chromium.chrome.browser.compositor.scene_layer.TabListSceneLayer;
import org.chromium.chrome.browser.layouts.EventFilter;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.layouts.animation.CompositorAnimationHandler;
import org.chromium.chrome.browser.layouts.animation.CompositorAnimator;
import org.chromium.chrome.browser.layouts.scene_layer.SceneLayer;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.chrome.browser.tab_ui.TabContentManager;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.ui.interpolators.Interpolators;
import org.chromium.ui.resources.ResourceManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
/** This class handles animating the opening of new tabs. */
public class SimpleAnimationLayout extends Layout {
/** The animation for a tab being created in the foreground. */
private AnimatorSet mTabCreatedForegroundAnimation;
/** The animation for a tab being created in the background. */
private AnimatorSet mTabCreatedBackgroundAnimation;
/** Fraction to scale tabs by during animation. */
public static final float SCALE_FRACTION = 0.90f;
/** Duration of the first step of the background animation: zooming out, rotating in */
private static final long BACKGROUND_STEP1_DURATION = 300;
/** Duration of the second step of the background animation: pause */
private static final long BACKGROUND_STEP2_DURATION = 150;
/** Duration of the third step of the background animation: zooming in, sliding out */
private static final long BACKGROUND_STEP3_DURATION = 300;
/** Percentage of the screen covered by the new tab */
private static final float BACKGROUND_COVER_PCTG = 0.5f;
/** The time duration of the animation */
protected static final int FOREGROUND_ANIMATION_DURATION = 300;
private final TabListSceneLayer mSceneLayer;
private final BlackHoleEventFilter mBlackHoleEventFilter;
// The tab to select on finishing the animation.
private int mNextTabId;
/**
* Creates an instance of the {@link SimpleAnimationLayout}.
* @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.
*/
public SimpleAnimationLayout(
Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost) {
super(context, updateHost, renderHost);
mBlackHoleEventFilter = new BlackHoleEventFilter(context);
mSceneLayer = new TabListSceneLayer();
}
@Override
public @ViewportMode int getViewportMode() {
return ViewportMode.USE_PREVIOUS_BROWSER_CONTROLS_STATE;
}
@Override
public void doneHiding() {
TabModelUtils.selectTabById(mTabModelSelector, mNextTabId, TabSelectionType.FROM_USER);
super.doneHiding();
}
@Override
public void show(long time, boolean animate) {
super.show(time, animate);
mNextTabId = Tab.INVALID_TAB_ID;
if (mTabModelSelector != null && mTabContentManager != null) {
Tab tab = mTabModelSelector.getCurrentTab();
if (tab != null && tab.isNativePage()) mTabContentManager.cacheTabThumbnail(tab);
}
reset();
}
@Override
public boolean handlesTabCreating() {
return true;
}
@Override
public boolean handlesTabClosing() {
return false;
}
@Override
protected void updateLayout(long time, long dt) {
super.updateLayout(time, dt);
if (mLayoutTabs == null) return;
boolean needUpdate = false;
for (int i = mLayoutTabs.length - 1; i >= 0; i--) {
needUpdate = updateSnap(dt, mLayoutTabs[i]) || needUpdate;
}
if (needUpdate) requestUpdate();
}
@Override
public void onTabCreating(int sourceTabId) {
super.onTabCreating(sourceTabId);
reset();
// Make sure any currently running animations can't influence tab if we are reusing it.
forceAnimationToFinish();
ensureSourceTabCreated(sourceTabId);
}
private void ensureSourceTabCreated(int sourceTabId) {
if (mLayoutTabs != null
&& mLayoutTabs.length == 1
&& mLayoutTabs[0].getId() == sourceTabId) {
return;
}
// Just draw the source tab on the screen.
TabModel sourceModel = mTabModelSelector.getModelForTabId(sourceTabId);
if (sourceModel == null) return;
LayoutTab sourceLayoutTab = createLayoutTab(sourceTabId, sourceModel.isIncognito());
sourceLayoutTab.setBorderAlpha(0.0f);
mLayoutTabs = new LayoutTab[] {sourceLayoutTab};
updateCacheVisibleIds(new LinkedList<Integer>(Arrays.asList(sourceTabId)));
}
@Override
public void onTabCreated(
long time,
int id,
int index,
int sourceId,
boolean newIsIncognito,
boolean background,
float originX,
float originY) {
super.onTabCreated(time, id, index, sourceId, newIsIncognito, background, originX, originY);
ensureSourceTabCreated(sourceId);
if (background && mLayoutTabs != null && mLayoutTabs.length > 0) {
tabCreatedInBackground(id, sourceId, newIsIncognito, originX, originY);
} else {
tabCreatedInForeground(id, sourceId, newIsIncognito, originX, originY);
}
}
/**
* Animate opening a tab in the foreground.
*
* @param id The id of the new tab to animate.
* @param sourceId The id of the tab that spawned this new tab.
* @param newIsIncognito true if the new tab is an incognito tab.
* @param originX The X coordinate of the last touch down event that spawned this tab.
* @param originY The Y coordinate of the last touch down event that spawned this tab.
*/
private void tabCreatedInForeground(
int id, int sourceId, boolean newIsIncognito, float originX, float originY) {
LayoutTab newLayoutTab = createLayoutTab(id, newIsIncognito);
if (mLayoutTabs == null || mLayoutTabs.length == 0) {
mLayoutTabs = new LayoutTab[] {newLayoutTab};
} else {
mLayoutTabs = new LayoutTab[] {mLayoutTabs[0], newLayoutTab};
}
updateCacheVisibleIds(new LinkedList<Integer>(Arrays.asList(id, sourceId)));
newLayoutTab.setBorderAlpha(0.0f);
newLayoutTab.setStaticToViewBlend(1.f);
forceAnimationToFinish();
CompositorAnimationHandler handler = getAnimationHandler();
CompositorAnimator scaleAnimation =
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
newLayoutTab,
LayoutTab.SCALE,
0f,
1f,
FOREGROUND_ANIMATION_DURATION);
CompositorAnimator alphaAnimation =
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
newLayoutTab,
LayoutTab.ALPHA,
0f,
1f,
FOREGROUND_ANIMATION_DURATION);
CompositorAnimator xAnimation =
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
newLayoutTab,
LayoutTab.X,
originX,
0f,
FOREGROUND_ANIMATION_DURATION);
CompositorAnimator yAnimation =
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
newLayoutTab,
LayoutTab.Y,
originY,
0f,
FOREGROUND_ANIMATION_DURATION);
mTabCreatedForegroundAnimation = new AnimatorSet();
mTabCreatedForegroundAnimation.setInterpolator(Interpolators.STANDARD_INTERPOLATOR);
mTabCreatedForegroundAnimation.playTogether(
scaleAnimation, alphaAnimation, xAnimation, yAnimation);
mTabCreatedForegroundAnimation.start();
mTabModelSelector.selectModel(newIsIncognito);
mNextTabId = id;
startHiding();
}
/**
* Animate opening a tab in the background.
*
* @param id The id of the new tab to animate.
* @param sourceId The id of the tab that spawned this new tab.
* @param newIsIncognito true if the new tab is an incognito tab.
* @param originX The X screen coordinate in dp of the last touch down event that spawned
* this tab.
* @param originY The Y screen coordinate in dp of the last touch down event that spawned
* this tab.
*/
private void tabCreatedInBackground(
int id, int sourceId, boolean newIsIncognito, float originX, float originY) {
LayoutTab newLayoutTab = createLayoutTab(id, newIsIncognito);
// mLayoutTabs should already have the source tab from tabCreating().
assert mLayoutTabs.length == 1;
LayoutTab sourceLayoutTab = mLayoutTabs[0];
mLayoutTabs = new LayoutTab[] {sourceLayoutTab, newLayoutTab};
updateCacheVisibleIds(new LinkedList<Integer>(Arrays.asList(id, sourceId)));
forceAnimationToFinish();
newLayoutTab.setBorderAlpha(0.0f);
final float scale = SCALE_FRACTION;
final float margin = Math.min(getWidth(), getHeight()) * (1.0f - scale) / 2.0f;
CompositorAnimationHandler handler = getAnimationHandler();
Collection<Animator> animationList = new ArrayList<>(5);
// Step 1: zoom out the source tab and bring in the new tab
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
sourceLayoutTab,
LayoutTab.SCALE,
1f,
scale,
BACKGROUND_STEP1_DURATION));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
sourceLayoutTab,
LayoutTab.X,
0f,
margin,
BACKGROUND_STEP1_DURATION));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
sourceLayoutTab,
LayoutTab.Y,
0f,
margin,
BACKGROUND_STEP1_DURATION));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
sourceLayoutTab,
LayoutTab.BORDER_SCALE,
1f / scale,
1f,
BACKGROUND_STEP1_DURATION));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
sourceLayoutTab,
LayoutTab.BORDER_ALPHA,
0f,
1f,
BACKGROUND_STEP1_DURATION));
AnimatorSet step1Source = new AnimatorSet();
step1Source.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
step1Source.playTogether(animationList);
float pauseX = margin;
float pauseY = margin;
if (getOrientation() == Orientation.PORTRAIT) {
pauseY = BACKGROUND_COVER_PCTG * getHeight();
} else {
pauseX = BACKGROUND_COVER_PCTG * getWidth();
}
animationList = new ArrayList<>(4);
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
newLayoutTab,
LayoutTab.ALPHA,
0f,
1f,
BACKGROUND_STEP1_DURATION / 2));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
newLayoutTab,
LayoutTab.SCALE,
0f,
scale,
BACKGROUND_STEP1_DURATION));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
newLayoutTab,
LayoutTab.X,
originX,
pauseX,
BACKGROUND_STEP1_DURATION));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
newLayoutTab,
LayoutTab.Y,
originY,
pauseY,
BACKGROUND_STEP1_DURATION));
AnimatorSet step1New = new AnimatorSet();
step1New.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
step1New.playTogether(animationList);
AnimatorSet step1 = new AnimatorSet();
step1.playTogether(step1New, step1Source);
// step 2: pause and admire the nice tabs
// step 3: zoom in the source tab and slide down the new tab
animationList = new ArrayList<>(7);
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
sourceLayoutTab,
LayoutTab.SCALE,
scale,
1f,
BACKGROUND_STEP3_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
sourceLayoutTab,
LayoutTab.X,
margin,
0f,
BACKGROUND_STEP3_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
sourceLayoutTab,
LayoutTab.Y,
margin,
0f,
BACKGROUND_STEP3_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
sourceLayoutTab,
LayoutTab.BORDER_SCALE,
1f,
1f / scale,
BACKGROUND_STEP3_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
sourceLayoutTab,
LayoutTab.BORDER_ALPHA,
1f,
0f,
BACKGROUND_STEP3_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler, newLayoutTab, LayoutTab.ALPHA, 1f, 0f, BACKGROUND_STEP3_DURATION));
if (getOrientation() == Orientation.PORTRAIT) {
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
newLayoutTab,
LayoutTab.Y,
pauseY,
getHeight(),
BACKGROUND_STEP3_DURATION,
Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR));
} else {
animationList.add(
CompositorAnimator.ofWritableFloatPropertyKey(
handler,
newLayoutTab,
LayoutTab.X,
pauseX,
getWidth(),
BACKGROUND_STEP3_DURATION,
Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR));
}
AnimatorSet step3 = new AnimatorSet();
step3.setStartDelay(BACKGROUND_STEP2_DURATION);
step3.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Once the animation has finished, we can switch layouts.
mNextTabId = sourceId;
startHiding();
}
});
step3.playTogether(animationList);
mTabCreatedBackgroundAnimation = new AnimatorSet();
mTabCreatedBackgroundAnimation.playSequentially(step1, step3);
mTabCreatedBackgroundAnimation.start();
mTabModelSelector.selectModel(newIsIncognito);
}
@Override
protected void forceAnimationToFinish() {
super.forceAnimationToFinish();
if (mTabCreatedForegroundAnimation != null) mTabCreatedForegroundAnimation.end();
if (mTabCreatedBackgroundAnimation != null) mTabCreatedBackgroundAnimation.end();
}
/** Resets the internal state. */
private void reset() {
mLayoutTabs = null;
}
@Override
protected EventFilter getEventFilter() {
return mBlackHoleEventFilter;
}
@Override
protected SceneLayer getSceneLayer() {
return mSceneLayer;
}
@Override
protected void updateSceneLayer(
RectF viewport,
RectF contentViewport,
TabContentManager tabContentManager,
ResourceManager resourceManager,
BrowserControlsStateProvider browserControls) {
super.updateSceneLayer(
viewport, contentViewport, tabContentManager, resourceManager, browserControls);
assert mSceneLayer != null;
// The content viewport is intentionally sent as both params below.
mSceneLayer.pushLayers(
getContext(),
contentViewport,
contentViewport,
this,
tabContentManager,
resourceManager,
browserControls,
SceneLayer.INVALID_RESOURCE_ID,
0,
0);
}
@Override
public int getLayoutType() {
return LayoutType.SIMPLE_ANIMATION;
}
}