Commit 94de8546 authored by Matthew Jones's avatar Matthew Jones Committed by Commit Bot

Implement simple bottom toolbar with MVC framework

This change implements a simple bottom toolbar that has an android view,
a composited component, resizes the viewport, and scrolls on and
off-screen. This toolbar is implemented using the new MVC guidelines:

- The BottomToolbarController sets up all the components of the bottom
  toolbar.
- The BottomToolbarMediator is responsible for pushing updates to the
  model and running most of the business logic.
- The BottomToolbarModel is a blob of state with no actual business
  logic.
- The BottomToolbarViewBinder reacts to events from a
  PropertyModelChangeProcessor and updates the relevant views.

BUG=815324,825965

Change-Id: I75e5aac6a2bb2a21ea9e4010fc81076d2d39da8b
Reviewed-on: https://chromium-review.googlesource.com/980577
Commit-Queue: Matthew Jones <mdjones@chromium.org>
Reviewed-by: default avatarTheresa <twellington@chromium.org>
Reviewed-by: default avatarBernhard Bauer <bauerb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#548436}
parent 2cc7d67e
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2018 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. -->
<org.chromium.chrome.browser.toolbar.ScrollingBottomViewResourceFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bottom_toolbar_control_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/control_container_height" >
<ImageView
android:id="@+id/bottom_toolbar_top_shadow"
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_shadow_height"
android:src="@drawable/modern_toolbar_shadow"
android:scaleType="fitXY"
android:scaleY="-1"
android:contentDescription="@null" />
<org.chromium.chrome.browser.widget.bottomsheet.TouchRestrictingFrameLayout
android:id="@+id/bottom_toolbar_container"
android:layout_width="match_parent"
android:layout_height="@dimen/control_container_height"
android:layout_marginTop="@dimen/toolbar_shadow_height" >
<View
android:id="@+id/bottom_sheet_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/modern_primary_color" >
</View>
</org.chromium.chrome.browser.widget.bottomsheet.TouchRestrictingFrameLayout>
</org.chromium.chrome.browser.toolbar.ScrollingBottomViewResourceFrameLayout>
......@@ -85,6 +85,14 @@
android:background="@android:color/black"
android:visibility="gone" />
<ViewStub
android:id="@+id/bottom_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|bottom"
android:inflatedId="@+id/bottom_toolbar"
android:layout="@layout/bottom_toolbar" />
<ViewStub
android:id="@+id/control_container_stub"
android:layout_width="match_parent"
......
......@@ -127,6 +127,7 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tabmodel.TabWindowManager;
import org.chromium.chrome.browser.toolbar.BottomToolbarController;
import org.chromium.chrome.browser.toolbar.Toolbar;
import org.chromium.chrome.browser.toolbar.ToolbarControlContainer;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
......@@ -258,6 +259,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
private ToolbarManager mToolbarManager;
private FindToolbarManager mFindToolbarManager;
private BottomSheetController mBottomSheetController;
private BottomToolbarController mBottomToolbarController;
private BottomSheet mBottomSheet;
private ContextualSuggestionsCoordinator mContextualSuggestionsCoordinator;
private FadingBackgroundView mFadingBackgroundView;
......@@ -1146,6 +1148,11 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
mBottomSheet = null;
}
if (mBottomToolbarController != null) {
mBottomToolbarController.destroy();
mBottomToolbarController = null;
}
if (mContextualSuggestionsCoordinator != null) {
mContextualSuggestionsCoordinator.destroy();
mContextualSuggestionsCoordinator = null;
......@@ -1263,6 +1270,13 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
VrShellDelegate.onNativeLibraryAvailable();
super.finishNativeInitialization();
if (FeatureUtilities.isChromeDuplexEnabled()) {
ViewGroup coordinator = findViewById(R.id.coordinator);
mBottomToolbarController = new BottomToolbarController(mFullscreenManager,
mCompositorViewHolder.getResourceManager(),
mCompositorViewHolder.getLayoutManager(), coordinator);
}
if (FeatureUtilities.isContextualSuggestionsBottomSheetEnabled(isTablet())) {
ViewGroup coordinator = (ViewGroup) findViewById(R.id.coordinator);
getLayoutInflater().inflate(R.layout.bottom_sheet, coordinator);
......
......@@ -141,6 +141,16 @@ public abstract class Layout implements TabContentManager.ThumbnailChangeListene
return mUpdateHost.getAnimationHandler();
}
/**
* Adds a {@link SceneOverlay} that can be shown in this layout to the first position in the
* scene overlay list, meaning it will be drawn behind all other overlays.
* @param overlay The {@link SceneOverlay} to be added.
*/
void addSceneOverlayToBack(SceneOverlay overlay) {
assert !mSceneOverlays.contains(overlay);
mSceneOverlays.add(0, overlay);
}
/**
* 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
......
......@@ -902,6 +902,15 @@ public class LayoutManager implements LayoutUpdateHost, LayoutProvider,
mStaticLayout.addSceneOverlay(mContextualSearchPanel);
}
/**
* Add a {@link SceneOverlay} to the back of the list. This means the overlay will be drawn
* first and therefore behind all other overlays currently in the list.
* @param overlay The overlay to be added to the back of the list.
*/
public void addSceneOverlayToBack(SceneOverlay overlay) {
mStaticLayout.addSceneOverlayToBack(overlay);
}
/**
* Clears all content associated with {@code tabId} from the internal caches.
* @param tabId The id of the tab to clear.
......
......@@ -35,6 +35,9 @@ public class ScrollingBottomViewSceneLayer extends SceneOverlayLayer implements
/** The current offset of the bottom view in px. */
private int mCurrentOffsetPx;
/** The {@link ViewResourceFrameLayout} that this scene layer represents. */
private ViewResourceFrameLayout mBottomView;
/**
* Build a composited bottom view layer.
* @param resourceManager A resource manager for dynamic resource creation.
......@@ -43,10 +46,11 @@ public class ScrollingBottomViewSceneLayer extends SceneOverlayLayer implements
*/
public ScrollingBottomViewSceneLayer(ResourceManager resourceManager,
ViewResourceFrameLayout bottomView, int topShadowHeightPx) {
mResourceId = bottomView.getId();
mBottomView = bottomView;
mResourceId = mBottomView.getId();
mTopShadowHeightPx = topShadowHeightPx;
resourceManager.getDynamicResourceLoader().registerResource(
mResourceId, bottomView.getResourceAdapter());
mResourceId, mBottomView.getResourceAdapter());
}
/**
......@@ -82,7 +86,8 @@ public class ScrollingBottomViewSceneLayer extends SceneOverlayLayer implements
@Override
public boolean isSceneOverlayTreeShowing() {
return true;
// If the offset is greater than the toolbar's height, don't draw the layer.
return mCurrentOffsetPx < mBottomView.getHeight() - mTopShadowHeightPx;
}
@Override
......
// Copyright 2018 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.modelutil;
import org.chromium.chrome.browser.modelutil.PropertyObservable.PropertyObserver;
/**
* A model change processor for use with a {@link PropertyObservable} model. The
* {@link PropertyModelChangeProcessor} should be registered as a property observer of the model.
* Internally uses a view binder to bind model properties to the toolbar view.
* @param <M> The {@link PropertyObservable} model.
* @param <V> The view object that is changing.
* @param <P> The property of the view that changed.
*/
public class PropertyModelChangeProcessor<M extends PropertyObservable<P>, V, P>
implements PropertyObserver<P> {
/**
* A generic view binder that associates a view with a model.
* @param <M> The {@link PropertyObservable} model.
* @param <V> The view object that is changing.
* @param <P> The property of the view that changed.
*/
public interface ViewBinder<M, V, P> { void bind(M model, V view, P propertyKey); }
private final V mView;
private final M mModel;
private final ViewBinder<M, V, P> mViewBinder;
/**
* Construct a new PropertyModelChangeProcessor.
* @param model The model containing the data to be bound.
* @param view The view to which data will be bound.
* @param viewBinder A class that binds the model to the view.
*/
public PropertyModelChangeProcessor(M model, V view, ViewBinder<M, V, P> viewBinder) {
mModel = model;
mView = view;
mViewBinder = viewBinder;
}
@Override
public void onPropertyChanged(PropertyObservable<P> source, P propertyKey) {
// TODO(bauerb): Add support for batching and for full model updates.
mViewBinder.bind(mModel, mView, propertyKey);
}
}
\ No newline at end of file
// Copyright 2018 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.toolbar;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
import org.chromium.chrome.browser.compositor.scene_layer.ScrollingBottomViewSceneLayer;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
import org.chromium.chrome.browser.toolbar.BottomToolbarModel.PropertyKey;
import org.chromium.chrome.browser.toolbar.BottomToolbarViewBinder.ViewHolder;
import org.chromium.ui.resources.ResourceManager;
/**
* The controller for the bottom toolbar. This class handles all interactions that the bottom
* toolbar has with the outside world. This class has two primary components, an Android view that
* handles user actions and a composited texture that draws when the controls are being scrolled
* off-screen. The Android version does not draw unless the controls offset is 0.
*/
public class BottomToolbarController {
/** The mediator that handles events from outside the bottom toolbar. */
private BottomToolbarMediator mMediator;
/**
* Build the controller that manages the bottom toolbar.
* @param fullscreenManager A {@link ChromeFullscreenManager} to update the bottom controls
* height for the renderer.
* @param resourceManager A {@link ResourceManager} for loading textures into the compositor.
* @param layoutManager A {@link LayoutManager} to attach overlays to.
* @param root The root {@link ViewGroup} for locating the vies to inflate.
*/
public BottomToolbarController(ChromeFullscreenManager fullscreenManager,
ResourceManager resourceManager, LayoutManager layoutManager, ViewGroup root) {
BottomToolbarModel model = new BottomToolbarModel();
mMediator = new BottomToolbarMediator(model, fullscreenManager, root.getResources());
int shadowHeight =
root.getResources().getDimensionPixelOffset(R.dimen.toolbar_shadow_height);
// This is the Android view component of the views that constitute the bottom toolbar.
View inflatedView = ((ViewStub) root.findViewById(R.id.bottom_toolbar)).inflate();
final ScrollingBottomViewResourceFrameLayout toolbarRoot =
(ScrollingBottomViewResourceFrameLayout) inflatedView;
toolbarRoot.setTopShadowHeight(shadowHeight);
// This is the compositor component of the bottom toolbar views.
final ScrollingBottomViewSceneLayer sceneLayer =
new ScrollingBottomViewSceneLayer(resourceManager, toolbarRoot, shadowHeight);
layoutManager.addSceneOverlayToBack(sceneLayer);
PropertyModelChangeProcessor<BottomToolbarModel, ViewHolder, PropertyKey> processor =
new PropertyModelChangeProcessor<>(model, new ViewHolder(sceneLayer, toolbarRoot),
new BottomToolbarViewBinder());
model.addObserver(processor);
}
/**
* Clean up any state when the bottom toolbar is destroyed.
*/
public void destroy() {
mMediator.destroy();
}
}
// Copyright 2018 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.toolbar;
import android.content.res.Resources;
import android.view.View;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager.FullscreenListener;
/**
* This class is responsible for reacting to events from the outside world, interacting with other
* coordinators, running most of the business logic associated with the bottom toolbar, and updating
* the model accordingly.
*/
class BottomToolbarMediator implements FullscreenListener {
/** The model for the bottom toolbar that holds all of its state. */
private BottomToolbarModel mModel;
/** The fullscreen manager to observe browser controls events. */
private ChromeFullscreenManager mFullscreenManager;
/**
* Build a new mediator that handles events from outside the bottom toolbar.
* @param model The {@link BottomToolbarModel} that holds all the state for the bottom toolbar.
* @param fullscreenManager A {@link ChromeFullscreenManager} for events related to the browser
* controls.
* @param resources Android {@link Resources} to pull dimensions from.
*/
public BottomToolbarMediator(BottomToolbarModel model,
ChromeFullscreenManager fullscreenManager, Resources resources) {
mModel = model;
mFullscreenManager = fullscreenManager;
mFullscreenManager.addListener(this);
// Notify the fullscreen manager that the bottom controls now have a height.
fullscreenManager.setBottomControlsHeight(
resources.getDimensionPixelOffset(R.dimen.control_container_height));
fullscreenManager.updateViewportSize();
}
/**
* Clean up anything that needs to be when the bottom toolbar is destroyed.
*/
public void destroy() {
mFullscreenManager.removeListener(this);
}
@Override
public void onContentOffsetChanged(float offset) {}
@Override
public void onControlsOffsetChanged(float topOffset, float bottomOffset, boolean needsAnimate) {
mModel.setYOffset((int) bottomOffset);
if (bottomOffset > 0) {
mModel.setAndroidViewVisibility(View.INVISIBLE);
} else {
mModel.setAndroidViewVisibility(View.VISIBLE);
}
}
@Override
public void onToggleOverlayVideoMode(boolean enabled) {}
@Override
public void onBottomControlsHeightChanged(int bottomControlsHeight) {}
}
// Copyright 2018 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.toolbar;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
/**
* All of the state for the bottom toolbar, updated by the {@link BottomToolbarController}.
*/
public class BottomToolbarModel extends PropertyObservable<BottomToolbarModel.PropertyKey> {
/** The different properties that can change on the bottom toolbar. */
public static class PropertyKey {
public static final PropertyKey Y_OFFSET = new PropertyKey();
public static final PropertyKey ANDROID_VIEW_VISIBILITY = new PropertyKey();
private PropertyKey() {}
}
/** The Y offset of the view in px. */
private int mYOffsetPx;
/** The visibility of the Android view version of the toolbar. */
private int mAndroidViewVisibility;
/** Default constructor. */
public BottomToolbarModel() {}
/**
* @param offsetPx The current Y offset in px.
*/
public void setYOffset(int offsetPx) {
mYOffsetPx = offsetPx;
notifyPropertyChanged(PropertyKey.Y_OFFSET);
}
/**
* @return The current Y offset in px.
*/
public int getYOffset() {
return mYOffsetPx;
}
/**
* @param visibility The visibility of the Android view version of the toolbar.
*/
public void setAndroidViewVisibility(int visibility) {
mAndroidViewVisibility = visibility;
notifyPropertyChanged(PropertyKey.ANDROID_VIEW_VISIBILITY);
}
/**
* @return The visibility of the Android view version of the toolbar.
*/
public int getAndroidViewVisibility() {
return mAndroidViewVisibility;
}
}
// Copyright 2018 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.toolbar;
import android.view.ViewGroup;
import org.chromium.chrome.browser.compositor.scene_layer.ScrollingBottomViewSceneLayer;
import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
import org.chromium.chrome.browser.toolbar.BottomToolbarModel.PropertyKey;
/**
* This class is responsible for pushing updates to both the Android view and the compositor
* component of the bottom toolbar. These updates are pulled from the {@link BottomToolbarModel}
* when a notification of an update is received.
*/
public class BottomToolbarViewBinder
implements PropertyModelChangeProcessor.ViewBinder<BottomToolbarModel,
BottomToolbarViewBinder.ViewHolder, BottomToolbarModel.PropertyKey> {
/**
* A wrapper class that holds a {@link ViewGroup} (the toolbar view) and a composited layer to
* be used with the {@link BottomToolbarViewBinder}.
*/
public static class ViewHolder {
/** A handle to the composited bottom toolbar layer. */
public final ScrollingBottomViewSceneLayer sceneLayer;
/** A handle to the Android {@link ViewGroup} version of the toolbar. */
public final ViewGroup toolbarRoot;
/**
* @param compositedSceneLayer The composited bottom toolbar view.
* @param toolbarRootView The Android {@link ViewGroup} toolbar.
*/
public ViewHolder(
ScrollingBottomViewSceneLayer compositedSceneLayer, ViewGroup toolbarRootView) {
sceneLayer = compositedSceneLayer;
toolbarRoot = toolbarRootView;
}
}
/**
* Build a binder that handles interaction between the model and the views that make up the
* bottom toolbar.
*/
public BottomToolbarViewBinder() {}
@Override
public final void bind(BottomToolbarModel model, ViewHolder view, PropertyKey propertyKey) {
if (PropertyKey.Y_OFFSET == propertyKey) {
view.sceneLayer.setYOffset(model.getYOffset());
} else if (PropertyKey.ANDROID_VIEW_VISIBILITY == propertyKey) {
view.toolbarRoot.setVisibility(model.getAndroidViewVisibility());
} else {
assert false : "Unhandled property detected in BottomToolbarViewBinder!";
}
}
}
// Copyright 2018 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.toolbar;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.util.AttributeSet;
import org.chromium.chrome.browser.widget.ViewResourceFrameLayout;
import org.chromium.ui.resources.dynamics.ViewResourceAdapter;
/**
* A {@link ViewResourceFrameLayout} that specifically handles redraw of the top shadow of the view
* it represents.
*/
public class ScrollingBottomViewResourceFrameLayout extends ViewResourceFrameLayout {
/** A cached rect to avoid extra allocations. */
private final Rect mCachedRect = new Rect();
/** The height of the shadow sitting above the bottom view in px. */
private int mTopShadowHeightPx;
public ScrollingBottomViewResourceFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected ViewResourceAdapter createResourceAdapter() {
return new ViewResourceAdapter(this) {
@Override
public void onCaptureStart(Canvas canvas, Rect dirtyRect) {
mCachedRect.set(dirtyRect);
if (mCachedRect.intersect(0, 0, getWidth(), mTopShadowHeightPx)) {
canvas.save();
// Clip the canvas to only the section of the dirty rect that contains the top
// shadow of the view.
canvas.clipRect(mCachedRect);
// Clear the shadow so redrawing does not make it progressively darker.
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
canvas.restore();
}
super.onCaptureStart(canvas, dirtyRect);
}
};
}
/**
* @param height The height of the view's top shadow in px.
*/
public void setTopShadowHeight(int height) {
mTopShadowHeightPx = height;
}
}
......@@ -673,6 +673,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/modaldialog/TabModalLifetimeHandler.java",
"java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java",
"java/src/org/chromium/chrome/browser/modelutil/ListObservable.java",
"java/src/org/chromium/chrome/browser/modelutil/PropertyModelChangeProcessor.java",
"java/src/org/chromium/chrome/browser/modelutil/PropertyObservable.java",
"java/src/org/chromium/chrome/browser/modelutil/RecyclerViewModelChangeProcessor.java",
"java/src/org/chromium/chrome/browser/modelutil/RecyclerViewAdapter.java",
......@@ -1271,10 +1272,15 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/tabmodel/document/StorageDelegate.java",
"java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java",
"java/src/org/chromium/chrome/browser/toolbar/ActionModeController.java",
"java/src/org/chromium/chrome/browser/toolbar/BottomToolbarController.java",
"java/src/org/chromium/chrome/browser/toolbar/BottomToolbarMediator.java",
"java/src/org/chromium/chrome/browser/toolbar/BottomToolbarModel.java",
"java/src/org/chromium/chrome/browser/toolbar/BottomToolbarViewBinder.java",
"java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbar.java",
"java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbarAnimationDelegate.java",
"java/src/org/chromium/chrome/browser/toolbar/HomePageButton.java",
"java/src/org/chromium/chrome/browser/toolbar/KeyboardNavigationListener.java",
"java/src/org/chromium/chrome/browser/toolbar/ScrollingBottomViewResourceFrameLayout.java",
"java/src/org/chromium/chrome/browser/toolbar/TabSwitcherCallout.java",
"java/src/org/chromium/chrome/browser/toolbar/TabSwitcherDrawable.java",
"java/src/org/chromium/chrome/browser/toolbar/Toolbar.java",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment