// 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.overlays.strip;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.LayerTitleCache;
import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
import org.chromium.chrome.browser.compositor.layouts.components.CompositorButton;
import org.chromium.chrome.browser.compositor.layouts.components.VirtualView;
import org.chromium.chrome.browser.compositor.layouts.eventfilter.AreaGestureEventFilter;
import org.chromium.chrome.browser.compositor.layouts.eventfilter.EventFilter;
import org.chromium.chrome.browser.compositor.overlays.SceneOverlay;
import org.chromium.chrome.browser.compositor.scene_layer.SceneOverlayLayer;
import org.chromium.chrome.browser.compositor.scene_layer.TabStripSceneLayer;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.ui.base.LocalizationUtils;
import org.chromium.ui.resources.ResourceManager;
import java.util.List;
/**
* This class handles managing which {@link StripLayoutHelper} is currently active and dispatches
* all input and model events to the proper destination.
*/
public class StripLayoutHelperManager implements SceneOverlay {
// Caching Variables
private final RectF mStripFilterArea = new RectF();
// 1px border colors
private static final float BORDER_OPACITY = 0.2f;
private static final float BORDER_OPACITY_INCOGNITO = 0.4f;
// Model selector buttons constants.
private static final float MODEL_SELECTOR_BUTTON_Y_OFFSET_DP = 10.f;
private static final float MODEL_SELECTOR_BUTTON_END_PADDING_DP = 6.f;
private static final float MODEL_SELECTOR_BUTTON_START_PADDING_DP = 3.f;
private static final float MODEL_SELECTOR_BUTTON_WIDTH_DP = 24.f;
private static final float MODEL_SELECTOR_BUTTON_HEIGHT_DP = 24.f;
// External influences
private TabModelSelector mTabModelSelector;
private final LayoutUpdateHost mUpdateHost;
// Event Filters
private final AreaGestureEventFilter mEventFilter;
// Internal state
private boolean mIsIncognito;
private final StripLayoutHelper mNormalHelper;
private final StripLayoutHelper mIncognitoHelper;
// UI State
private float mWidth; // in dp units
private final float mHeight; // in dp units
private int mOrientation;
private final CompositorButton mModelSelectorButton;
private TabStripSceneLayer mTabStripTreeProvider;
/**
* Creates an instance of the {@link StripLayoutHelperManager}.
* @param context The current Android {@link Context}.
* @param updateHost The parent {@link LayoutUpdateHost}.
* @param renderHost The {@link LayoutRenderHost}.
*/
public StripLayoutHelperManager(Context context, LayoutUpdateHost updateHost,
LayoutRenderHost renderHost, AreaGestureEventFilter eventFilter) {
mUpdateHost = updateHost;
mTabStripTreeProvider = new TabStripSceneLayer(context);
mEventFilter = eventFilter;
mNormalHelper = new StripLayoutHelper(context, updateHost, renderHost, false);
mIncognitoHelper = new StripLayoutHelper(context, updateHost, renderHost, true);
mModelSelectorButton = new CompositorButton(
context, MODEL_SELECTOR_BUTTON_WIDTH_DP, MODEL_SELECTOR_BUTTON_HEIGHT_DP);
mModelSelectorButton.setIncognito(false);
mModelSelectorButton.setVisible(false);
// Pressed resources are the same as the unpressed resources.
mModelSelectorButton.setResources(R.drawable.btn_tabstrip_switch_normal,
R.drawable.btn_tabstrip_switch_normal, R.drawable.btn_tabstrip_switch_incognito,
R.drawable.btn_tabstrip_switch_incognito);
mModelSelectorButton.setY(MODEL_SELECTOR_BUTTON_Y_OFFSET_DP);
Resources res = context.getResources();
mHeight = res.getDimension(R.dimen.tab_strip_height) / res.getDisplayMetrics().density;
mModelSelectorButton.setAccessibilityDescription(
res.getString(R.string.accessibility_tabstrip_btn_incognito_toggle_standard),
res.getString(R.string.accessibility_tabstrip_btn_incognito_toggle_incognito));
onContextChanged(context);
}
/**
* Cleans up internal state.
*/
public void destroy() {
mTabStripTreeProvider.destroy();
mTabStripTreeProvider = null;
mIncognitoHelper.destroy();
mNormalHelper.destroy();
}
@Override
public SceneOverlayLayer getUpdatedSceneOverlayTree(LayerTitleCache layerTitleCache,
ResourceManager resourceManager, float yOffset) {
assert mTabStripTreeProvider != null;
Tab selectedTab = mTabModelSelector.getCurrentModel().getTabAt(
mTabModelSelector.getCurrentModel().index());
int selectedTabId = selectedTab == null ? TabModel.INVALID_TAB_INDEX : selectedTab.getId();
mTabStripTreeProvider.pushAndUpdateStrip(this, layerTitleCache, resourceManager,
getActiveStripLayoutHelper().getStripLayoutTabsToRender(), yOffset,
selectedTabId);
return mTabStripTreeProvider;
}
@Override
public boolean isSceneOverlayTreeShowing() {
// TODO(mdjones): This matches existing behavior but can be improved to return false if
// the top controls offset is equal to the top controls height.
return true;
}
@Override
public EventFilter getEventFilter() {
return mEventFilter;
}
@Override
public void onSizeChanged(
float width, float height, float visibleViewportOffsetY, int orientation) {
mWidth = width;
mOrientation = orientation;
if (!LocalizationUtils.isLayoutRtl()) {
mModelSelectorButton.setX(
mWidth - MODEL_SELECTOR_BUTTON_WIDTH_DP - MODEL_SELECTOR_BUTTON_END_PADDING_DP);
} else {
mModelSelectorButton.setX(MODEL_SELECTOR_BUTTON_END_PADDING_DP);
}
mNormalHelper.onSizeChanged(mWidth, mHeight);
mIncognitoHelper.onSizeChanged(mWidth, mHeight);
mStripFilterArea.set(0, 0, mWidth, Math.min(getHeight(), visibleViewportOffsetY));
mEventFilter.setEventArea(mStripFilterArea);
}
public CompositorButton getNewTabButton() {
return getActiveStripLayoutHelper().getNewTabButton();
}
public CompositorButton getModelSelectorButton() {
return mModelSelectorButton;
}
@Override
public void getVirtualViews(List<VirtualView> views) {
if (mModelSelectorButton.isVisible()) views.add(mModelSelectorButton);
getActiveStripLayoutHelper().getVirtualViews(views);
}
@Override
public boolean shouldHideAndroidTopControls() {
return false;
}
/**
* @return The opacity to use for the fade on the left side of the tab strip.
*/
public float getLeftFadeOpacity() {
return getActiveStripLayoutHelper().getLeftFadeOpacity();
}
/**
* @return The opacity to use for the fade on the right side of the tab strip.
*/
public float getRightFadeOpacity() {
return getActiveStripLayoutHelper().getRightFadeOpacity();
}
/**
* @return The brightness of background tabs in the tabstrip.
*/
public float getBackgroundTabBrightness() {
return getActiveStripLayoutHelper().getBackgroundTabBrightness();
}
/**
* @return The brightness of the entire tabstrip.
*/
public float getBrightness() {
return getActiveStripLayoutHelper().getBrightness();
}
/**
* Sets the {@link TabModelSelector} that this {@link StripLayoutHelperManager} will visually
* represent, and various objects associated with it.
* @param modelSelector The {@link TabModelSelector} to visually represent.
* @param tabCreatorManager The {@link TabCreatorManager}, used to create new tabs.
*/
public void setTabModelSelector(TabModelSelector modelSelector,
TabCreatorManager tabCreatorManager) {
if (mTabModelSelector == modelSelector) return;
mTabModelSelector = modelSelector;
mNormalHelper.setTabModel(mTabModelSelector.getModel(false),
tabCreatorManager.getTabCreator(false));
mIncognitoHelper.setTabModel(mTabModelSelector.getModel(true),
tabCreatorManager.getTabCreator(true));
tabModelSwitched(mTabModelSelector.isIncognitoSelected());
}
@Override
public void tabTitleChanged(int tabId, String title) {
getActiveStripLayoutHelper().tabTitleChanged(tabId, title);
}
public float getHeight() {
return mHeight;
}
public float getWidth() {
return mWidth;
}
public int getOrientation() {
return mOrientation;
}
public float getBorderOpacity() {
return mIsIncognito ? BORDER_OPACITY_INCOGNITO : BORDER_OPACITY;
}
/**
* Updates all internal resources and dimensions.
* @param context The current Android {@link Context}.
*/
public void onContextChanged(Context context) {
mNormalHelper.onContextChanged(context);
mIncognitoHelper.onContextChanged(context);
}
@Override
public boolean updateOverlay(long time, long dt) {
getInactiveStripLayoutHelper().finishAnimation();
return getActiveStripLayoutHelper().updateLayout(time, dt);
}
@Override
public boolean onBackPressed() {
return false;
}
@Override
public void onHideLayout() {}
@Override
public boolean handlesTabCreating() {
return false;
}
@Override
public void tabStateInitialized() {
updateModelSwitcherButton();
}
@Override
public void tabModelSwitched(boolean incognito) {
if (incognito == mIsIncognito) return;
mIsIncognito = incognito;
if (mIsIncognito) {
mIncognitoHelper.tabModelSelected();
} else {
mNormalHelper.tabModelSelected();
}
updateModelSwitcherButton();
mUpdateHost.requestUpdate();
}
private void updateModelSwitcherButton() {
mModelSelectorButton.setIncognito(mIsIncognito);
if (mTabModelSelector != null) {
boolean isVisible = mTabModelSelector.getModel(true).getCount() != 0;
mModelSelectorButton.setVisible(isVisible);
float endMargin = isVisible
? MODEL_SELECTOR_BUTTON_WIDTH_DP + MODEL_SELECTOR_BUTTON_END_PADDING_DP
+ MODEL_SELECTOR_BUTTON_START_PADDING_DP
: 0.0f;
mNormalHelper.setEndMargin(endMargin);
mIncognitoHelper.setEndMargin(endMargin);
}
}
@Override
public void tabSelected(long time, boolean incognito, int id, int prevId) {
getStripLayoutHelper(incognito).tabSelected(time, id, prevId);
}
@Override
public void tabMoved(long time, boolean incognito, int id, int oldIndex, int newIndex) {
getStripLayoutHelper(incognito).tabMoved(time, id, oldIndex, newIndex);
}
@Override
public void tabClosed(long time, boolean incognito, int id) {
getStripLayoutHelper(incognito).tabClosed(time, id);
updateModelSwitcherButton();
}
@Override
public void tabClosureCancelled(long time, boolean incognito, int id) {
getStripLayoutHelper(incognito).tabClosureCancelled(time, id);
updateModelSwitcherButton();
}
@Override
public void tabCreated(long time, boolean incognito, int id, int prevId, boolean selected) {
getStripLayoutHelper(incognito).tabCreated(time, id, prevId, selected);
}
@Override
public void tabPageLoadStarted(int id, boolean incognito) {
getStripLayoutHelper(incognito).tabPageLoadStarted(id);
}
@Override
public void tabPageLoadFinished(int id, boolean incognito) {
getStripLayoutHelper(incognito).tabPageLoadFinished(id);
}
@Override
public void tabLoadStarted(int id, boolean incognito) {
getStripLayoutHelper(incognito).tabLoadStarted(id);
}
@Override
public void tabLoadFinished(int id, boolean incognito) {
getStripLayoutHelper(incognito).tabLoadFinished(id);
}
/**
* Called on touch drag event.
* @param time The current time of the app in ms.
* @param x The y coordinate of the end of the drag event.
* @param y The y coordinate of the end of the drag event.
* @param deltaX The number of pixels dragged in the x direction.
* @param deltaY The number of pixels dragged in the y direction.
* @param totalX The total delta x since the drag started.
* @param totalY The total delta y since the drag started.
*/
public void drag(
long time, float x, float y, float deltaX, float deltaY, float totalX, float totalY) {
mModelSelectorButton.drag(x, y);
getActiveStripLayoutHelper().drag(time, x, y, deltaX, deltaY, totalX, totalY);
}
/**
* Called on touch fling event. This is called before the onUpOrCancel event.
* @param time The current time of the app in ms.
* @param x The y coordinate of the start of the fling event.
* @param y The y coordinate of the start of the fling event.
* @param velocityX The amount of velocity in the x direction.
* @param velocityY The amount of velocity in the y direction.
*/
public void fling(long time, float x, float y, float velocityX, float velocityY) {
getActiveStripLayoutHelper().fling(time, x, y, velocityX, velocityY);
}
/**
* Called on onDown event.
* @param time The time stamp in millisecond of the event.
* @param x The x position of the event.
* @param y The y position of the event.
* @param fromMouse Whether the event originates from a mouse.
* @param buttons State of all buttons that are pressed.
*/
public void onDown(long time, float x, float y, boolean fromMouse, int buttons) {
if (mModelSelectorButton.onDown(x, y)) return;
getActiveStripLayoutHelper().onDown(time, x, y, fromMouse, buttons);
}
/**
* Called on long press touch event.
* @param time The current time of the app in ms.
* @param x The x coordinate of the position of the press event.
* @param y The y coordinate of the position of the press event.
*/
public void onLongPress(long time, float x, float y) {
getActiveStripLayoutHelper().onLongPress(time, x, y);
}
/**
* Called on click. This is called before the onUpOrCancel event.
* @param time The current time of the app in ms.
* @param x The x coordinate of the position of the click.
* @param y The y coordinate of the position of the click.
* @param fromMouse Whether the event originates from a mouse.
* @param buttons State of all buttons that were pressed when onDown was invoked.
*/
public void click(long time, float x, float y, boolean fromMouse, int buttons) {
if (mModelSelectorButton.click(x, y) && mTabModelSelector != null) {
getActiveStripLayoutHelper().finishAnimation();
if (!mModelSelectorButton.isVisible()) return;
mTabModelSelector.selectModel(!mTabModelSelector.isIncognitoSelected());
return;
}
getActiveStripLayoutHelper().click(time, x, y, fromMouse, buttons);
}
/**
* Called on up or cancel touch events. This is called after the click and fling event if any.
* @param time The current time of the app in ms.
*/
public void onUpOrCancel(long time) {
if (mModelSelectorButton.onUpOrCancel() && mTabModelSelector != null) {
getActiveStripLayoutHelper().finishAnimation();
if (!mModelSelectorButton.isVisible()) return;
mTabModelSelector.selectModel(!mTabModelSelector.isIncognitoSelected());
return;
}
getActiveStripLayoutHelper().onUpOrCancel(time);
}
/**
* @param incognito Whether or not you want the incognito StripLayoutHelper
* @return The requested StripLayoutHelper.
*/
@VisibleForTesting
public StripLayoutHelper getStripLayoutHelper(boolean incognito) {
return incognito ? mIncognitoHelper : mNormalHelper;
}
/**
* @return The currently visible strip layout helper.
*/
@VisibleForTesting
public StripLayoutHelper getActiveStripLayoutHelper() {
return getStripLayoutHelper(mIsIncognito);
}
private StripLayoutHelper getInactiveStripLayoutHelper() {
return mIsIncognito ? mNormalHelper : mIncognitoHelper;
}
}