| Index: chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6d2c08ab214bc26014752a430942de352c9dbe3a
|
| --- /dev/null
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/Layout.java
|
| @@ -0,0 +1,1181 @@
|
| +// 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 static org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.AnimatableAnimation.createAnimation;
|
| +
|
| +import android.content.Context;
|
| +import android.graphics.Point;
|
| +import android.graphics.Rect;
|
| +import android.graphics.RectF;
|
| +import android.view.MotionEvent;
|
| +import android.view.View;
|
| +import android.view.ViewGroup;
|
| +import android.view.animation.Interpolator;
|
| +
|
| +import org.chromium.base.VisibleForTesting;
|
| +import org.chromium.base.annotations.SuppressFBWarnings;
|
| +import org.chromium.chrome.browser.Tab;
|
| +import org.chromium.chrome.browser.compositor.LayerTitleCache;
|
| +import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel;
|
| +import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animatable;
|
| +import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animation;
|
| +import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab;
|
| +import org.chromium.chrome.browser.compositor.layouts.components.VirtualView;
|
| +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.overlays.SceneOverlay;
|
| +import org.chromium.chrome.browser.compositor.scene_layer.SceneLayer;
|
| +import org.chromium.chrome.browser.compositor.scene_layer.SceneOverlayLayer;
|
| +import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
|
| +import org.chromium.chrome.browser.tabmodel.TabModel;
|
| +import org.chromium.chrome.browser.tabmodel.TabModelSelector;
|
| +import org.chromium.chrome.browser.tabmodel.TabModelUtils;
|
| +import org.chromium.content.browser.ContentViewCore;
|
| +import org.chromium.ui.resources.ResourceManager;
|
| +
|
| +import java.util.ArrayList;
|
| +import java.util.List;
|
| +
|
| +/**
|
| + * Abstract representation of an OpenGL layout tailored to draw tabs. It is a framework used as an
|
| + * alternative to the Android UI for lower level hardware accelerated rendering.
|
| + * This layout also pass through all the events that may happen.
|
| + */
|
| +
|
| +public abstract class Layout implements TabContentManager.ThumbnailChangeListener {
|
| + /**
|
| + * The orientation of the device.
|
| + */
|
| + public interface Orientation {
|
| + public static final int UNSET = 0;
|
| + public static final int PORTRAIT = 1;
|
| + public static final int LANDSCAPE = 2;
|
| + }
|
| +
|
| + /**
|
| + * The sizing requirements of each layout. These flags allow each layout to specify certain
|
| + * sizing allowances/constraints by combining them.
|
| + *
|
| + * TODO(dtrainor): Flesh out support for all of these flag combinations.
|
| + */
|
| + public interface SizingFlags {
|
| + /**
|
| + * The toolbar is allowed to be hidden.
|
| + */
|
| + public static final int ALLOW_TOOLBAR_HIDE = 0x1;
|
| +
|
| + /**
|
| + * The toolbar is allowed to be shown.
|
| + */
|
| + public static final int ALLOW_TOOLBAR_SHOW = 0x10;
|
| +
|
| + /**
|
| + * The toolbar is allowed to animate between the transitions. Note that this flag is not
|
| + * completely supported in all combinations with the other flags. As functionality is
|
| + * added and required this will be improved.
|
| + */
|
| + public static final int ALLOW_TOOLBAR_ANIMATE = 0x100;
|
| +
|
| + /**
|
| + * The layout requires a fullscreen size regardless of the toolbar position.
|
| + */
|
| + public static final int REQUIRE_FULLSCREEN_SIZE = 0x1000;
|
| +
|
| + /**
|
| + * The layout does not support fullscreen and it should animate the toolbar away.
|
| + * A helper flag to make it easier to specify restrictions.
|
| + */
|
| + public static final int HELPER_NO_FULLSCREEN_SUPPORT =
|
| + ALLOW_TOOLBAR_SHOW | ALLOW_TOOLBAR_ANIMATE;
|
| +
|
| + /**
|
| + * The layout supports persistent fullscreen and should hide the toolbar immediately.
|
| + * A helper flag to make it easier to specify restrictions.
|
| + */
|
| + public static final int HELPER_HIDE_TOOLBAR_IMMEDIATE = ALLOW_TOOLBAR_HIDE;
|
| +
|
| + /**
|
| + * The layout fully supports fullscreen and the toolbar is allowed to show, hide, and
|
| + * animate.
|
| + * A helper flag to make it easier to specify restrictions.
|
| + */
|
| + public static final int HELPER_SUPPORTS_FULLSCREEN =
|
| + ALLOW_TOOLBAR_HIDE | ALLOW_TOOLBAR_SHOW | ALLOW_TOOLBAR_ANIMATE;
|
| + }
|
| +
|
| + // Defines to make the code easier to read.
|
| + public static final boolean NEED_TITLE = true;
|
| + public static final boolean NO_TITLE = false;
|
| + public static final boolean SHOW_CLOSE_BUTTON = true;
|
| + public static final boolean NO_CLOSE_BUTTON = false;
|
| +
|
| + /** Length of the unstalling animation. **/
|
| + public static final long UNSTALLED_ANIMATION_DURATION_MS = 500;
|
| +
|
| + // Drawing area properties.
|
| + private float mWidth;
|
| + private float mHeight;
|
| + private float mHeightMinusTopControls;
|
| +
|
| + /** A {@link Context} instance. */
|
| + private Context mContext;
|
| +
|
| + /** The current {@link Orientation} of the layout. */
|
| + private int mCurrentOrientation;
|
| +
|
| + // Tabs
|
| + protected TabModelSelector mTabModelSelector;
|
| + protected TabContentManager mTabContentManager;
|
| +
|
| + private ChromeAnimation<Animatable<?>> mLayoutAnimations;
|
| +
|
| + // Tablet tab strip managers.
|
| + private final List<SceneOverlay> mSceneOverlays = new ArrayList<SceneOverlay>();
|
| +
|
| + // Helpers
|
| + private final LayoutUpdateHost mUpdateHost;
|
| + protected final LayoutRenderHost mRenderHost;
|
| + private EventFilter mEventFilter;
|
| +
|
| + /** The tabs currently being rendered as part of this layout. The tabs are
|
| + * drawn using the same ordering as this array. */
|
| + protected LayoutTab[] mLayoutTabs;
|
| +
|
| + // True means that the layout is going to hide as soon as the animation finishes.
|
| + private boolean mIsHiding;
|
| +
|
| + // The next id to show when the layout is hidden, or TabBase#INVALID_TAB_ID if no change.
|
| + private int mNextTabId = Tab.INVALID_TAB_ID;
|
| +
|
| + /**
|
| + * The {@link Layout} is not usable until sizeChanged is called.
|
| + * This is convenient this way so we can pre-create the layout before the host is fully defined.
|
| + * @param context The current Android's context.
|
| + * @param updateHost The parent {@link LayoutUpdateHost}.
|
| + * @param renderHost The parent {@link LayoutRenderHost}.
|
| + * @param eventFilter The {@link EventFilter} this {@link Layout} is listening to.
|
| + */
|
| + public Layout(Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost,
|
| + EventFilter eventFilter) {
|
| + mContext = context;
|
| + mUpdateHost = updateHost;
|
| + mRenderHost = renderHost;
|
| + mEventFilter = eventFilter;
|
| +
|
| + // Invalid sizes
|
| + mWidth = -1;
|
| + mHeight = -1;
|
| + mHeightMinusTopControls = -1;
|
| +
|
| + mCurrentOrientation = Orientation.UNSET;
|
| + }
|
| +
|
| + /**
|
| + * Adds a {@link SceneOverlay} that can potentially be shown on top of this {@link Layout}. The
|
| + * {@link SceneOverlay}s added to this {@link Layout} will be cascaded in the order they are
|
| + * added. The {@link SceneOverlay} added first will become the content of the
|
| + * {@link SceneOverlay} added second, and so on.
|
| + * @param helper A {@link SceneOverlay} to add as a potential overlay for this {@link Layout}.
|
| + */
|
| + public void addSceneOverlay(SceneOverlay helper) {
|
| + assert !mSceneOverlays.contains(helper);
|
| + mSceneOverlays.add(helper);
|
| + }
|
| +
|
| + /**
|
| + * Cleans up any internal state. This object should not be used after this call.
|
| + */
|
| + public void destroy() {
|
| + }
|
| +
|
| + /**
|
| + * @return The current {@link Context} instance associated with this {@link Layout}.
|
| + */
|
| + public Context getContext() {
|
| + return mContext;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the {@link Layout} is currently active.
|
| + */
|
| + public boolean isActive() {
|
| + return mUpdateHost.isActiveLayout(this);
|
| + }
|
| +
|
| + /**
|
| + * @return A {@link View} that should be the one the user can interact with. This can be
|
| + * {@code null} if there is no {@link View} representing {@link Tab} content that should
|
| + * be interacted with at this time.
|
| + */
|
| + public View getViewForInteraction() {
|
| + if (!shouldDisplayContentOverlay()) return null;
|
| +
|
| + Tab tab = mTabModelSelector.getCurrentTab();
|
| + if (tab == null) return null;
|
| +
|
| + return tab.getView();
|
| + }
|
| +
|
| + /**
|
| + * Used to get a list of Android {@link View}s that represent both the normal content as well as
|
| + * overlays.
|
| + * @param views A {@link List} that will be populated with {@link View}s that represent all of
|
| + * the content in this {@link Layout}.
|
| + */
|
| + public void getAllViews(List<View> views) {
|
| + Tab tab = mTabModelSelector.getCurrentTab();
|
| + if (tab == null) return;
|
| + tab.getAllViews(views);
|
| + }
|
| +
|
| + /**
|
| + * Used to get a list of {@link ContentViewCore}s that represent both the normal content as well
|
| + * as overlays. These are all {@link ContentViewCore}s currently showing or rendering content
|
| + * for this {@link Layout}.
|
| + * @param contents A {@link List} that will be populated with {@link ContentViewCore}s currently
|
| + * rendering content related to this {@link Layout}.
|
| + */
|
| + public void getAllContentViewCores(List<ContentViewCore> contents) {
|
| + Tab tab = mTabModelSelector.getCurrentTab();
|
| + if (tab == null) return;
|
| + tab.getAllContentViewCores(contents);
|
| + }
|
| +
|
| + /**
|
| + * Get a list of virtual views for accessibility.
|
| + *
|
| + * @param views A List to populate with virtual views.
|
| + */
|
| + public void getVirtualViews(List<VirtualView> views) {
|
| + // TODO(dtrainor): Investigate order.
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).getVirtualViews(views);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Creates a {@link LayoutTab}.
|
| + * @param id The id of the reference {@link Tab} in the {@link TabModel}.
|
| + * @param isIncognito Whether the new tab is incognito.
|
| + * @param showCloseButton True to show and activate a close button on the border.
|
| + * @param isTitleNeeded Whether a title will be shown.
|
| + * @return The newly created {@link LayoutTab}.
|
| + */
|
| + public LayoutTab createLayoutTab(int id, boolean isIncognito,
|
| + boolean showCloseButton, boolean isTitleNeeded) {
|
| + return createLayoutTab(id, isIncognito, showCloseButton, isTitleNeeded, -1.f, -1.f);
|
| + }
|
| +
|
| + /**
|
| + * Creates a {@link LayoutTab}.
|
| + * @param id The id of the reference {@link Tab} in the {@link TabModel}.
|
| + * @param isIncognito Whether the new tab is incognito.
|
| + * @param showCloseButton True to show and activate a close button on the border.
|
| + * @param isTitleNeeded Whether a title will be shown.
|
| + * @param maxContentWidth The max content width of the tab. Negative numbers will use the
|
| + * original content width.
|
| + * @param maxContentHeight The max content height of the tab. Negative numbers will use the
|
| + * original content height.
|
| + * @return The newly created {@link LayoutTab}.
|
| + */
|
| + public LayoutTab createLayoutTab(int id, boolean isIncognito, boolean showCloseButton,
|
| + boolean isTitleNeeded, float maxContentWidth, float maxContentHeight) {
|
| + LayoutTab layoutTab = mUpdateHost.createLayoutTab(
|
| + id, isIncognito, showCloseButton, isTitleNeeded, maxContentWidth, maxContentHeight);
|
| + initLayoutTabFromHost(layoutTab);
|
| + return layoutTab;
|
| + }
|
| +
|
| + /**
|
| + * Releases the data we keep for that {@link LayoutTab}.
|
| + * @param layoutTab The {@link LayoutTab} to release.
|
| + */
|
| + public void releaseTabLayout(LayoutTab layoutTab) {
|
| + mUpdateHost.releaseTabLayout(layoutTab.getId());
|
| + }
|
| +
|
| + /**
|
| + * Update the animation and give chance to cascade the changes.
|
| + * @param time The current time of the app in ms.
|
| + * @param dt The delta time between update frames in ms.
|
| + * @return Whether the layout is done updating.
|
| + */
|
| + public final boolean onUpdate(long time, long dt) {
|
| + final boolean doneAnimating = onUpdateAnimation(time, false);
|
| +
|
| + // Don't update the layout if onUpdateAnimation ended up making a new layout active.
|
| + if (mUpdateHost.isActiveLayout(this)) updateLayout(time, dt);
|
| +
|
| + return doneAnimating;
|
| + }
|
| +
|
| + /**
|
| + * Layout-specific updates. Cascades the values updated by the animations.
|
| + * @param time The current time of the app in ms.
|
| + * @param dt The delta time between update frames in ms.
|
| + */
|
| + protected void updateLayout(long time, long dt) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).updateOverlay(time, dt);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Request that the renderer render a frame (after the current frame). This
|
| + * should be called whenever a new frame should be rendered.
|
| + */
|
| + public void requestRender() {
|
| + mRenderHost.requestRender();
|
| + }
|
| +
|
| + /**
|
| + * Requests one more frame of refresh for the transforms and changing properties. Primarily,
|
| + * this is so animations can continue to animate.
|
| + */
|
| + public void requestUpdate() {
|
| + mUpdateHost.requestUpdate();
|
| + }
|
| +
|
| + /**
|
| + * Called when the context and size of the view has changed.
|
| + *
|
| + * @param context The current Android's context.
|
| + */
|
| + public void contextChanged(Context context) {
|
| + mContext = context;
|
| + LayoutTab.resetDimensionConstants(context);
|
| + }
|
| +
|
| + /**
|
| + * Called when the size of the viewport has changed.
|
| + * @param availableViewport The actual viewport this {@link Layout} should be rendering to.
|
| + * @param visibleViewport The visible viewport that represents the area on the screen
|
| + * this {@link Layout} gets to draw to (potentially takes into
|
| + * account top controls).
|
| + * @param screenViewport The viewport of the screen.
|
| + * @param heightMinusTopControls The height the {@link Layout} gets excluding the height of the
|
| + * top controls. TODO(dtrainor): Look at getting rid of this.
|
| + * @param orientation The new orientation. Valid values are defined by
|
| + * {@link Orientation}.
|
| + */
|
| + public final void sizeChanged(RectF availableViewport, RectF visibleViewport,
|
| + RectF screenViewport, float heightMinusTopControls, int orientation) {
|
| + // 1. Pull out this Layout's specific width and height properties based on the available
|
| + // viewport.
|
| + float width = availableViewport.width();
|
| + float height = availableViewport.height();
|
| +
|
| + // 2. Check if any Layout-specific properties have changed.
|
| + boolean layoutPropertiesChanged = mWidth != width
|
| + || mHeight != height
|
| + || mHeightMinusTopControls != heightMinusTopControls
|
| + || mCurrentOrientation != orientation;
|
| +
|
| + // 3. Update the internal sizing properties.
|
| + mWidth = width;
|
| + mHeight = height;
|
| + mHeightMinusTopControls = heightMinusTopControls;
|
| + mCurrentOrientation = orientation;
|
| +
|
| + // 4. Notify the actual Layout if necessary.
|
| + if (layoutPropertiesChanged) {
|
| + notifySizeChanged(width, height, orientation);
|
| + }
|
| +
|
| + // 5. TODO(dtrainor): Notify the overlay objects.
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).onSizeChanged(width, height, visibleViewport.top);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Notifies when the size or the orientation of the view has actually changed.
|
| + *
|
| + * @param width The new width in dp.
|
| + * @param height The new height in dp.
|
| + * @param orientation The new orientation.
|
| + */
|
| + protected void notifySizeChanged(float width, float height, int orientation) { }
|
| +
|
| + /**
|
| + * Notify the a title has changed.
|
| + *
|
| + * @param tabId The id of the tab that has changed.
|
| + * @param title The new title.
|
| + */
|
| + public void tabTitleChanged(int tabId, String title) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).tabTitleChanged(tabId, title);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Sets the managers needed to for the layout to get information from outside. The managers
|
| + * are tailored to be called from the GL thread.
|
| + *
|
| + * @param modelSelector The {@link TabModelSelector} to be set on the layout.
|
| + * @param manager The {@link TabContentManager} to get tab display content.
|
| + */
|
| + public void setTabModelSelector(TabModelSelector modelSelector, TabContentManager manager) {
|
| + if (mTabContentManager != null) mTabContentManager.removeThumbnailChangeListener(this);
|
| + mTabModelSelector = modelSelector;
|
| + mTabContentManager = manager;
|
| + if (mTabContentManager != null) mTabContentManager.addThumbnailChangeListener(this);
|
| + }
|
| +
|
| + /**
|
| + * @return Information about the sizing requirements of this layout.
|
| + */
|
| + public int getSizingFlags() {
|
| + return SizingFlags.HELPER_NO_FULLSCREEN_SUPPORT;
|
| + }
|
| +
|
| + /**
|
| + * Informs this cache of the relevant {@link Tab} {@code id}s that will be used in the
|
| + * near future.
|
| + */
|
| + protected void updateCacheVisibleIds(List<Integer> priority) {
|
| + if (mTabContentManager != null) mTabContentManager.updateVisibleIds(priority);
|
| + }
|
| +
|
| + /**
|
| + * To be called when the layout is starting a transition out of the view mode.
|
| + * @param nextTabId The id of the next tab.
|
| + * @param hintAtTabSelection Whether or not the new tab selection should be broadcast as a hint
|
| + * potentially before this {@link Layout} is done hiding and the
|
| + * selection occurs.
|
| + */
|
| + public void startHiding(int nextTabId, boolean hintAtTabSelection) {
|
| + getLayoutTab(nextTabId);
|
| + mUpdateHost.startHiding(nextTabId, hintAtTabSelection);
|
| + mIsHiding = true;
|
| + mNextTabId = nextTabId;
|
| + }
|
| +
|
| + /**
|
| + * @return True is the layout is in the process of hiding itself.
|
| + */
|
| + public boolean isHiding() {
|
| + return mIsHiding;
|
| + }
|
| +
|
| + /**
|
| + * @return The incognito state of the layout.
|
| + */
|
| + public boolean isIncognito() {
|
| + return mTabModelSelector.isIncognitoSelected();
|
| + }
|
| +
|
| + /**
|
| + * To be called when the transition into the layout is done.
|
| + */
|
| + public void doneShowing() {
|
| + mUpdateHost.doneShowing();
|
| + }
|
| +
|
| + /**
|
| + * To be called when the transition out of the view mode is done.
|
| + * This is currently called by the renderer when all the animation are done while hiding.
|
| + */
|
| + public void doneHiding() {
|
| + mIsHiding = false;
|
| + if (mNextTabId != Tab.INVALID_TAB_ID) {
|
| + TabModel model = mTabModelSelector.getModelForTabId(mNextTabId);
|
| + if (model != null) {
|
| + TabModelUtils.setIndex(model, TabModelUtils.getTabIndexById(model, mNextTabId));
|
| + }
|
| + mNextTabId = Tab.INVALID_TAB_ID;
|
| + }
|
| + mUpdateHost.doneHiding();
|
| + }
|
| +
|
| + /**
|
| + * Called when a tab is getting selected. Typically when exiting the overview mode.
|
| + * @param time The current time of the app in ms.
|
| + * @param tabId The id of the selected tab.
|
| + */
|
| + public void onTabSelecting(long time, int tabId) {
|
| + startHiding(tabId, true);
|
| + }
|
| +
|
| + /**
|
| + * Initialize the layout to be shown.
|
| + * @param time The current time of the app in ms.
|
| + * @param animate Whether to play an entry animation.
|
| + */
|
| + public void show(long time, boolean animate) {
|
| + mIsHiding = false;
|
| + mNextTabId = Tab.INVALID_TAB_ID;
|
| + }
|
| +
|
| + /**
|
| + * Hands the layout an Android view to attach it's views to.
|
| + * @param container The Android View to attach the layout's views to.
|
| + */
|
| + public void attachViews(ViewGroup container) { }
|
| +
|
| + /**
|
| + * Signal to the Layout to detach it's views from the container.
|
| + */
|
| + public void detachViews() { }
|
| +
|
| + /**
|
| + * Called when the swipe animation get initiated. It gives a chance to initialize everything.
|
| + * @param time The current time of the app in ms.
|
| + * @param direction The direction the swipe is in.
|
| + * @param x The horizontal coordinate the swipe started at in dp.
|
| + * @param y The vertical coordinate the swipe started at in dp.
|
| + */
|
| + public void swipeStarted(long time, ScrollDirection direction, float x, float y) { }
|
| +
|
| + /**
|
| + * Updates a swipe gesture.
|
| + * @param time The current time of the app in ms.
|
| + * @param x The horizontal coordinate the swipe is currently at in dp.
|
| + * @param y The vertical coordinate the swipe is currently at in dp.
|
| + * @param dx The horizontal delta since the last update in dp.
|
| + * @param dy The vertical delta since the last update in dp.
|
| + * @param tx The horizontal difference between the start and the current position in dp.
|
| + * @param ty The vertical difference between the start and the current position in dp.
|
| + */
|
| + public void swipeUpdated(long time, float x, float y, float dx, float dy, float tx, float ty) {
|
| + }
|
| +
|
| + /**
|
| + * Called when the swipe ends; most likely on finger up event. It gives a chance to start
|
| + * an ending animation to exit the mode gracefully.
|
| + * @param time The current time of the app in ms.
|
| + */
|
| + public void swipeFinished(long time) { }
|
| +
|
| + /**
|
| + * Called when the user has cancelled a swipe; most likely if they have dragged their finger
|
| + * back to the starting position. Some handlers will throw swipeFinished() instead.
|
| + * @param time The current time of the app in ms.
|
| + */
|
| + public void swipeCancelled(long time) { }
|
| +
|
| + /**
|
| + * Fling from a swipe gesture.
|
| + * @param time The current time of the app in ms.
|
| + * @param x The horizontal coordinate the swipe is currently at in dp.
|
| + * @param y The vertical coordinate the swipe is currently at in dp.
|
| + * @param tx The horizontal difference between the start and the current position in dp.
|
| + * @param ty The vertical difference between the start and the current position in dp.
|
| + * @param vx The horizontal velocity of the fling.
|
| + * @param vy The vertical velocity of the fling.
|
| + */
|
| + public void swipeFlingOccurred(long time, float x, float y, float tx, float ty, float vx,
|
| + float vy) { }
|
| +
|
| + /**
|
| + * Forces the current animation to finish and broadcasts the proper event.
|
| + */
|
| + protected void forceAnimationToFinish() {
|
| + if (mLayoutAnimations != null) {
|
| + mLayoutAnimations.updateAndFinish();
|
| + mLayoutAnimations = null;
|
| + onAnimationFinished();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * @return The width of the drawing area.
|
| + */
|
| + public float getWidth() {
|
| + return mWidth;
|
| + }
|
| +
|
| + /**
|
| + * @return The height of the drawing area.
|
| + */
|
| + public float getHeight() {
|
| + return mHeight;
|
| + }
|
| +
|
| + /**
|
| + * @return The height of the drawing area minus the top controls.
|
| + */
|
| + public float getHeightMinusTopControls() {
|
| + return mHeightMinusTopControls;
|
| + }
|
| +
|
| + /**
|
| + * @see Orientation
|
| + * @return The orientation of the screen (portrait or landscape). Values are defined by
|
| + * {@link Orientation}.
|
| + */
|
| + public int getOrientation() {
|
| + return mCurrentOrientation;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether or not any tabs in this layer use the toolbar resource.
|
| + */
|
| + public boolean usesToolbarLayer() {
|
| + if (mLayoutTabs == null) return false;
|
| + for (int i = 0; i < mLayoutTabs.length; i++) {
|
| + if (mLayoutTabs[i].showToolbar()) return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * Returns whether or not the native Contextual Search layout is showing. Layouts supporting
|
| + * Contextual Search should override this method and return "true", indicating that
|
| + * the layout has already been swapped from an Android View to a native cc::Layer.
|
| + * @return Whether or not the Contextual Search bar is showing.
|
| + */
|
| + public boolean isContextualSearchLayoutShowing() {
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @return The {@link ContextualSearchPanel} or "null" if there is none for this layout.
|
| + */
|
| + public ContextualSearchPanel getContextualSearchPanel() {
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * @param currentOffset The current top controls offset in dp.
|
| + * @return {@link Float#NaN} if no offset should be used, or a value in dp if the top controls
|
| + * offset should be overridden.
|
| + */
|
| + public float getTopControlsOffset(float currentOffset) {
|
| + return Float.NaN;
|
| + }
|
| +
|
| + /**
|
| + * Initializes a {@link LayoutTab} with data from the {@link LayoutUpdateHost}. This function
|
| + * eventually needs to be called but may be overridden to manage the posting traffic.
|
| + *
|
| + * @param layoutTab The {@link LayoutTab} To initialize from a
|
| + * {@link Tab} on the UI thread.
|
| + * @return Whether the asynchronous initialization of the {@link LayoutTab} has really
|
| + * been posted.
|
| + */
|
| + protected boolean initLayoutTabFromHost(LayoutTab layoutTab) {
|
| + if (layoutTab.isInitFromHostNeeded()) {
|
| + mUpdateHost.initLayoutTabFromHost(layoutTab.getId());
|
| + return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * 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.
|
| + */
|
| + public void drag(long time, float x, float y, float deltaX, float deltaY) { }
|
| +
|
| + /**
|
| + * 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) { }
|
| +
|
| + /**
|
| + * 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.
|
| + */
|
| + public void onDown(long time, float x, float y) { }
|
| +
|
| + /**
|
| + * 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) { }
|
| +
|
| + /**
|
| + * 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.
|
| + */
|
| + public void click(long time, float x, float y) { }
|
| +
|
| + /**
|
| + * 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) { }
|
| +
|
| + /**
|
| + * Called when at least 2 touch events are detected.
|
| + * @param time The current time of the app in ms.
|
| + * @param x0 The x coordinate of the first touch event.
|
| + * @param y0 The y coordinate of the first touch event.
|
| + * @param x1 The x coordinate of the second touch event.
|
| + * @param y1 The y coordinate of the second touch event.
|
| + * @param firstEvent The pinch is the first of a sequence of pinch events.
|
| + */
|
| + public void onPinch(long time, float x0, float y0, float x1, float y1, boolean firstEvent) { }
|
| +
|
| + /**
|
| + * Called by the LayoutManager when an animation should be killed.
|
| + */
|
| + public void unstallImmediately() { }
|
| +
|
| + /**
|
| + * Called by the LayoutManager when an animation should be killed.
|
| + * @param tabId The tab that the kill signal is associated with
|
| + */
|
| + public void unstallImmediately(int tabId) { }
|
| +
|
| + /**
|
| + * Called by the LayoutManager when they system back button is pressed.
|
| + * @return Whether or not the layout consumed the event.
|
| + */
|
| + public boolean onBackPressed() {
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * Called when a tab get selected. Typically when a tab get closed and the new current tab get
|
| + * selected.
|
| + * @param time The current time of the app in ms.
|
| + * @param tabId The id of the selected tab.
|
| + * @param prevId The id of the previously selected tab.
|
| + * @param incognito Whether or not the affected model was incognito.
|
| + */
|
| + public void onTabSelected(long time, int tabId, int prevId, boolean incognito) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).tabSelected(time, incognito, tabId, prevId);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when a tab is about to be closed. When called, the closing tab will still
|
| + * be part of the model.
|
| + * @param time The current time of the app in ms.
|
| + * @param tabId The id of the tab being closed
|
| + */
|
| + public void onTabClosing(long time, int tabId) {
|
| + }
|
| +
|
| + /**
|
| + * Called when a tab is being closed. When called, the closing tab will not
|
| + * be part of the model.
|
| + * @param time The current time of the app in ms.
|
| + * @param tabId The id of the tab being closed.
|
| + * @param nextTabId The id if the tab that is being switched to.
|
| + * @param incognito Whether or not the affected model was incognito.
|
| + */
|
| + public void onTabClosed(long time, int tabId, int nextTabId, boolean incognito) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).tabClosed(time, incognito, tabId);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when all the tabs in the current stack need to be closed.
|
| + * When called, the tabs will still be part of the model.
|
| + * @param time The current time of the app in ms.
|
| + * @param incognito True if this the incognito tab model should close all tabs, false otherwise.
|
| + */
|
| + public void onTabsAllClosing(long time, boolean incognito) {
|
| + }
|
| +
|
| + /**
|
| + * Called before a tab is created from the top left button.
|
| + *
|
| + * @param sourceTabId The id of the source tab.
|
| + */
|
| + public void onTabCreating(int sourceTabId) { }
|
| +
|
| + /**
|
| + * Called when a tab is created from the top left button.
|
| + * @param time The current time of the app in ms.
|
| + * @param tabId The id of the newly created tab.
|
| + * @param tabIndex The index of the newly created tab.
|
| + * @param sourceTabId The id of the source tab.
|
| + * @param newIsIncognito Whether the new tab is incognito.
|
| + * @param background Whether the tab is created in the background.
|
| + * @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.
|
| + */
|
| + public void onTabCreated(long time, int tabId, int tabIndex, int sourceTabId,
|
| + boolean newIsIncognito, boolean background, float originX, float originY) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).tabCreated(time, newIsIncognito, tabId, sourceTabId, !background);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when the current tabModel switched (e.g. standard -> incognito).
|
| + *
|
| + * @param incognito True if the new model is incognito.
|
| + */
|
| + public void onTabModelSwitched(boolean incognito) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).tabModelSwitched(incognito);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when a tab has been moved in the tabModel.
|
| + * @param time The current time of the app in ms.
|
| + * @param tabId The id of the Tab.
|
| + * @param oldIndex The old index of the tab in the tabModel.
|
| + * @param newIndex The new index of the tab in the tabModel.
|
| + * @param incognito True if the tab is incognito.
|
| + */
|
| + public void onTabMoved(long time, int tabId, int oldIndex, int newIndex, boolean incognito) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).tabMoved(time, incognito, tabId, oldIndex, newIndex);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when a tab has started loading.
|
| + * @param id The id of the Tab.
|
| + * @param incognito True if the tab is incognito.
|
| + */
|
| + public void onTabPageLoadStarted(int id, boolean incognito) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).tabPageLoadStarted(id, incognito);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when a tab has finished loading.
|
| + * @param id The id of the Tab.
|
| + * @param incognito True if the tab is incognito.
|
| + */
|
| + public void onTabPageLoadFinished(int id, boolean incognito) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).tabPageLoadFinished(id, incognito);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when a tab has started loading resources.
|
| + * @param id The id of the Tab.
|
| + * @param incognito True if the tab is incognito.
|
| + */
|
| + public void onTabLoadStarted(int id, boolean incognito) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).tabLoadStarted(id, incognito);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when a tab has stopped loading resources.
|
| + * @param id The id of the Tab.
|
| + * @param incognito True if the tab is incognito.
|
| + */
|
| + public void onTabLoadFinished(int id, boolean incognito) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).tabLoadFinished(id, incognito);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when a tab close has been undone and the tab has been restored.
|
| + * @param time The current time of the app in ms.
|
| + * @param id The id of the Tab.
|
| + * @param incognito True if the tab is incognito
|
| + */
|
| + public void onTabClosureCancelled(long time, int id, boolean incognito) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + mSceneOverlays.get(i).tabClosureCancelled(time, incognito, id);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when a tab is finally closed if the action was previously undoable.
|
| + * @param time The current time of the app in ms.
|
| + * @param id The id of the Tab.
|
| + * @param incognito True if the tab is incognito
|
| + */
|
| + public void onTabClosureCommitted(long time, int id, boolean incognito) { }
|
| +
|
| + @Override
|
| + public void onThumbnailChange(int id) {
|
| + requestUpdate();
|
| + }
|
| +
|
| + /**
|
| + * Steps the animation forward and updates all the animated values.
|
| + * @param time The current time of the app in ms.
|
| + * @param jumpToEnd Whether to finish the animation.
|
| + * @return Whether the animation was finished.
|
| + */
|
| + protected boolean onUpdateAnimation(long time, boolean jumpToEnd) {
|
| + boolean finished = true;
|
| + if (mLayoutAnimations != null) {
|
| + if (jumpToEnd) {
|
| + finished = mLayoutAnimations.finished();
|
| + mLayoutAnimations.updateAndFinish();
|
| + } else {
|
| + finished = mLayoutAnimations.update(time);
|
| + }
|
| +
|
| + if (finished || jumpToEnd) {
|
| + mLayoutAnimations = null;
|
| + onAnimationFinished();
|
| + }
|
| + requestUpdate();
|
| + }
|
| + return finished;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether or not there is an animation currently being driven by this {@link Layout}.
|
| + */
|
| + @VisibleForTesting
|
| + public boolean isLayoutAnimating() {
|
| + return mLayoutAnimations != null && !mLayoutAnimations.finished();
|
| + }
|
| +
|
| + /**
|
| + * Called when layout-specific actions are needed after the animation finishes.
|
| + */
|
| + protected void onAnimationStarted() {
|
| + }
|
| +
|
| + /**
|
| + * Called when layout-specific actions are needed after the animation finishes.
|
| + */
|
| + protected void onAnimationFinished() {
|
| + }
|
| +
|
| + /**
|
| + * Creates an {@link org.chromium.chrome.browser.compositor.layouts.ChromeAnimation
|
| + * .AnimatableAnimation} and adds it to the animation.
|
| + * Automatically sets the start value at the beginning of the animation.
|
| + */
|
| + protected <T extends Enum<?>> void addToAnimation(Animatable<T> object, T prop, float start,
|
| + float end, long duration, long startTime) {
|
| + addToAnimation(object, prop, start, end, duration, startTime, false);
|
| + }
|
| +
|
| + /**
|
| + * Creates an {@link org.chromium.chrome.browser.compositor.layouts.ChromeAnimation
|
| + * .AnimatableAnimation} and it to the animation. Uses a deceleration interpolator by default.
|
| + */
|
| + protected <T extends Enum<?>> void addToAnimation(Animatable<T> object, T prop, float start,
|
| + float end, long duration, long startTime, boolean setStartValueAfterDelay) {
|
| + addToAnimation(object, prop, start, end, duration, startTime, setStartValueAfterDelay,
|
| + ChromeAnimation.getDecelerateInterpolator());
|
| + }
|
| +
|
| + /**
|
| + * Creates an {@link org.chromium.chrome.browser.compositor.layouts.ChromeAnimation
|
| + * .AnimatableAnimation} and
|
| + * adds it to the animation.
|
| + *
|
| + * @param <T> The Enum type of the Property being used
|
| + * @param object The object being animated
|
| + * @param prop The property being animated
|
| + * @param start The starting value of the animation
|
| + * @param end The ending value of the animation
|
| + * @param duration The duration of the animation in ms
|
| + * @param startTime The start time in ms
|
| + * @param setStartValueAfterDelay See {@link Animation#setStartValueAfterStartDelay(boolean)}
|
| + * @param interpolator The interpolator to use for the animation
|
| + */
|
| + protected <T extends Enum<?>> void addToAnimation(Animatable<T> object, T prop, float start,
|
| + float end, long duration, long startTime, boolean setStartValueAfterDelay,
|
| + Interpolator interpolator) {
|
| + ChromeAnimation.Animation<Animatable<?>> component = createAnimation(object, prop, start,
|
| + end, duration, startTime, setStartValueAfterDelay, interpolator);
|
| + addToAnimation(component);
|
| + }
|
| +
|
| + /**
|
| + * Appends an Animation to the current animation set and starts it immediately. If the set is
|
| + * already finished or doesn't exist, the animation set is also started.
|
| + */
|
| + protected void addToAnimation(ChromeAnimation.Animation<Animatable<?>> component) {
|
| + if (mLayoutAnimations == null || mLayoutAnimations.finished()) {
|
| + onAnimationStarted();
|
| + mLayoutAnimations = new ChromeAnimation<Animatable<?>>();
|
| + mLayoutAnimations.start();
|
| + }
|
| + component.start();
|
| + mLayoutAnimations.add(component);
|
| + requestUpdate();
|
| + }
|
| +
|
| + /**
|
| + * @return whether or not the animation is currently being run.
|
| + */
|
| + protected boolean animationIsRunning() {
|
| + return mLayoutAnimations != null && !mLayoutAnimations.finished();
|
| + }
|
| +
|
| + /**
|
| + * Cancels any animation for the given object and property.
|
| + * @param object The object being animated.
|
| + * @param prop The property to search for.
|
| + */
|
| + protected <T extends Enum<?>> void cancelAnimation(Animatable<T> object, T prop) {
|
| + if (mLayoutAnimations != null) {
|
| + mLayoutAnimations.cancel(object, prop);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * @return The {@link LayoutTab}s to be drawn.
|
| + */
|
| + @SuppressFBWarnings("EI_EXPOSE_REP")
|
| + public LayoutTab[] getLayoutTabsToRender() {
|
| + return mLayoutTabs;
|
| + }
|
| +
|
| + /**
|
| + * @param id The id of the {@link LayoutTab} to search for.
|
| + * @return A {@link LayoutTab} represented by a {@link Tab} with an id of {@code id}.
|
| + */
|
| + public LayoutTab getLayoutTab(int id) {
|
| + if (mLayoutTabs != null) {
|
| + for (int i = 0; i < mLayoutTabs.length; i++) {
|
| + if (mLayoutTabs[i].getId() == id) return mLayoutTabs[i];
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the layout is handling the model updates when a tab is closing.
|
| + */
|
| + public boolean handlesTabClosing() {
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the layout is handling the model updates when a tab is creating.
|
| + */
|
| + public boolean handlesTabCreating() {
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the layout is handling the model updates when closing all the tabs.
|
| + */
|
| + public boolean handlesCloseAll() {
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @return True if the content decoration layer should be shown.
|
| + */
|
| + public boolean shouldDisplayContentOverlay() {
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @return True if the currently active content view is shown in the normal interactive mode.
|
| + */
|
| + public boolean isTabInteractive() {
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @return The number of drawn tabs. This does not count the one culled out.
|
| + */
|
| + @VisibleForTesting
|
| + public int getLayoutTabsDrawnCount() {
|
| + return mRenderHost.getLayoutTabsDrawnCount();
|
| + }
|
| +
|
| + /**
|
| + * Setting this will only take effect the next time this layout is shown. If it is currently
|
| + * showing the original filter will still be used.
|
| + * @param filter
|
| + */
|
| + public void setEventFilter(EventFilter filter) {
|
| + mEventFilter = filter;
|
| + }
|
| +
|
| + /**
|
| + * @param e The {@link MotionEvent} to consider.
|
| + * @param offsets The current touch offsets that should be applied to the
|
| + * {@link EventFilter}s.
|
| + * @param isKeyboardShowing Whether or not the keyboard is showing.
|
| + * @return The {@link EventFilter} the {@link Layout} is listening to.
|
| + */
|
| + public EventFilter findInterceptingEventFilter(
|
| + MotionEvent e, Point offsets, boolean isKeyboardShowing) {
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + EventFilter eventFilter = mSceneOverlays.get(i).getEventFilter();
|
| + if (offsets != null) eventFilter.setCurrentMotionEventOffsets(offsets.x, offsets.y);
|
| + if (eventFilter.onInterceptTouchEvent(e, isKeyboardShowing)) return eventFilter;
|
| + }
|
| +
|
| + if (mEventFilter != null) {
|
| + if (offsets != null) mEventFilter.setCurrentMotionEventOffsets(offsets.x, offsets.y);
|
| + if (mEventFilter.onInterceptTouchEvent(e, isKeyboardShowing)) return mEventFilter;
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * Build a {@link SceneLayer} if it hasn't already been built, and update it and return it.
|
| + *
|
| + * @param contentViewport A viewport in which to display content.
|
| + * @param layerTitleCache A layer title cache.
|
| + * @param tabContentManager A tab content manager.
|
| + * @param resourceManager A resource manager.
|
| + * @param fullscreenManager A fullscreen manager.
|
| + * @return A {@link SceneLayer} that represents the content for this
|
| + * {@link Layout}.
|
| + */
|
| + public final SceneLayer getUpdatedSceneLayer(Rect viewport,
|
| + Rect contentViewport, LayerTitleCache layerTitleCache,
|
| + TabContentManager tabContentManager, ResourceManager resourceManager,
|
| + ChromeFullscreenManager fullscreenManager) {
|
| + updateSceneLayer(viewport, contentViewport, layerTitleCache, tabContentManager,
|
| + resourceManager, fullscreenManager);
|
| +
|
| + SceneLayer content = getSceneLayer();
|
| + for (int i = 0; i < mSceneOverlays.size(); i++) {
|
| + SceneOverlayLayer overlayLayer = mSceneOverlays.get(i).getUpdatedSceneOverlayTree(
|
| + layerTitleCache, resourceManager, fullscreenManager);
|
| +
|
| + overlayLayer.setContentTree(content);
|
| + content = overlayLayer;
|
| + }
|
| +
|
| + return content;
|
| + }
|
| +
|
| + /**
|
| + * Get an instance of {@link SceneLayer}. Any class inheriting {@link Layout}
|
| + * should override this function in order for other functions to work.
|
| + *
|
| + * @return The scene layer for this {@link Layout}.
|
| + */
|
| + protected abstract SceneLayer getSceneLayer();
|
| +
|
| + /**
|
| + * Update {@link SceneLayer} instance this layout holds. Any class inheriting {@link Layout}
|
| + * should override this function in order for other functions to work.
|
| + */
|
| + protected void updateSceneLayer(Rect viewport, Rect contentViewport,
|
| + LayerTitleCache layerTitleCache, TabContentManager tabContentManager,
|
| + ResourceManager resourceManager, ChromeFullscreenManager fullscreenManager) {
|
| + }
|
| +}
|
|
|