From 066e4e3043e05f5dadc4e9c00469c28afa198b2c Mon Sep 17 00:00:00 2001 From: wesj Date: Thu, 29 Jan 2015 10:43:40 -0800 Subject: [PATCH] Bug 909434 - Add ability to drag urlbar, preffed off. r=wesj --- mobile/android/base/AppConstants.java.in | 3 + mobile/android/base/BrowserApp.java | 185 ++++++++++++- mobile/android/base/GeckoApp.java | 18 +- mobile/android/base/LayoutInterceptor.java | 11 + mobile/android/base/OuterLayout.java | 254 ++++++++++++++++++ mobile/android/base/moz.build | 2 + .../base/resources/layout/gecko_app.xml | 4 +- .../android/base/resources/values/dimens.xml | 2 + mobile/android/base/tabs/TabsPanel.java | 107 ++++++-- .../android/base/toolbar/BrowserToolbar.java | 27 +- .../base/toolbar/BrowserToolbarNewTablet.java | 6 + mobile/android/base/widget/ButtonToast.java | 4 + 12 files changed, 587 insertions(+), 36 deletions(-) create mode 100644 mobile/android/base/LayoutInterceptor.java create mode 100644 mobile/android/base/OuterLayout.java diff --git a/mobile/android/base/AppConstants.java.in b/mobile/android/base/AppConstants.java.in index a2ff4cdebaa..577ae233a6d 100644 --- a/mobile/android/base/AppConstants.java.in +++ b/mobile/android/base/AppConstants.java.in @@ -287,4 +287,7 @@ public class AppConstants { //#else false; //#endif + + public static final boolean MOZ_DRAGGABLE_URLBAR = false; + } diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index b12ceb23516..114c27239da 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -259,6 +259,175 @@ public class BrowserApp extends GeckoApp private final DynamicToolbar mDynamicToolbar = new DynamicToolbar(); + private DragHelper mDragHelper; + + private class DragHelper implements OuterLayout.DragCallback { + private int[] mToolbarLocation = new int[2]; // to avoid creation every time we need to check for toolbar location. + // When dragging horizontally, the area of mainlayout between left drag bound and right drag bound can + // be dragged. A touch on the right of that area will automatically close the view. + private int mStatusBarHeight; + + public DragHelper() { + // If a layout round happens from the root, the offset placed by viewdraghelper gets forgotten and + // main layout gets replaced to offset 0. + ((MainLayout) mMainLayout).setLayoutInterceptor(new LayoutInterceptor() { + @Override + public void onLayout() { + if (mRootLayout.isMoving()) { + mRootLayout.restoreTargetViewPosition(); + } + } + }); + } + + @Override + public void onDragProgress(float progress) { + mBrowserToolbar.setToolBarButtonsAlpha(1.0f - progress); + mTabsPanel.translateInRange(progress); + } + + @Override + public View getViewToDrag() { + return mMainLayout; + } + + /** + * Since pressing the tabs button slides the main layout, whereas draghelper changes its offset, here we + * restore the position of mainlayout as if it was opened by pressing the button. This allows the closing + * mechanism to work. + */ + @Override + public void startDrag(boolean wasOpen) { + if (wasOpen) { + mTabsPanel.setHWLayerEnabled(true); + mMainLayout.offsetTopAndBottom(getDragRange()); + mMainLayout.scrollTo(0, 0); + } else { + prepareTabsToShow(); + mBrowserToolbar.hideVirtualKeyboard(); + } + mBrowserToolbar.setContextMenuEnabled(false); + } + + @Override + public void stopDrag(boolean stoppingToOpen) { + if (stoppingToOpen) { + mTabsPanel.setHWLayerEnabled(false); + mMainLayout.offsetTopAndBottom(-getDragRange()); + mMainLayout.scrollTo(0, -getDragRange()); + } else { + mTabsPanel.hideImmediately(); + mTabsPanel.setHWLayerEnabled(false); + } + // Re-enabling context menu only while stopping to close. + if (stoppingToOpen) { + mBrowserToolbar.setContextMenuEnabled(false); + } else { + mBrowserToolbar.setContextMenuEnabled(true); + } + } + + @Override + public int getDragRange() { + return mTabsPanel.getVerticalPanelHeight(); + } + + @Override + public int getOrderedChildIndex(int index) { + // See ViewDragHelper's findTopChildUnder method. ViewDragHelper looks for the topmost view in z order + // to understand what needs to be dragged. Here we are tampering Toast's index in case it's hidden, + // otherwise draghelper would try to drag it. + int mainLayoutIndex = mRootLayout.indexOfChild(mMainLayout); + if (index > mainLayoutIndex && (mToast == null || !mToast.isVisible())) { + return mainLayoutIndex; + } else { + return index; + } + } + + @Override + public boolean canDrag(MotionEvent event) { + if (!AppConstants.MOZ_DRAGGABLE_URLBAR) { + return false; + } + + // if no current tab is active. + if (Tabs.getInstance().getSelectedTab() == null) { + return false; + } + + // currently disabled for tablets. + if (HardwareUtils.isTablet()) { + return false; + } + + // not enabled in editing mode. + if (mBrowserToolbar.isEditing()) { + return false; + } + + return isInToolbarBounds((int) event.getRawY()); + } + + @Override + public boolean canInterceptEventWhileOpen(MotionEvent event) { + if (event.getActionMasked() != MotionEvent.ACTION_DOWN) { + return false; + } + + // Need to check if are intercepting a touch on main layout since we might hit a visible toast. + if (mRootLayout.findTopChildUnder(event) == mMainLayout && + isInToolbarBounds((int) event.getRawY())) { + return true; + } + return false; + } + + private boolean isInToolbarBounds(int y) { + mBrowserToolbar.getLocationOnScreen(mToolbarLocation); + final int upperLimit = mToolbarLocation[1] + mBrowserToolbar.getMeasuredHeight(); + final int lowerLimit = mToolbarLocation[1]; + return (y > lowerLimit && y < upperLimit); + } + + public void prepareTabsToShow() { + if (ensureTabsPanelExists()) { + // If we've just inflated the tabs panel, only show it once the current + // layout pass is done to avoid displayed temporary UI states during + // relayout. + final ViewTreeObserver vto = mTabsPanel.getViewTreeObserver(); + if (vto.isAlive()) { + vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + mTabsPanel.getViewTreeObserver().removeGlobalOnLayoutListener(this); + prepareTabsToShow(); + } + }); + } + } else { + mTabsPanel.prepareToDrag(); + } + } + + public int getLowerLimit() { + return getStatusBarHeight(); + } + + private int getStatusBarHeight() { + if (mStatusBarHeight != 0) { + return mStatusBarHeight; + } + final int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + mStatusBarHeight = getResources().getDimensionPixelSize(resourceId); + return mStatusBarHeight; + } + Log.e(LOGTAG, "Unable to find statusbar height"); + return 0; + } + } + @Override public View onCreateView(final String name, final Context context, final AttributeSet attrs) { final View view; @@ -658,6 +827,9 @@ public class BrowserApp extends GeckoApp } }); + mDragHelper = new DragHelper(); + mRootLayout.setDraggableCallback(mDragHelper); + // Set the maximum bits-per-pixel the favicon system cares about. IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth()); } @@ -1358,6 +1530,7 @@ public class BrowserApp extends GeckoApp invalidateOptionsMenu(); if (mTabsPanel != null) { + mRootLayout.reset(); updateSideBarState(); mTabsPanel.refresh(); } @@ -1381,6 +1554,10 @@ public class BrowserApp extends GeckoApp }); } + private boolean isSideBar() { + return (HardwareUtils.isTablet() && getOrientation() == Configuration.ORIENTATION_LANDSCAPE); + } + private void updateSideBarState() { if (NewTabletUI.isEnabled(this)) { return; @@ -1389,7 +1566,7 @@ public class BrowserApp extends GeckoApp if (mMainLayoutAnimator != null) mMainLayoutAnimator.stop(); - boolean isSideBar = (HardwareUtils.isTablet() && getOrientation() == Configuration.ORIENTATION_LANDSCAPE); + boolean isSideBar = isSideBar(); final int sidebarWidth = getResources().getDimensionPixelSize(R.dimen.tabs_sidebar_width); ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mTabsPanel.getLayoutParams(); @@ -1401,6 +1578,7 @@ public class BrowserApp extends GeckoApp mMainLayout.scrollTo(mainLayoutScrollX, 0); mTabsPanel.setIsSideBar(isSideBar); + mRootLayout.updateDragHelperParameters(); } @Override @@ -1732,7 +1910,7 @@ public class BrowserApp extends GeckoApp @Override public void onGlobalLayout() { mTabsPanel.getViewTreeObserver().removeGlobalOnLayoutListener(this); - mTabsPanel.show(panel); + showTabs(panel); } }); } @@ -1821,10 +1999,13 @@ public class BrowserApp extends GeckoApp if (!areTabsShown()) { mTabsPanel.setVisibility(View.INVISIBLE); mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + mRootLayout.setClosed(); + mBrowserToolbar.setContextMenuEnabled(true); } else { // Cancel editing mode to return to page content when the TabsPanel closes. We cancel // it here because there are graphical glitches if it's canceled while it's visible. mBrowserToolbar.cancelEdit(); + mRootLayout.setOpen(); } mTabsPanel.finishTabsAnimation(); diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 1edaad086ec..8f0947df81d 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -161,8 +161,9 @@ public abstract class GeckoApp // after a version upgrade. private static final int CLEANUP_DEFERRAL_SECONDS = 15; - protected RelativeLayout mRootLayout; + protected OuterLayout mRootLayout; protected RelativeLayout mMainLayout; + protected RelativeLayout mGeckoLayout; private View mCameraView; private OrientationEventListener mCameraOrientationEventListener; @@ -1267,7 +1268,7 @@ public abstract class GeckoApp setContentView(getLayout()); // Set up Gecko layout. - mRootLayout = (RelativeLayout) findViewById(R.id.root_layout); + mRootLayout = (OuterLayout) findViewById(R.id.root_layout); mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout); mMainLayout = (RelativeLayout) findViewById(R.id.main_layout); @@ -2393,11 +2394,24 @@ public abstract class GeckoApp public static class MainLayout extends RelativeLayout { private TouchEventInterceptor mTouchEventInterceptor; private MotionEventInterceptor mMotionEventInterceptor; + private LayoutInterceptor mLayoutInterceptor; public MainLayout(Context context, AttributeSet attrs) { super(context, attrs); } + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (mLayoutInterceptor != null) { + mLayoutInterceptor.onLayout(); + } + } + + public void setLayoutInterceptor(LayoutInterceptor interceptor) { + mLayoutInterceptor = interceptor; + } + public void setTouchEventInterceptor(TouchEventInterceptor interceptor) { mTouchEventInterceptor = interceptor; } diff --git a/mobile/android/base/LayoutInterceptor.java b/mobile/android/base/LayoutInterceptor.java new file mode 100644 index 00000000000..be80ea57521 --- /dev/null +++ b/mobile/android/base/LayoutInterceptor.java @@ -0,0 +1,11 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +package org.mozilla.gecko; + +public interface LayoutInterceptor { + public void onLayout(); +} diff --git a/mobile/android/base/OuterLayout.java b/mobile/android/base/OuterLayout.java new file mode 100644 index 00000000000..d8b0e8d302b --- /dev/null +++ b/mobile/android/base/OuterLayout.java @@ -0,0 +1,254 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +package org.mozilla.gecko; + +import android.content.Context; +import android.support.v4.view.ViewCompat; +import android.support.v4.widget.ViewDragHelper; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.RelativeLayout; + +/* Outerlayout is the container layout of all the main views. It allows mainlayout to be dragged while targeting + the toolbar and it's responsible for handling the dragprocess. It relies on ViewDragHelper to ease the drag process. + */ +public class OuterLayout extends RelativeLayout { + private final double AUTO_OPEN_SPEED_LIMIT = 800.0; + private ViewDragHelper mDragHelper; + private int mDraggingBorder; + private int mDragRange; + private boolean mIsOpen = false; + private int mDraggingState = ViewDragHelper.STATE_IDLE; + private DragCallback mDragCallback; + + public static interface DragCallback { + public void startDrag(boolean wasOpen); + public void stopDrag(boolean stoppingToOpen); + public int getDragRange(); + public int getOrderedChildIndex(int index); + public boolean canDrag(MotionEvent event); + public boolean canInterceptEventWhileOpen(MotionEvent event); + public void onDragProgress(float progress); + public View getViewToDrag(); + public int getLowerLimit(); + } + + private class DragHelperCallback extends ViewDragHelper.Callback { + @Override + public void onViewDragStateChanged(int newState) { + if (newState == mDraggingState) { // no change + return; + } + + // if the view stopped moving. + if ((mDraggingState == ViewDragHelper.STATE_DRAGGING || mDraggingState == ViewDragHelper.STATE_SETTLING) && + newState == ViewDragHelper.STATE_IDLE) { + + final float rangeToCheck = mDragRange; + final float lowerLimit = mDragCallback.getLowerLimit(); + if (mDraggingBorder == lowerLimit) { + mIsOpen = false; + mDragCallback.onDragProgress(0); + } else if (mDraggingBorder == rangeToCheck) { + mIsOpen = true; + mDragCallback.onDragProgress(1); + } + mDragCallback.stopDrag(mIsOpen); + } + + // The view was previuosly moving. + if (newState == ViewDragHelper.STATE_DRAGGING && !isMoving()) { + mDragCallback.startDrag(mIsOpen); + updateRanges(); + } + + mDraggingState = newState; + } + + @Override + public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { + mDraggingBorder = top; + final float progress = Math.min(1, ((float) top) / mDragRange); + mDragCallback.onDragProgress(progress); + } + + @Override + public int getViewVerticalDragRange(View child) { + return mDragRange; + } + + @Override + public int getOrderedChildIndex(int index) { + return mDragCallback.getOrderedChildIndex(index); + } + + @Override + public boolean tryCaptureView(View view, int i) { + return (view.getId() == mDragCallback.getViewToDrag().getId()); + } + + @Override + public int clampViewPositionVertical(View child, int top, int dy) { + return top; + } + + @Override + public void onViewReleased(View releasedChild, float xvel, float yvel) { + final float rangeToCheck = mDragRange; + final float speedToCheck = yvel; + + if (mDraggingBorder == mDragCallback.getLowerLimit()) { + return; + } + + if (mDraggingBorder == rangeToCheck) { + return; + } + + boolean settleToOpen = false; + // Speed has priority over position. + if (speedToCheck > AUTO_OPEN_SPEED_LIMIT) { + settleToOpen = true; + } else if (speedToCheck < -AUTO_OPEN_SPEED_LIMIT) { + settleToOpen = false; + } else if (mDraggingBorder > rangeToCheck / 2) { + settleToOpen = true; + } else if (mDraggingBorder < rangeToCheck / 2) { + settleToOpen = false; + } + + final int settleDestX; + final int settleDestY; + if (settleToOpen) { + settleDestX = 0; + settleDestY = mDragRange; + } else { + settleDestX = 0; + settleDestY = mDragCallback.getLowerLimit(); + } + + if(mDragHelper.settleCapturedViewAt(settleDestX, settleDestY)) { + ViewCompat.postInvalidateOnAnimation(OuterLayout.this); + } + } + } + + public OuterLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + private void updateRanges() { + // Need to wait for the tabs to show in order to fetch the right sizes. + mDragRange = mDragCallback.getDragRange() + mDragCallback.getLowerLimit(); + } + + private void updateOrientation() { + mDragHelper.setEdgeTrackingEnabled(0); + } + + @Override + protected void onFinishInflate() { + mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback()); + mIsOpen = false; + super.onFinishInflate(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (mDragCallback.canDrag(event)) { + if (mDragHelper.shouldInterceptTouchEvent(event)) { + return true; + } + } + + // Because while open the target layout is translated and draghelper does not catch it. + if (mIsOpen && mDragCallback.canInterceptEventWhileOpen(event)) { + return true; + } + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // touch events can be passed to the helper if we target the toolbar or we are already dragging. + if (mDragCallback.canDrag(event) || mDraggingState == ViewDragHelper.STATE_DRAGGING) { + mDragHelper.processTouchEvent(event); + } + return true; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + // The first time fennec is started, tabs might not have been created while we drag. In that case we need + // an arbitrary range to start dragging that will be updated as soon as the tabs are created. + + if (mDragRange == 0) { + mDragRange = h / 2; + } + super.onSizeChanged(w, h, oldw, oldh); + } + + @Override + public void computeScroll() { // needed for automatic settling. + if (mDragHelper.continueSettling(true)) { + ViewCompat.postInvalidateOnAnimation(this); + } + } + + /** + * To be called when closing the tabs from outside (i.e. when touching the main layout). + */ + public void setClosed() { + mIsOpen = false; + mDragHelper.abort(); + } + + /** + * To be called when opening the tabs from outside (i.e. when clicking on the tabs button). + */ + public void setOpen() { + mIsOpen = true; + mDragHelper.abort(); + } + + public void setDraggableCallback(DragCallback dragCallback) { + mDragCallback = dragCallback; + updateOrientation(); + } + + // If a change happens while we are dragging, we abort the dragging and set to open state. + public void reset() { + updateOrientation(); + if (isMoving()) { + mDragHelper.abort(); + if (mDragCallback != null) { + mDragCallback.stopDrag(false); + mDragCallback.onDragProgress(0f); + } + } + } + + public void updateDragHelperParameters() { + mDragRange = mDragCallback.getDragRange() + mDragCallback.getLowerLimit(); + updateOrientation(); + } + + public boolean isMoving() { + return (mDraggingState == ViewDragHelper.STATE_DRAGGING || + mDraggingState == ViewDragHelper.STATE_SETTLING); + } + + public boolean isOpen() { + return mIsOpen; + } + + public View findTopChildUnder(MotionEvent event) { + return mDragHelper.findTopChildUnder((int) event.getX(), (int) event.getY()); + } + + public void restoreTargetViewPosition() { + mDragCallback.getViewToDrag().offsetTopAndBottom(mDraggingBorder); + } +} diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index 90449ae735e..765d9b98c8e 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -333,6 +333,7 @@ gbjar.sources += [ 'InputMethods.java', 'IntentHelper.java', 'JavaAddonManager.java', + 'LayoutInterceptor.java', 'LocaleManager.java', 'Locales.java', 'lwt/LightweightTheme.java', @@ -356,6 +357,7 @@ gbjar.sources += [ 'NotificationService.java', 'NSSBridge.java', 'OrderedBroadcastHelper.java', + 'OuterLayout.java', 'preferences/AlignRightLinkPreference.java', 'preferences/AndroidImport.java', 'preferences/AndroidImportPreference.java', diff --git a/mobile/android/base/resources/layout/gecko_app.xml b/mobile/android/base/resources/layout/gecko_app.xml index 8f5e552ad22..49edbacae7d 100644 --- a/mobile/android/base/resources/layout/gecko_app.xml +++ b/mobile/android/base/resources/layout/gecko_app.xml @@ -3,7 +3,7 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - - + diff --git a/mobile/android/base/resources/values/dimens.xml b/mobile/android/base/resources/values/dimens.xml index 1cfff1b83cd..8a1a32fe82a 100644 --- a/mobile/android/base/resources/values/dimens.xml +++ b/mobile/android/base/resources/values/dimens.xml @@ -192,6 +192,8 @@ 2dp 2dp + 256dp + 5dip 12dip diff --git a/mobile/android/base/tabs/TabsPanel.java b/mobile/android/base/tabs/TabsPanel.java index 6977b4674a2..950dd1009c0 100644 --- a/mobile/android/base/tabs/TabsPanel.java +++ b/mobile/android/base/tabs/TabsPanel.java @@ -14,6 +14,8 @@ import org.mozilla.gecko.R; import org.mozilla.gecko.Telemetry; import org.mozilla.gecko.TelemetryContract; import org.mozilla.gecko.animation.PropertyAnimator; +import org.mozilla.gecko.Tab; +import org.mozilla.gecko.Tabs; import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.lwt.LightweightTheme; import org.mozilla.gecko.lwt.LightweightThemeDrawable; @@ -393,15 +395,40 @@ public class TabsPanel extends LinearLayout } public void show(Panel panelToShow) { - if (!isShown()) + final boolean showAnimation = !mVisible; + prepareToShow(panelToShow); + if (isSideBar()) { + if (showAnimation) { + dispatchLayoutChange(getWidth(), getHeight()); + } + } else { + int height = getVerticalPanelHeight(); + dispatchLayoutChange(getWidth(), height); + } + } + + public void prepareToDrag() { + Tab selectedTab = Tabs.getInstance().getSelectedTab(); + if (selectedTab != null && selectedTab.isPrivate()) { + prepareToShow(TabsPanel.Panel.PRIVATE_TABS); + } else { + prepareToShow(TabsPanel.Panel.NORMAL_TABS); + } + if (mIsSideBar) { + prepareSidebarAnimation(getWidth()); + } + } + + public void prepareToShow(Panel panelToShow) { + if (!isShown()) { setVisibility(View.VISIBLE); + } if (mPanel != null) { // Hide the old panel. mPanel.hide(); } - final boolean showAnimation = !mVisible; mVisible = true; mCurrentPanel = panelToShow; @@ -431,20 +458,20 @@ public class TabsPanel extends LinearLayout if (!HardwareUtils.hasMenuButton()) { mMenuButton.setVisibility(View.VISIBLE); mMenuButton.setEnabled(true); - mPopupMenu.setAnchor(mMenuButton); } else { mPopupMenu.setAnchor(mAddTab); } + } - if (isSideBar()) { - if (showAnimation) - dispatchLayoutChange(getWidth(), getHeight()); - } else { - int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height); - int height = actionBarHeight + getTabContainerHeight(mTabsContainer); - dispatchLayoutChange(getWidth(), height); - } - mHeaderVisible = true; + public void hideImmediately() { + mVisible = false; + setVisibility(View.INVISIBLE); + } + + public int getVerticalPanelHeight() { + final int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height); + final int height = actionBarHeight + getTabContainerHeight(mTabsContainer); + return height; } public void hide() { @@ -488,6 +515,28 @@ public class TabsPanel extends LinearLayout return mCurrentPanel; } + public void setHWLayerEnabled(boolean enabled) { + if (Versions.preHC) { + return; + } + if (enabled) { + mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } else { + mHeader.setLayerType(View.LAYER_TYPE_NONE, null); + mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null); + } + } + + public void prepareSidebarAnimation(int tabsPanelWidth) { + if (mVisible) { + ViewHelper.setTranslationX(mHeader, -tabsPanelWidth); + ViewHelper.setTranslationX(mTabsContainer, -tabsPanelWidth); + // The footer view is only present on the sidebar, v11+. + ViewHelper.setTranslationX(mFooter, -tabsPanelWidth); + } + } + public void prepareTabsAnimation(PropertyAnimator animator) { // Not worth doing this on pre-Honeycomb without proper // hardware accelerated animations. @@ -497,13 +546,7 @@ public class TabsPanel extends LinearLayout if (mIsSideBar) { final int tabsPanelWidth = getWidth(); - if (mVisible) { - ViewHelper.setTranslationX(mHeader, -tabsPanelWidth); - ViewHelper.setTranslationX(mTabsContainer, -tabsPanelWidth); - - // The footer view is only present on the sidebar, v11+. - ViewHelper.setTranslationX(mFooter, -tabsPanelWidth); - } + prepareSidebarAnimation(tabsPanelWidth); final int translationX = (mVisible ? 0 : -tabsPanelWidth); animator.attach(mTabsContainer, PropertyAnimator.Property.TRANSLATION_X, translationX); animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_X, translationX); @@ -523,8 +566,25 @@ public class TabsPanel extends LinearLayout animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_Y, translationY); } - mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null); + setHWLayerEnabled(true); + } + + public void translateInRange(float progress) { + final Resources resources = getContext().getResources(); + if (!mIsSideBar) { + final int toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height); + final int translationY = (int) - ((1 - progress) * toolbarHeight); + ViewHelper.setTranslationY(mHeader, translationY); + ViewHelper.setTranslationY(mTabsContainer, translationY); + mTabsContainer.setAlpha(progress); + } else { + final int tabsPanelWidth = getWidth(); + prepareSidebarAnimation(tabsPanelWidth); + final int translationX = (int) - ((1 - progress) * tabsPanelWidth); + ViewHelper.setTranslationX(mHeader, translationX); + ViewHelper.setTranslationX(mTabsContainer, translationX); + ViewHelper.setTranslationX(mFooter, translationX); + } } public void finishTabsAnimation() { @@ -532,10 +592,9 @@ public class TabsPanel extends LinearLayout return; } - mHeader.setLayerType(View.LAYER_TYPE_NONE, null); - mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null); + setHWLayerEnabled(false); - // If the tabs panel is now hidden, call hide() on current panel and unset it as the current panel + // If the tray is now hidden, call hide() on current panel and unset it as the current panel // to avoid hide() being called again when the layout is opened next. if (!mVisible && mPanel != null) { mPanel.hide(); diff --git a/mobile/android/base/toolbar/BrowserToolbar.java b/mobile/android/base/toolbar/BrowserToolbar.java index 8ed1d65f433..c2128847eb5 100644 --- a/mobile/android/base/toolbar/BrowserToolbar.java +++ b/mobile/android/base/toolbar/BrowserToolbar.java @@ -140,6 +140,7 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout private final int shadowSize; private final ToolbarPrefs prefs; + private boolean contextMenuEnabled = true; public abstract boolean isAnimating(); @@ -243,8 +244,8 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - // We don't the context menu while editing - if (isEditing()) { + // We don't the context menu while editing or while dragging + if (isEditing() || !contextMenuEnabled) { return; } @@ -569,16 +570,19 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout menuButton.setNextFocusDownId(nextId); } + public void hideVirtualKeyboard() { + InputMethodManager imm = + (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(tabsButton.getWindowToken(), 0); + } + private void toggleTabs() { if (activity.areTabsShown()) { if (activity.hasTabsSideBar()) activity.hideTabs(); } else { - // hide the virtual keyboard - InputMethodManager imm = - (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(tabsButton.getWindowToken(), 0); + hideVirtualKeyboard(); Tab tab = Tabs.getInstance().getSelectedTab(); if (tab != null) { if (!tab.isPrivate()) @@ -673,6 +677,13 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout } } + public void setToolBarButtonsAlpha(float alpha) { + ViewHelper.setAlpha(tabsCounter, alpha); + if (hasSoftMenuButton && !HardwareUtils.isTablet()) { + ViewHelper.setAlpha(menuIcon, alpha); + } + } + public void onEditSuggestion(String suggestion) { if (!isEditing()) { return; @@ -948,6 +959,10 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout return drawable; } + public void setContextMenuEnabled(boolean enabled) { + contextMenuEnabled = enabled; + } + public static class TabEditingState { // The edited text from the most recent time this tab was unselected. protected String lastEditingText; diff --git a/mobile/android/base/toolbar/BrowserToolbarNewTablet.java b/mobile/android/base/toolbar/BrowserToolbarNewTablet.java index 06f06c9051f..101d5497ab5 100644 --- a/mobile/android/base/toolbar/BrowserToolbarNewTablet.java +++ b/mobile/android/base/toolbar/BrowserToolbarNewTablet.java @@ -172,6 +172,12 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase { // Do nothing. } + @Override + public void setToolBarButtonsAlpha(float alpha) { + // Do nothing; + } + + @Override public void startEditing(final String url, final PropertyAnimator animator) { // We already know the forward button state - no need to store it here. diff --git a/mobile/android/base/widget/ButtonToast.java b/mobile/android/base/widget/ButtonToast.java index e402040eee9..2ff3fb6e043 100644 --- a/mobile/android/base/widget/ButtonToast.java +++ b/mobile/android/base/widget/ButtonToast.java @@ -173,4 +173,8 @@ public class ButtonToast { hide(false, ReasonHidden.TIMEOUT); } }; + + public boolean isVisible() { + return (mView.getVisibility() == View.VISIBLE); + } }