Bug 858969 - Refactor dynamic toolbar so page is offset and not overlapped. r=kats,nrc

Refactor the dynamic toolbar code so that the ownership of various properties
is clearer, and the page is offset by the toolbar instead of being overlapped.
This fixes problems with the scroll origin of the page not corresponding to
the visible origin on the screen.
This commit is contained in:
Chris Lord 2013-04-25 18:47:08 +01:00
parent 706ec32a4e
commit b365c94dfd
30 changed files with 692 additions and 561 deletions

View File

@ -252,6 +252,12 @@ public:
*/
virtual void SetDestinationSurfaceSize(const gfx::IntSize& aSize) = 0;
/**
* Declare an offset to use when rendering layers. This will be ignored when
* rendering to a target instead of the screen.
*/
virtual void SetScreenRenderOffset(const gfx::Point& aOffset) = 0;
/**
* Tell the compositor to actually draw a quad. What to do draw and how it is
* drawn is specified by aEffectChain. aRect is the quad to draw, in user space.

View File

@ -934,10 +934,15 @@ CompositorParent::TransformScrollableLayer(Layer* aLayer, const gfx3DMatrix& aRo
displayPortDevPixels.y += scrollOffsetDevPixels.y;
gfx::Margin fixedLayerMargins(0, 0, 0, 0);
float offsetX = 0, offsetY = 0;
SyncViewportInfo(displayPortDevPixels, 1/rootScaleX, mLayersUpdated,
mScrollOffset, mXScale, mYScale, fixedLayerMargins);
mScrollOffset, mXScale, mYScale, fixedLayerMargins,
offsetX, offsetY);
mLayersUpdated = false;
// Apply the render offset
mLayerManager->GetCompositor()->SetScreenRenderOffset(gfx::Point(offsetX, offsetY));
// Handle transformations for asynchronous panning and zooming. We determine the
// zoom used by Gecko from the transformation set on the root layer, and we
// determine the scroll offset used by Gecko from the frame metrics of the
@ -1091,11 +1096,13 @@ void
CompositorParent::SyncViewportInfo(const nsIntRect& aDisplayPort,
float aDisplayResolution, bool aLayersUpdated,
nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
gfx::Margin& aFixedLayerMargins)
gfx::Margin& aFixedLayerMargins, float& aOffsetX,
float& aOffsetY)
{
#ifdef MOZ_WIDGET_ANDROID
AndroidBridge::Bridge()->SyncViewportInfo(aDisplayPort, aDisplayResolution, aLayersUpdated,
aScrollOffset, aScaleX, aScaleY, aFixedLayerMargins);
aScrollOffset, aScaleX, aScaleY, aFixedLayerMargins,
aOffsetX, aOffsetY);
#endif
}

View File

@ -180,7 +180,7 @@ protected:
virtual void SetPageRect(const gfx::Rect& aCssPageRect);
virtual void SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
gfx::Margin& aFixedLayerMargins);
gfx::Margin& aFixedLayerMargins, float& aOffsetX, float& aOffsetY);
void SetEGLSurfaceSize(int width, int height);
private:

View File

@ -621,6 +621,9 @@ CompositorOGL::PrepareViewport(const gfx::IntSize& aSize,
viewMatrix.Translate(-gfxPoint(1.0, -1.0));
viewMatrix.Scale(2.0f / float(aSize.width), 2.0f / float(aSize.height));
viewMatrix.Scale(1.0f, -1.0f);
if (!mTarget) {
viewMatrix.Translate(gfxPoint(mRenderOffset.x, mRenderOffset.y));
}
viewMatrix = aWorldTransform * viewMatrix;

View File

@ -90,6 +90,10 @@ public:
*/
virtual void SetDestinationSurfaceSize(const gfx::IntSize& aSize) MOZ_OVERRIDE;
virtual void SetScreenRenderOffset(const gfx::Point& aOffset) MOZ_OVERRIDE {
mRenderOffset = aOffset;
}
virtual void MakeCurrent(MakeCurrentFlags aFlags = 0) MOZ_OVERRIDE {
if (mDestroyed) {
NS_WARNING("Call on destroyed layer manager");
@ -141,6 +145,8 @@ private:
/** The size of the surface we are rendering to */
nsIntSize mSurfaceSize;
gfx::Point mRenderOffset;
/** Helper-class used by Initialize **/
class ReadDrawFPSPref MOZ_FINAL : public nsRunnable {
public:

View File

@ -8,6 +8,7 @@ package org.mozilla.gecko;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.gfx.GeckoLayerClient;
import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.PanZoomController;
@ -66,6 +67,7 @@ abstract public class BrowserApp extends GeckoApp
implements TabsPanel.TabsLayoutChangeListener,
PropertyAnimator.PropertyAnimationListener,
View.OnKeyListener,
GeckoLayerClient.OnMetricsChangedListener,
AboutHome.UriLoadListener,
AboutHome.LoadCompleteListener {
private static final String LOGTAG = "GeckoBrowserApp";
@ -113,35 +115,6 @@ abstract public class BrowserApp extends GeckoApp
// We'll ask for feedback after the user launches the app this many times.
private static final int FEEDBACK_LAUNCH_COUNT = 15;
// Variables used for scrolling the toolbar on/off the page;
// A drag has to move this amount multiplied by the height of the toolbar
// before the toolbar will appear or disappear.
private static final float TOOLBAR_MOVEMENT_THRESHOLD = 0.3f;
// Whether the dynamic toolbar pref is enabled.
private boolean mDynamicToolbarEnabled = false;
// The last recorded touch event from onInterceptTouchEvent. These are
// not updated until the movement threshold has been exceeded.
private float mLastTouchX = 0.0f;
private float mLastTouchY = 0.0f;
// Because we can only scroll by integer amounts, we store the fractional
// amounts to be applied here.
private float mToolbarSubpixelAccumulation = 0.0f;
// Used by onInterceptTouchEvent to lock the toolbar into an off or on
// position.
private boolean mToolbarLocked = false;
// Whether the toolbar movement threshold has been passed by the current
// drag.
private boolean mToolbarThresholdPassed = false;
// Toggled when the tabs tray is made visible to disable toolbar movement.
private boolean mToolbarPinned = false;
// Stored value of the toolbar height, so we know when it's changed.
private int mToolbarHeight = 0;
@ -166,7 +139,7 @@ abstract public class BrowserApp extends GeckoApp
if (isDynamicToolbarEnabled()) {
// Show the toolbar.
mBrowserToolbar.animateVisibility(true);
mLayerView.getLayerMarginsAnimator().showMargins(false);
}
} else {
hideAboutHome();
@ -195,7 +168,7 @@ abstract public class BrowserApp extends GeckoApp
if (isDynamicToolbarEnabled()) {
// Show the toolbar.
mBrowserToolbar.animateVisibility(true);
mLayerView.getLayerMarginsAnimator().showMargins(false);
}
}
break;
@ -228,134 +201,6 @@ abstract public class BrowserApp extends GeckoApp
updateAboutHomeTopSites();
}
@Override
public boolean onInterceptTouchEvent(View view, MotionEvent event) {
if (!isDynamicToolbarEnabled() || mToolbarPinned) {
return super.onInterceptTouchEvent(view, event);
}
// Don't let the toolbar scroll at all if the page is shorter than
// the screen height.
ImmutableViewportMetrics metrics =
mLayerView.getLayerClient().getViewportMetrics();
if (metrics.getPageHeight() < metrics.getHeight()) {
return super.onInterceptTouchEvent(view, event);
}
int action = event.getActionMasked();
int pointerCount = event.getPointerCount();
// Whenever there are no pointers left on the screen, tell the page
// to clamp the viewport on fixed layer margin changes. This lets the
// toolbar scrolling off the top of the page make the page scroll up
// if it'd cause the page to go into overscroll, but only when there
// are no pointers held down.
mLayerView.getLayerClient().setClampOnFixedLayerMarginsChange(
pointerCount == 0 || action == MotionEvent.ACTION_CANCEL ||
action == MotionEvent.ACTION_UP);
View toolbarView = mBrowserToolbar.getLayout();
if (action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_POINTER_DOWN) {
if (pointerCount == 1) {
mToolbarLocked = mToolbarThresholdPassed = false;
mToolbarSubpixelAccumulation = 0.0f;
mLastTouchX = event.getX();
mLastTouchY = event.getY();
return super.onInterceptTouchEvent(view, event);
}
// Animate the toolbar to the fully on/off position.
mBrowserToolbar.animateVisibility(
toolbarView.getScrollY() > toolbarView.getHeight() / 2 ?
false : true);
}
// If more than one pointer has been tracked, or we've locked the
// toolbar movement, let the event pass through and be handled by the
// PanZoomController for zooming.
if (pointerCount > 1 || mToolbarLocked) {
return super.onInterceptTouchEvent(view, event);
}
// If a pointer has been lifted so that there's only one pointer left,
// unlock the toolbar and track that remaining pointer.
if (pointerCount == 1 && action == MotionEvent.ACTION_POINTER_UP) {
mLastTouchY = event.getY(1 - event.getActionIndex());
return super.onInterceptTouchEvent(view, event);
}
// Handle scrolling the toolbar
float eventX = event.getX();
float eventY = event.getY();
float deltaX = mLastTouchX - eventX;
float deltaY = mLastTouchY - eventY;
int toolbarY = toolbarView.getScrollY();
int toolbarHeight = toolbarView.getHeight();
// Check if we've passed the toolbar movement threshold
if (!mToolbarThresholdPassed) {
float threshold = toolbarHeight * TOOLBAR_MOVEMENT_THRESHOLD;
if (Math.abs(deltaY) > threshold) {
mToolbarThresholdPassed = true;
// If we're scrolling downwards and the toolbar was hidden
// when we started scrolling, lock it.
if (deltaY > 0 && toolbarY == toolbarHeight) {
mToolbarLocked = true;
return super.onInterceptTouchEvent(view, event);
}
} else if (Math.abs(deltaX) > threshold) {
// Any horizontal scrolling past the threshold should
// initiate toolbar lock.
mToolbarLocked = true;
mToolbarThresholdPassed = true;
return super.onInterceptTouchEvent(view, event);
} else {
// The threshold hasn't been passed. We don't want to update
// the stored last touch position, so return here.
return super.onInterceptTouchEvent(view, event);
}
} else if (action == MotionEvent.ACTION_MOVE) {
// Cancel any ongoing animation before we start moving the toolbar.
mBrowserToolbar.cancelVisibilityAnimation();
// Move the toolbar by the amount the touch event has moved,
// clamping to fully visible or fully hidden.
// Don't let the toolbar scroll off the top if it's just exposing
// overscroll area.
float toolbarMaxY = Math.min(toolbarHeight,
Math.max(0, toolbarHeight - (metrics.pageRectTop -
metrics.viewportRectTop)));
float newToolbarYf = Math.max(0, Math.min(toolbarMaxY,
toolbarY + deltaY + mToolbarSubpixelAccumulation));
int newToolbarY = Math.round(newToolbarYf);
mToolbarSubpixelAccumulation = (newToolbarYf - newToolbarY);
toolbarView.scrollTo(0, newToolbarY);
// Reset tracking when the toolbar is fully visible or hidden.
if (newToolbarY == 0 || newToolbarY == toolbarHeight) {
mLastTouchY = eventY;
}
} else if (action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_CANCEL) {
// Animate the toolbar to fully on or off, depending on how much
// of it is hidden and the current swipe velocity.
PanZoomController pzc = mLayerView.getPanZoomController();
float yVelocity = (pzc == null ? 0.0f : pzc.getVelocityVector().y);
mBrowserToolbar.animateVisibilityWithVelocityBias(
toolbarY > toolbarHeight / 2 ? false : true, yVelocity);
}
// Update the last recorded position.
mLastTouchX = eventX;
mLastTouchY = eventY;
return super.onInterceptTouchEvent(view, event);
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// Global onKey handler. This is called if the focused UI doesn't
@ -372,15 +217,19 @@ abstract public class BrowserApp extends GeckoApp
// Toggle/focus the address bar on gamepad-y button.
if (mBrowserToolbar.isVisible()) {
if (isDynamicToolbarEnabled() && !mAboutHome.getUserVisibleHint()) {
mBrowserToolbar.animateVisibility(false);
mLayerView.requestFocus();
if (mLayerView != null) {
mLayerView.getLayerMarginsAnimator().hideMargins(false);
mLayerView.requestFocus();
}
} else {
// Just focus the address bar when about:home is visible
// or when the dynamic toolbar isn't enabled.
mBrowserToolbar.requestFocusFromTouch();
}
} else {
mBrowserToolbar.animateVisibility(true);
if (mLayerView != null) {
mLayerView.getLayerMarginsAnimator().showMargins(false);
}
mBrowserToolbar.requestFocusFromTouch();
}
return true;
@ -499,10 +348,9 @@ abstract public class BrowserApp extends GeckoApp
public boolean onInterceptMotionEvent(View view, MotionEvent event) {
// If we get a gamepad panning MotionEvent while the focus is not on the layerview,
// put the focus on the layerview and carry on
LayerView layerView = mLayerView;
if (layerView != null && !layerView.hasFocus() && GamepadUtils.isPanningControl(event)) {
if (mLayerView != null && !mLayerView.hasFocus() && GamepadUtils.isPanningControl(event)) {
if (mAboutHome.getUserVisibleHint()) {
layerView.requestFocus();
mLayerView.requestFocus();
} else {
mAboutHome.requestFocus();
}
@ -595,18 +443,23 @@ abstract public class BrowserApp extends GeckoApp
private void setDynamicToolbarEnabled(boolean enabled) {
if (enabled) {
if (mLayerView != null) {
mLayerView.getLayerClient().setOnMetricsChangedListener(this);
}
setToolbarMargin(0);
} else {
// Immediately show the toolbar when disabling the dynamic
// toolbar.
if (mLayerView != null) {
mLayerView.getLayerClient().setOnMetricsChangedListener(null);
}
mAboutHome.setPadding(0, 0, 0, 0);
mBrowserToolbar.cancelVisibilityAnimation();
mBrowserToolbar.getLayout().scrollTo(0, 0);
if (mBrowserToolbar != null) {
mBrowserToolbar.getLayout().scrollTo(0, 0);
}
}
// Refresh the margins to reset the padding on the spacer and
// make sure that Gecko is in sync.
((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
refreshToolbarHeight();
}
private boolean isDynamicToolbarEnabled() {
@ -671,12 +524,6 @@ abstract public class BrowserApp extends GeckoApp
mBrowserToolbar.updateBackButton(false);
mBrowserToolbar.updateForwardButton(false);
// Reset mToolbarHeight before setting margins so we force the
// Viewport:FixedMarginsChanged message to be sent again now that
// Gecko has loaded.
mToolbarHeight = 0;
((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
mDoorHangerPopup.setAnchor(mBrowserToolbar.mFavicon);
if (isExternalURL || mRestoreMode != RESTORE_NONE) {
@ -695,6 +542,13 @@ abstract public class BrowserApp extends GeckoApp
}
}
// Listen to margin changes to position the toolbar correctly
if (isDynamicToolbarEnabled()) {
refreshToolbarHeight();
mLayerView.getLayerMarginsAnimator().showMargins(true);
mLayerView.getLayerClient().setOnMetricsChangedListener(this);
}
// Intercept key events for gamepad shortcuts
mLayerView.setOnKeyListener(this);
}
@ -709,7 +563,41 @@ abstract public class BrowserApp extends GeckoApp
mGeckoLayout.requestLayout();
}
public void setToolbarHeight(int aHeight, int aVisibleHeight) {
@Override
public void onMetricsChanged(ImmutableViewportMetrics aMetrics) {
if (mAboutHome.getUserVisibleHint() || mBrowserToolbar == null) {
return;
}
final View toolbarLayout = mBrowserToolbar.getLayout();
final int marginTop = Math.round(aMetrics.marginTop);
ThreadUtils.postToUiThread(new Runnable() {
public void run() {
toolbarLayout.scrollTo(0, toolbarLayout.getHeight() - marginTop);
}
});
}
@Override
public void onPanZoomStopped() {
if (!isDynamicToolbarEnabled() || mAboutHome.getUserVisibleHint()) {
return;
}
ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
if (metrics.marginTop >= mToolbarHeight / 2) {
mLayerView.getLayerMarginsAnimator().showMargins(false);
} else {
mLayerView.getLayerMarginsAnimator().hideMargins(false);
}
}
public void refreshToolbarHeight() {
int height = 0;
if (mBrowserToolbar != null) {
height = mBrowserToolbar.getLayout().getHeight();
}
if (!isDynamicToolbarEnabled() || mAboutHome.getUserVisibleHint()) {
// Use aVisibleHeight here so that when the dynamic toolbar is
// enabled, the padding will animate with the toolbar becoming
@ -718,40 +606,19 @@ abstract public class BrowserApp extends GeckoApp
// When the dynamic toolbar is enabled, set the padding on the
// about:home widget directly - this is to avoid resizing the
// LayerView, which can cause visible artifacts.
mAboutHome.setPadding(0, aVisibleHeight, 0, 0);
mAboutHome.setPadding(0, height, 0, 0);
} else {
setToolbarMargin(aVisibleHeight);
setToolbarMargin(height);
height = 0;
}
aHeight = aVisibleHeight = 0;
} else {
setToolbarMargin(0);
}
// Update the Gecko-side global for fixed viewport margins.
if (aHeight != mToolbarHeight) {
mToolbarHeight = aHeight;
// In the current UI, this is the only place we have need of
// viewport margins (to stop the toolbar from obscuring fixed-pos
// content).
GeckoAppShell.sendEventToGecko(
GeckoEvent.createBroadcastEvent("Viewport:FixedMarginsChanged",
"{ \"top\" : " + aHeight + ", \"right\" : 0, \"bottom\" : 0, \"left\" : 0 }"));
}
if (mLayerView != null) {
mLayerView.getLayerClient().setFixedLayerMargins(0, aVisibleHeight, 0, 0);
// Force a redraw when the view isn't moving and the toolbar is
// fully visible or fully hidden. This is to make sure that the
// Gecko-side fixed viewport margins are in sync when the view and
// bar aren't animating.
PointF velocityVector = mLayerView.getPanZoomController().getVelocityVector();
if ((aVisibleHeight == 0 || aVisibleHeight == aHeight) &&
FloatUtils.fuzzyEquals(velocityVector.x, 0.0f) &&
FloatUtils.fuzzyEquals(velocityVector.y, 0.0f)) {
mLayerView.getLayerClient().forceRedraw();
}
if (mLayerView != null && height != mToolbarHeight) {
mToolbarHeight = height;
mLayerView.getLayerMarginsAnimator().setMaxMargins(0, height, 0, 0);
mLayerView.getLayerMarginsAnimator().showMargins(true);
}
}
@ -1103,11 +970,13 @@ abstract public class BrowserApp extends GeckoApp
// If the tabs layout is animating onto the screen, pin the dynamic
// toolbar.
if (width > 0 && height > 0) {
mToolbarPinned = true;
mBrowserToolbar.animateVisibility(true);
} else {
mToolbarPinned = false;
if (mLayerView != null && isDynamicToolbarEnabled()) {
if (width > 0 && height > 0) {
mLayerView.getLayerMarginsAnimator().showMargins(false);
mLayerView.getLayerMarginsAnimator().setMarginsPinned(true);
} else {
mLayerView.getLayerMarginsAnimator().setMarginsPinned(false);
}
}
mMainLayoutAnimator.start();
@ -1209,6 +1078,15 @@ abstract public class BrowserApp extends GeckoApp
return;
}
// Refresh toolbar height to possibly restore the toolbar padding
refreshToolbarHeight();
// Show the toolbar before hiding about:home so the
// onMetricsChanged callback still works.
if (isDynamicToolbarEnabled() && mLayerView != null) {
mLayerView.getLayerMarginsAnimator().showMargins(true);
}
// We use commitAllowingStateLoss() instead of commit() here to avoid an
// IllegalStateException. showAboutHome() and hideAboutHome() are
// executed inside of tab's onChange() callback. Since that callback can
@ -1222,9 +1100,6 @@ abstract public class BrowserApp extends GeckoApp
mAboutHome.setUserVisibleHint(true);
mBrowserToolbar.setNextFocusDownId(R.id.abouthome_content);
// Refresh margins to possibly restore the toolbar padding
((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
}
private void hideAboutHome() {
@ -1239,8 +1114,8 @@ abstract public class BrowserApp extends GeckoApp
mBrowserToolbar.setShadowVisibility(true);
mBrowserToolbar.setNextFocusDownId(R.id.layer_view);
// Refresh margins to possibly restore the toolbar padding
((BrowserToolbarLayout)mBrowserToolbar.getLayout()).refreshMargins();
// Refresh toolbar height to possibly restore the toolbar padding
refreshToolbarHeight();
}
private class HideTabsTouchListener implements TouchEventInterceptor {
@ -1463,8 +1338,8 @@ abstract public class BrowserApp extends GeckoApp
if (!mBrowserToolbar.openOptionsMenu())
super.openOptionsMenu();
if (isDynamicToolbarEnabled())
mBrowserToolbar.animateVisibility(true);
if (isDynamicToolbarEnabled() && mLayerView != null)
mLayerView.getLayerMarginsAnimator().showMargins(false);
}
@Override

View File

@ -114,20 +114,11 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
private PropertyAnimator mVisibilityAnimator;
private enum ToolbarVisibility {
VISIBLE,
HIDDEN,
INCONSISTENT
};
private ToolbarVisibility mVisibility;
private static final int TABS_CONTRACTED = 1;
private static final int TABS_EXPANDED = 2;
private static final int FORWARD_ANIMATION_DURATION = 450;
private static final int VISIBILITY_ANIMATION_DURATION = 250;
public BrowserToolbar(BrowserApp activity) {
// BrowserToolbar is attached to BrowserApp only.
mActivity = activity;
@ -138,8 +129,6 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
mAnimateSiteSecurity = true;
mAnimatingEntry = false;
mVisibility = ToolbarVisibility.INCONSISTENT;
}
public void from(LinearLayout layout) {
@ -505,71 +494,8 @@ public class BrowserToolbar implements ViewSwitcher.ViewFactory,
}
}
private boolean canToolbarHide() {
// Forbid the toolbar from hiding if hiding the toolbar would cause
// the page to go into overscroll.
LayerView layerView = GeckoApp.mAppContext.getLayerView();
if (layerView != null) {
ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
return (metrics.getPageHeight() >= metrics.getHeight());
}
return false;
}
public void animateVisibility(boolean show) {
// Do nothing if there's a delayed animation pending that does the
// same thing and this request also has a delay.
if (mVisibility != ToolbarVisibility.INCONSISTENT &&
show == isVisible()) {
return;
}
cancelVisibilityAnimation();
mVisibility = show ? ToolbarVisibility.VISIBLE : ToolbarVisibility.HIDDEN;
mVisibilityAnimator = new PropertyAnimator(VISIBILITY_ANIMATION_DURATION);
mVisibilityAnimator.attach(mLayout, PropertyAnimator.Property.SCROLL_Y,
show ? 0 : mLayout.getHeight());
// Only start the animation if we're showing the toolbar, or it's ok
// to hide it.
if (mVisibility == ToolbarVisibility.VISIBLE ||
canToolbarHide()) {
mVisibilityAnimator.start();
}
}
/**
* Animate the visibility of the toolbar, but take into account the
* velocity of what's moving underneath the toolbar. If that velocity
* is greater than the default animation velocity, it will determine
* the direction of the toolbar animation. Velocity is specified in
* pixels per 1/60 seconds (a 60Hz frame).
*/
public void animateVisibilityWithVelocityBias(boolean show, float velocity) {
// Work out the default animation velocity. This assumes a linear
// animation which is incorrect, but the animation is short enough that
// there's very little difference.
float defaultVelocity =
mLayout.getHeight() / ((VISIBILITY_ANIMATION_DURATION / 1000.0f) * 60);
if (Math.abs(velocity) > defaultVelocity) {
show = (velocity > 0) ? false : true;
}
animateVisibility(show);
}
public void cancelVisibilityAnimation() {
if (mVisibilityAnimator != null) {
mVisibility = ToolbarVisibility.INCONSISTENT;
mVisibilityAnimator.stop(false);
mVisibilityAnimator = null;
}
}
public boolean isVisible() {
return mVisibility == ToolbarVisibility.VISIBLE;
return mLayout.getScrollY() == 0;
}
public void setNextFocusDownId(int nextId) {

View File

@ -28,15 +28,6 @@ public class BrowserToolbarLayout extends LinearLayout {
return super.onTouchEvent(event);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (t != oldt) {
refreshMargins();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
@ -44,19 +35,14 @@ public class BrowserToolbarLayout extends LinearLayout {
if (h != oldh) {
// Post this to happen outside of onSizeChanged, as this may cause
// a layout change and relayouts within a layout change don't work.
final int height = h;
post(new Runnable() {
@Override
public void run() {
refreshMargins();
((BrowserApp)GeckoApp.mAppContext).refreshToolbarHeight();
}
});
}
}
public void refreshMargins() {
int height = getHeight();
int visibleHeight = height - getScrollY();
((BrowserApp)GeckoApp.mAppContext).setToolbarHeight(height, visibleHeight);
}
}

View File

@ -767,10 +767,12 @@ abstract public class GeckoApp
if (tab == null)
return;
tab.setZoomConstraints(new ZoomConstraints(message));
tab.setIsRTL(message.getBoolean("isRTL"));
// Sync up the layer view and the tab if the tab is currently displayed.
LayerView layerView = mLayerView;
if (layerView != null && Tabs.getInstance().isSelectedTab(tab)) {
layerView.setZoomConstraints(tab.getZoomConstraints());
layerView.setIsRTL(tab.getIsRTL());
}
} else if (event.equals("Session:StatePurged")) {
onStatePurged();

View File

@ -590,10 +590,10 @@ public class GeckoEvent {
sb.append("{ \"x\" : ").append(metrics.viewportRectLeft)
.append(", \"y\" : ").append(metrics.viewportRectTop)
.append(", \"zoom\" : ").append(metrics.zoomFactor)
.append(", \"fixedMarginLeft\" : ").append(metrics.fixedLayerMarginLeft)
.append(", \"fixedMarginTop\" : ").append(metrics.fixedLayerMarginTop)
.append(", \"fixedMarginRight\" : ").append(metrics.fixedLayerMarginRight)
.append(", \"fixedMarginBottom\" : ").append(metrics.fixedLayerMarginBottom)
.append(", \"fixedMarginLeft\" : ").append(metrics.marginLeft)
.append(", \"fixedMarginTop\" : ").append(metrics.marginTop)
.append(", \"fixedMarginRight\" : ").append(metrics.marginRight)
.append(", \"fixedMarginBottom\" : ").append(metrics.marginBottom)
.append(", \"displayPort\" :").append(displayPort.toJSON())
.append('}');
event.mCharactersExtra = sb.toString();

View File

@ -191,6 +191,7 @@ FENNEC_JAVA_FILES = \
gfx/IntSize.java \
gfx/JavaPanZoomController.java \
gfx/Layer.java \
gfx/LayerMarginsAnimator.java \
gfx/LayerRenderer.java \
gfx/LayerView.java \
gfx/PluginLayer.java \

View File

@ -56,6 +56,7 @@ public class Tab {
private String mContentType;
private boolean mHasTouchListeners;
private ZoomConstraints mZoomConstraints;
private boolean mIsRTL;
private ArrayList<View> mPluginViews;
private HashMap<Object, Layer> mPluginLayers;
private int mBackgroundColor;
@ -295,6 +296,14 @@ public class Tab {
return mZoomConstraints;
}
public void setIsRTL(boolean aIsRTL) {
mIsRTL = aIsRTL;
}
public boolean getIsRTL() {
return mIsRTL;
}
public void setHasTouchListeners(boolean aValue) {
mHasTouchListeners = aValue;
}

View File

@ -122,21 +122,25 @@ class TextSelection extends Layer implements GeckoEventListener {
// cache the relevant values from the context and bail out if they are the same. we do this
// because this draw function gets called a lot (once per compositor frame) and we want to
// avoid doing a lot of extra work in cases where it's not needed.
if (FloatUtils.fuzzyEquals(mViewLeft, context.viewport.left)
&& FloatUtils.fuzzyEquals(mViewTop, context.viewport.top)
&& FloatUtils.fuzzyEquals(mViewZoom, context.zoomFactor)) {
final float viewLeft = context.viewport.left - context.offset.x;
final float viewTop = context.viewport.top - context.offset.y;
final float viewZoom = context.zoomFactor;
if (FloatUtils.fuzzyEquals(mViewLeft, viewLeft)
&& FloatUtils.fuzzyEquals(mViewTop, viewTop)
&& FloatUtils.fuzzyEquals(mViewZoom, viewZoom)) {
return;
}
mViewLeft = context.viewport.left;
mViewTop = context.viewport.top;
mViewZoom = context.zoomFactor;
mViewLeft = viewLeft;
mViewTop = viewTop;
mViewZoom = viewZoom;
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
mStartHandle.repositionWithViewport(context.viewport.left, context.viewport.top, context.zoomFactor);
mMiddleHandle.repositionWithViewport(context.viewport.left, context.viewport.top, context.zoomFactor);
mEndHandle.repositionWithViewport(context.viewport.left, context.viewport.top, context.zoomFactor);
mStartHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
mMiddleHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
mEndHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
}
});
}

View File

@ -155,7 +155,8 @@ class TextSelectionHandle extends ImageView implements View.OnTouchListener {
}
ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
repositionWithViewport(metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor);
PointF offset = metrics.getMarginOffset();
repositionWithViewport(metrics.viewportRectLeft - offset.x, metrics.viewportRectTop - offset.y, metrics.zoomFactor);
}
void repositionWithViewport(float x, float y, float zoom) {

View File

@ -83,16 +83,16 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
* that because mViewportMetrics might get reassigned in between reading the different
* fields. */
private volatile ImmutableViewportMetrics mViewportMetrics;
private OnMetricsChangedListener mViewportChangeListener;
private ZoomConstraints mZoomConstraints;
private boolean mGeckoIsReady;
private final PanZoomController mPanZoomController;
private final LayerMarginsAnimator mMarginsAnimator;
private LayerView mView;
private boolean mClampOnMarginChange;
public GeckoLayerClient(Context context, LayerView view, EventDispatcher eventDispatcher) {
// we can fill these in with dummy values because they are always written
// to before being read
@ -108,7 +108,6 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
mProgressiveUpdateDisplayPort = new DisplayPortMetrics();
mLastProgressiveUpdateWasLowPrecision = false;
mProgressiveUpdateWasInDanger = false;
mClampOnMarginChange = true;
mForceRedraw = true;
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
@ -117,6 +116,7 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
mZoomConstraints = new ZoomConstraints(false);
mPanZoomController = PanZoomController.Factory.create(this, view, eventDispatcher);
mMarginsAnimator = new LayerMarginsAnimator(this);
mView = view;
mView.setListener(this);
}
@ -206,6 +206,10 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
return mPanZoomController;
}
LayerMarginsAnimator getLayerMarginsAnimator() {
return mMarginsAnimator;
}
/* Informs Gecko that the screen size has changed. */
private void sendResizeEventIfNecessary(boolean force) {
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
@ -259,50 +263,49 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
});
}
private void adjustFixedLayerMarginsForOverscroll(ImmutableViewportMetrics metrics, RectF adjustedMargins) {
// When the page is in overscroll, we want that to 'eat into' the fixed
// margin on that side of the viewport. This is because overscroll
// equates to extra visible area and we use the fixed margins to stop
// fixed position elements from being obscured by chrome.
// When we're overscrolled, we also want to make sure that the fixed
// content on the non-overscroll side isn't obscured by the edge of the
// window, so when adjusting one side of a margin, we apply the opposite
// adjustment to the other side of the margin.
adjustedMargins.left = metrics.fixedLayerMarginLeft;
adjustedMargins.top = metrics.fixedLayerMarginTop;
adjustedMargins.right = metrics.fixedLayerMarginRight;
adjustedMargins.bottom = metrics.fixedLayerMarginBottom;
/**
* Derives content document fixed position margins/fixed layer margins from
* the view margins in the given metrics object.
*/
private void getFixedMargins(ImmutableViewportMetrics metrics, RectF fixedMargins) {
fixedMargins.left = 0;
fixedMargins.top = 0;
fixedMargins.right = 0;
fixedMargins.bottom = 0;
// The maximum margins are determined by the scrollable area of the page.
float maxMarginWidth = Math.max(0, metrics.getPageWidth() - metrics.getWidthWithoutMargins());
float maxMarginHeight = Math.max(0, metrics.getPageHeight() - metrics.getHeightWithoutMargins());
// Adjust for left overscroll
float leftOverscroll = metrics.pageRectLeft - metrics.viewportRectLeft;
if (leftOverscroll > 0) {
adjustedMargins.left = FloatUtils.clamp(adjustedMargins.left - leftOverscroll, 0, maxMarginWidth);
adjustedMargins.right = FloatUtils.clamp(adjustedMargins.right + leftOverscroll, 0, maxMarginWidth - adjustedMargins.left);
PointF offset = metrics.getMarginOffset();
RectF overscroll = metrics.getOverscroll();
if (offset.x >= 0) {
fixedMargins.right = Math.max(0, Math.min(offset.x - overscroll.right, maxMarginWidth));
} else {
fixedMargins.left = Math.max(0, Math.min(-offset.x - overscroll.left, maxMarginWidth));
}
if (offset.y >= 0) {
fixedMargins.bottom = Math.max(0, Math.min(offset.y - overscroll.bottom, maxMarginHeight));
} else {
fixedMargins.top = Math.max(0, Math.min(-offset.y - overscroll.top, maxMarginHeight));
}
// Adjust for right overscroll
float rightOverscroll = metrics.viewportRectRight - metrics.pageRectRight;
if (rightOverscroll > 0) {
adjustedMargins.right = FloatUtils.clamp(adjustedMargins.right - rightOverscroll, 0, maxMarginWidth);
adjustedMargins.left = FloatUtils.clamp(adjustedMargins.left + rightOverscroll, 0, maxMarginWidth - adjustedMargins.right);
// Adjust for overscroll. If we're overscrolled on one side, add that
// distance to the margins of the other side (limiting to the maximum
// margin size calculated above).
if (overscroll.left > 0) {
fixedMargins.right = Math.min(maxMarginWidth - fixedMargins.left,
fixedMargins.right + overscroll.left);
} else if (overscroll.right > 0) {
fixedMargins.left = Math.min(maxMarginWidth - fixedMargins.right,
fixedMargins.left + overscroll.right);
}
// Adjust for top overscroll
float topOverscroll = metrics.pageRectTop - metrics.viewportRectTop;
if (topOverscroll > 0) {
adjustedMargins.top = FloatUtils.clamp(adjustedMargins.top - topOverscroll, 0, maxMarginHeight);
adjustedMargins.bottom = FloatUtils.clamp(adjustedMargins.bottom + topOverscroll, 0, maxMarginHeight - adjustedMargins.top);
}
// Adjust for bottom overscroll
float bottomOverscroll = metrics.viewportRectBottom - metrics.pageRectBottom;
if (bottomOverscroll > 0) {
adjustedMargins.bottom = FloatUtils.clamp(adjustedMargins.bottom - bottomOverscroll, 0, maxMarginHeight);
adjustedMargins.top = FloatUtils.clamp(adjustedMargins.top + bottomOverscroll, 0, maxMarginHeight - adjustedMargins.bottom);
if (overscroll.top > 0) {
fixedMargins.bottom = Math.min(maxMarginHeight - fixedMargins.top,
fixedMargins.bottom + overscroll.top);
} else if (overscroll.bottom > 0) {
fixedMargins.top = Math.min(maxMarginHeight - fixedMargins.bottom,
fixedMargins.top + overscroll.bottom);
}
}
@ -310,11 +313,10 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
ImmutableViewportMetrics metrics = getViewportMetrics();
ImmutableViewportMetrics clampedMetrics = metrics.clamp();
RectF fixedLayerMargins = new RectF();
adjustFixedLayerMarginsForOverscroll(metrics, fixedLayerMargins);
clampedMetrics = clampedMetrics.setFixedLayerMargins(
fixedLayerMargins.left, fixedLayerMargins.top,
fixedLayerMargins.right, fixedLayerMargins.bottom);
RectF margins = new RectF();
getFixedMargins(metrics, margins);
clampedMetrics = clampedMetrics.setMargins(
margins.left, margins.top, margins.right, margins.bottom);
if (displayPort == null) {
displayPort = DisplayPortCalculator.calculate(metrics, mPanZoomController.getVelocityVector());
@ -386,21 +388,6 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
}
});
// If we're meant to be scrolled to the top, take into account
// the current fixed layer margins and offset the local viewport
// accordingly.
// XXX We should also do this for the left on an ltr document, and
// the right on an rtl document, but we don't currently have
// a way of determining the text direction from Java.
// This also applies to setFirstPaintViewport.
if (type == ViewportMessageType.UPDATE
&& FloatUtils.fuzzyEquals(newMetrics.viewportRectTop,
newMetrics.pageRectTop)
&& oldMetrics.fixedLayerMarginTop > 0) {
newMetrics = newMetrics.setViewportOrigin(newMetrics.viewportRectLeft,
newMetrics.pageRectTop - oldMetrics.fixedLayerMarginTop);
}
setViewportMetrics(newMetrics, type == ViewportMessageType.UPDATE);
mDisplayPort = DisplayPortCalculator.calculate(getViewportMetrics(), null);
}
@ -423,58 +410,6 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
}
}
/**
* Sets margins on fixed-position layers, to be used when compositing.
* Must be called on the UI thread!
*/
public synchronized void setFixedLayerMargins(float left, float top, float right, float bottom) {
ImmutableViewportMetrics oldMetrics = getViewportMetrics();
ImmutableViewportMetrics newMetrics = oldMetrics.setFixedLayerMargins(left, top, right, bottom);
if (mClampOnMarginChange) {
// Only clamp on decreased margins
boolean changed = false;
float viewportRectLeft = oldMetrics.viewportRectLeft;
float viewportRectTop = oldMetrics.viewportRectTop;
// Clamp the x-axis if the page was over-scrolled into the margin
// area.
if (oldMetrics.fixedLayerMarginLeft > left &&
viewportRectLeft < oldMetrics.pageRectLeft - left) {
viewportRectLeft = oldMetrics.pageRectLeft - left;
changed = true;
} else if (oldMetrics.fixedLayerMarginRight > right &&
oldMetrics.viewportRectRight > oldMetrics.pageRectRight + right) {
viewportRectLeft = oldMetrics.pageRectRight + right - oldMetrics.getWidth();
changed = true;
}
// Do the same for the y-axis.
if (oldMetrics.fixedLayerMarginTop > top &&
viewportRectTop < oldMetrics.pageRectTop - top) {
viewportRectTop = oldMetrics.pageRectTop - top;
changed = true;
} else if (oldMetrics.fixedLayerMarginBottom > bottom &&
oldMetrics.viewportRectBottom > oldMetrics.pageRectBottom + bottom) {
viewportRectTop = oldMetrics.pageRectBottom + bottom - oldMetrics.getHeight();
changed = true;
}
// Set the new metrics, if they're different.
if (changed) {
newMetrics = newMetrics.setViewportOrigin(viewportRectLeft, viewportRectTop);
}
}
mViewportMetrics = newMetrics;
mView.requestRender();
setShadowVisibility();
}
public void setClampOnFixedLayerMarginsChange(boolean aClamp) {
mClampOnMarginChange = aClamp;
}
// This is called on the Gecko thread to determine if we're still interested
// in the update of this display-port to continue. We can return true here
// to abort the current update and continue with any subsequent ones. This
@ -580,6 +515,11 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
mZoomConstraints = constraints;
}
void setIsRTL(boolean aIsRTL) {
ImmutableViewportMetrics newMetrics = getViewportMetrics().setIsRTL(aIsRTL);
setViewportMetrics(newMetrics, false);
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature.
* The compositor invokes this function just before compositing a frame where the document
* is different from the document composited on the last frame. In these cases, the viewport
@ -593,11 +533,14 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
synchronized (this) {
ImmutableViewportMetrics currentMetrics = getViewportMetrics();
Tab tab = Tabs.getInstance().getSelectedTab();
final ImmutableViewportMetrics newMetrics = currentMetrics
.setViewportOrigin(offsetX, offsetY)
.setZoomFactor(zoom)
.setPageRect(new RectF(pageLeft, pageTop, pageRight, pageBottom),
new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom));
new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom))
.setIsRTL(tab.getIsRTL());
// Since we have switched to displaying a different document, we need to update any
// viewport-related state we have lying around. This includes mGeckoViewport and
// mViewportMetrics. Usually this information is updated via handleViewportMessage
@ -609,17 +552,8 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
}
});
// If we're meant to be scrolled to the top, take into account any
// margin set on the pan zoom controller.
if (FloatUtils.fuzzyEquals(offsetY, pageTop)
&& newMetrics.fixedLayerMarginTop > 0) {
setViewportMetrics(newMetrics.setViewportOrigin(offsetX,
-newMetrics.fixedLayerMarginTop));
} else {
setViewportMetrics(newMetrics);
}
setViewportMetrics(newMetrics);
Tab tab = Tabs.getInstance().getSelectedTab();
mView.setBackgroundColor(tab.getBackgroundColor());
setZoomConstraints(tab.getZoomConstraints());
@ -683,13 +617,23 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
mCurrentViewTransform.scale = mFrameMetrics.zoomFactor;
// Adjust the fixed layer margins so that overscroll subtracts from them.
adjustFixedLayerMarginsForOverscroll(mFrameMetrics, mCurrentViewTransformMargins);
getFixedMargins(mFrameMetrics, mCurrentViewTransformMargins);
mCurrentViewTransform.fixedLayerMarginLeft = mCurrentViewTransformMargins.left;
mCurrentViewTransform.fixedLayerMarginTop = mCurrentViewTransformMargins.top;
mCurrentViewTransform.fixedLayerMarginRight = mCurrentViewTransformMargins.right;
mCurrentViewTransform.fixedLayerMarginBottom = mCurrentViewTransformMargins.bottom;
mRootLayer.setPositionAndResolution(x, y, x + width, y + height, resolution);
// Offset the view transform so that it renders in the correct place.
PointF offset = mFrameMetrics.getMarginOffset();
mCurrentViewTransform.offsetX = offset.x;
mCurrentViewTransform.offsetY = offset.y;
mRootLayer.setPositionAndResolution(
Math.round(x + mCurrentViewTransform.offsetX),
Math.round(y + mCurrentViewTransform.offsetY),
Math.round(x + width + mCurrentViewTransform.offsetX),
Math.round(y + height + mCurrentViewTransform.offsetY),
resolution);
if (layersUpdated && mRecordDrawTimes) {
// If we got a layers update, that means a draw finished. Check to see if the area drawn matches
@ -818,9 +762,20 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
// ever be updated is in GeckoLayerClient.setFixedLayerMargins; both of these assign to
// mViewportMetrics directly.
metrics = metrics.setViewportSize(mViewportMetrics.getWidth(), mViewportMetrics.getHeight());
metrics = metrics.setFixedLayerMarginsFrom(mViewportMetrics);
metrics = metrics.setMarginsFrom(mViewportMetrics);
mViewportMetrics = metrics;
viewportMetricsChanged(notifyGecko);
}
/*
* You must hold the monitor while calling this.
*/
private void viewportMetricsChanged(boolean notifyGecko) {
if (mViewportChangeListener != null) {
mViewportChangeListener.onMetricsChanged(mViewportMetrics);
}
mView.requestRender();
if (notifyGecko && mGeckoIsReady) {
geometryChanged();
@ -828,6 +783,48 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
setShadowVisibility();
}
/*
* Updates the viewport metrics, overriding the viewport size and margins
* which are normally retained when calling setViewportMetrics.
* You must hold the monitor while calling this.
*/
void forceViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko, boolean forceRedraw) {
if (forceRedraw) {
mForceRedraw = true;
}
mViewportMetrics = metrics;
viewportMetricsChanged(notifyGecko);
}
/** Implementation of PanZoomTarget
* Scroll the viewport by a certain amount. This will take viewport margins
* and margin animation into account. If margins are currently animating,
* this will just go ahead and modify the viewport origin, otherwise the
* delta will be applied to the margins and the remainder will be applied to
* the viewport origin.
*
* You must hold the monitor while calling this.
*/
@Override
public void scrollBy(float dx, float dy) {
// Set mViewportMetrics manually so the margin changes take.
mViewportMetrics = mMarginsAnimator.scrollBy(mViewportMetrics, dx, dy);
viewportMetricsChanged(true);
}
/** Implementation of PanZoomTarget */
@Override
public void panZoomStopped() {
if (mViewportChangeListener != null) {
mViewportChangeListener.onPanZoomStopped();
}
}
public interface OnMetricsChangedListener {
public void onMetricsChanged(ImmutableViewportMetrics viewport);
public void onPanZoomStopped();
}
private void setShadowVisibility() {
ThreadUtils.postToUiThread(new Runnable() {
@Override
@ -836,7 +833,7 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
return;
}
ImmutableViewportMetrics m = mViewportMetrics;
BrowserApp.mBrowserToolbar.setShadowVisibility(m.viewportRectTop >= m.pageRectTop - m.fixedLayerMarginTop);
BrowserApp.mBrowserToolbar.setShadowVisibility(m.viewportRectTop >= m.pageRectTop);
}
});
}
@ -877,12 +874,14 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
PointF origin = viewportMetrics.getOrigin();
PointF offset = viewportMetrics.getMarginOffset();
origin.offset(-offset.x, -offset.y);
float zoom = viewportMetrics.zoomFactor;
ImmutableViewportMetrics geckoViewport = mGeckoViewport;
PointF geckoOrigin = geckoViewport.getOrigin();
float geckoZoom = geckoViewport.zoomFactor;
// viewPoint + origin gives the coordinate in device pixels from the top-left corner of the page.
// viewPoint + origin - offset gives the coordinate in device pixels from the top-left corner of the page.
// Divided by zoom, this gives us the coordinate in CSS pixels from the top-left corner of the page.
// geckoOrigin / geckoZoom is where Gecko thinks it is (scrollTo position) in CSS pixels from
// the top-left corner of the page. Subtracting the two gives us the offset of the viewPoint from
@ -894,6 +893,10 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
return layerPoint;
}
public void setOnMetricsChangedListener(OnMetricsChangedListener listener) {
mViewportChangeListener = listener;
}
/** Used by robocop for testing purposes. Not for production use! */
public void setDrawListener(DrawListener listener) {
mDrawListener = listener;

View File

@ -32,21 +32,26 @@ public class ImmutableViewportMetrics {
public final float viewportRectTop;
public final float viewportRectRight;
public final float viewportRectBottom;
public final float fixedLayerMarginLeft;
public final float fixedLayerMarginTop;
public final float fixedLayerMarginRight;
public final float fixedLayerMarginBottom;
public final float marginLeft;
public final float marginTop;
public final float marginRight;
public final float marginBottom;
public final float zoomFactor;
public final boolean isRTL;
public ImmutableViewportMetrics(DisplayMetrics metrics) {
viewportRectLeft = pageRectLeft = cssPageRectLeft = 0;
viewportRectTop = pageRectTop = cssPageRectTop = 0;
viewportRectRight = pageRectRight = cssPageRectRight = metrics.widthPixels;
viewportRectBottom = pageRectBottom = cssPageRectBottom = metrics.heightPixels;
fixedLayerMarginLeft = fixedLayerMarginTop = fixedLayerMarginRight = fixedLayerMarginBottom = 0;
marginLeft = marginTop = marginRight = marginBottom = 0;
zoomFactor = 1.0f;
isRTL = false;
}
/** This constructor is used by native code in AndroidJavaWrappers.cpp, be
* careful when modifying the signature.
*/
private ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop,
float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft,
float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom,
@ -57,16 +62,16 @@ public class ImmutableViewportMetrics {
aPageRectRight, aPageRectBottom, aCssPageRectLeft,
aCssPageRectTop, aCssPageRectRight, aCssPageRectBottom,
aViewportRectLeft, aViewportRectTop, aViewportRectRight,
aViewportRectBottom, 0.0f, 0.0f, 0.0f, 0.0f, aZoomFactor);
aViewportRectBottom, 0.0f, 0.0f, 0.0f, 0.0f, aZoomFactor, false);
}
private ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop,
float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft,
float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom,
float aViewportRectLeft, float aViewportRectTop, float aViewportRectRight,
float aViewportRectBottom, float aFixedLayerMarginLeft,
float aFixedLayerMarginTop, float aFixedLayerMarginRight,
float aFixedLayerMarginBottom, float aZoomFactor)
float aViewportRectBottom, float aMarginLeft,
float aMarginTop, float aMarginRight,
float aMarginBottom, float aZoomFactor, boolean aIsRTL)
{
pageRectLeft = aPageRectLeft;
pageRectTop = aPageRectTop;
@ -80,11 +85,12 @@ public class ImmutableViewportMetrics {
viewportRectTop = aViewportRectTop;
viewportRectRight = aViewportRectRight;
viewportRectBottom = aViewportRectBottom;
fixedLayerMarginLeft = aFixedLayerMarginLeft;
fixedLayerMarginTop = aFixedLayerMarginTop;
fixedLayerMarginRight = aFixedLayerMarginRight;
fixedLayerMarginBottom = aFixedLayerMarginBottom;
marginLeft = aMarginLeft;
marginTop = aMarginTop;
marginRight = aMarginRight;
marginBottom = aMarginBottom;
zoomFactor = aZoomFactor;
isRTL = aIsRTL;
}
public float getWidth() {
@ -96,17 +102,24 @@ public class ImmutableViewportMetrics {
}
public float getWidthWithoutMargins() {
return viewportRectRight - viewportRectLeft - fixedLayerMarginLeft - fixedLayerMarginRight;
return viewportRectRight - viewportRectLeft - marginLeft - marginRight;
}
public float getHeightWithoutMargins() {
return viewportRectBottom - viewportRectTop - fixedLayerMarginTop - fixedLayerMarginBottom;
return viewportRectBottom - viewportRectTop - marginTop - marginBottom;
}
public PointF getOrigin() {
return new PointF(viewportRectLeft, viewportRectTop);
}
public PointF getMarginOffset() {
if (isRTL) {
return new PointF(marginLeft - marginRight, marginTop);
}
return new PointF(marginLeft, marginTop);
}
public FloatSize getSize() {
return new FloatSize(viewportRectRight - viewportRectLeft, viewportRectBottom - viewportRectTop);
}
@ -130,14 +143,29 @@ public class ImmutableViewportMetrics {
return pageRectRight - pageRectLeft;
}
public float getPageWidthWithMargins() {
return (pageRectRight - pageRectLeft) + marginLeft + marginRight;
}
public float getPageHeight() {
return pageRectBottom - pageRectTop;
}
public float getPageHeightWithMargins() {
return (pageRectBottom - pageRectTop) + marginTop + marginBottom;
}
public RectF getCssPageRect() {
return new RectF(cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom);
}
public RectF getOverscroll() {
return new RectF(Math.max(0, pageRectLeft - viewportRectLeft),
Math.max(0, pageRectTop - viewportRectTop),
Math.max(0, viewportRectRight - pageRectRight),
Math.max(0, viewportRectBottom - pageRectBottom));
}
/*
* Returns the viewport metrics that represent a linear transition between "this" and "to" at
* time "t", which is on the scale [0, 1). This function interpolates all values stored in
@ -157,11 +185,12 @@ public class ImmutableViewportMetrics {
FloatUtils.interpolate(viewportRectTop, to.viewportRectTop, t),
FloatUtils.interpolate(viewportRectRight, to.viewportRectRight, t),
FloatUtils.interpolate(viewportRectBottom, to.viewportRectBottom, t),
FloatUtils.interpolate(fixedLayerMarginLeft, to.fixedLayerMarginLeft, t),
FloatUtils.interpolate(fixedLayerMarginTop, to.fixedLayerMarginTop, t),
FloatUtils.interpolate(fixedLayerMarginRight, to.fixedLayerMarginRight, t),
FloatUtils.interpolate(fixedLayerMarginBottom, to.fixedLayerMarginBottom, t),
FloatUtils.interpolate(zoomFactor, to.zoomFactor, t));
FloatUtils.interpolate(marginLeft, to.marginLeft, t),
FloatUtils.interpolate(marginTop, to.marginTop, t),
FloatUtils.interpolate(marginRight, to.marginRight, t),
FloatUtils.interpolate(marginBottom, to.marginBottom, t),
FloatUtils.interpolate(zoomFactor, to.zoomFactor, t),
t >= 0.5 ? to.isRTL : isRTL);
}
public ImmutableViewportMetrics setViewportSize(float width, float height) {
@ -173,8 +202,8 @@ public class ImmutableViewportMetrics {
pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
viewportRectLeft, viewportRectTop, viewportRectLeft + width, viewportRectTop + height,
fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
zoomFactor);
marginLeft, marginTop, marginRight, marginBottom,
zoomFactor, isRTL);
}
public ImmutableViewportMetrics setViewportOrigin(float newOriginX, float newOriginY) {
@ -182,8 +211,8 @@ public class ImmutableViewportMetrics {
pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
newOriginX, newOriginY, newOriginX + getWidth(), newOriginY + getHeight(),
fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
zoomFactor);
marginLeft, marginTop, marginRight, marginBottom,
zoomFactor, isRTL);
}
public ImmutableViewportMetrics setZoomFactor(float newZoomFactor) {
@ -191,28 +220,39 @@ public class ImmutableViewportMetrics {
pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom,
fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
newZoomFactor);
marginLeft, marginTop, marginRight, marginBottom,
newZoomFactor, isRTL);
}
public ImmutableViewportMetrics offsetViewportBy(float dx, float dy) {
return setViewportOrigin(viewportRectLeft + dx, viewportRectTop + dy);
}
public ImmutableViewportMetrics offsetViewportByAndClamp(float dx, float dy) {
if (isRTL) {
return setViewportOrigin(
Math.min(pageRectRight - getWidthWithoutMargins(), Math.max(viewportRectLeft + dx, pageRectLeft)),
Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeightWithoutMargins())));
}
return setViewportOrigin(
Math.max(pageRectLeft, Math.min(viewportRectLeft + dx, pageRectRight - getWidthWithoutMargins())),
Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeightWithoutMargins())));
}
public ImmutableViewportMetrics setPageRect(RectF pageRect, RectF cssPageRect) {
return new ImmutableViewportMetrics(
pageRect.left, pageRect.top, pageRect.right, pageRect.bottom,
cssPageRect.left, cssPageRect.top, cssPageRect.right, cssPageRect.bottom,
viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom,
fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
zoomFactor);
marginLeft, marginTop, marginRight, marginBottom,
zoomFactor, isRTL);
}
public ImmutableViewportMetrics setFixedLayerMargins(float left, float top, float right, float bottom) {
if (FloatUtils.fuzzyEquals(left, fixedLayerMarginLeft)
&& FloatUtils.fuzzyEquals(top, fixedLayerMarginTop)
&& FloatUtils.fuzzyEquals(right, fixedLayerMarginRight)
&& FloatUtils.fuzzyEquals(bottom, fixedLayerMarginBottom)) {
public ImmutableViewportMetrics setMargins(float left, float top, float right, float bottom) {
if (FloatUtils.fuzzyEquals(left, marginLeft)
&& FloatUtils.fuzzyEquals(top, marginTop)
&& FloatUtils.fuzzyEquals(right, marginRight)
&& FloatUtils.fuzzyEquals(bottom, marginBottom)) {
return this;
}
@ -220,14 +260,26 @@ public class ImmutableViewportMetrics {
pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom,
left, top, right, bottom, zoomFactor);
left, top, right, bottom, zoomFactor, isRTL);
}
public ImmutableViewportMetrics setFixedLayerMarginsFrom(ImmutableViewportMetrics fromMetrics) {
return setFixedLayerMargins(fromMetrics.fixedLayerMarginLeft,
fromMetrics.fixedLayerMarginTop,
fromMetrics.fixedLayerMarginRight,
fromMetrics.fixedLayerMarginBottom);
public ImmutableViewportMetrics setMarginsFrom(ImmutableViewportMetrics fromMetrics) {
return setMargins(fromMetrics.marginLeft,
fromMetrics.marginTop,
fromMetrics.marginRight,
fromMetrics.marginBottom);
}
public ImmutableViewportMetrics setIsRTL(boolean aIsRTL) {
if (isRTL == aIsRTL) {
return this;
}
return new ImmutableViewportMetrics(
pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom,
marginLeft, marginTop, marginRight, marginBottom, zoomFactor, aIsRTL);
}
/* This will set the zoom factor and re-scale page-size and viewport offset
@ -251,32 +303,33 @@ public class ImmutableViewportMetrics {
newPageRectLeft, newPageRectTop, newPageRectRight, newPageRectBottom,
cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
origin.x, origin.y, origin.x + getWidth(), origin.y + getHeight(),
fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
newZoomFactor);
marginLeft, marginTop, marginRight, marginBottom,
newZoomFactor, isRTL);
}
/** Clamps the viewport to remain within the page rect. */
private ImmutableViewportMetrics clamp(float marginLeft, float marginTop,
float marginRight, float marginBottom) {
RectF newViewport = getViewport();
PointF offset = getMarginOffset();
// The viewport bounds ought to never exceed the page bounds.
if (newViewport.right > pageRectRight + marginRight)
newViewport.offset((pageRectRight + marginRight) - newViewport.right, 0);
if (newViewport.left < pageRectLeft - marginLeft)
newViewport.offset((pageRectLeft - marginLeft) - newViewport.left, 0);
if (newViewport.right > pageRectRight + marginLeft + marginRight)
newViewport.offset((pageRectRight + marginLeft + marginRight) - newViewport.right, 0);
if (newViewport.left < pageRectLeft)
newViewport.offset(pageRectLeft - newViewport.left, 0);
if (newViewport.bottom > pageRectBottom + marginBottom)
newViewport.offset(0, (pageRectBottom + marginBottom) - newViewport.bottom);
if (newViewport.top < pageRectTop - marginTop)
newViewport.offset(0, (pageRectTop - marginTop) - newViewport.top);
if (newViewport.bottom > pageRectBottom + marginTop + marginBottom)
newViewport.offset(0, (pageRectBottom + marginTop + marginBottom) - newViewport.bottom);
if (newViewport.top < pageRectTop)
newViewport.offset(0, pageRectTop - newViewport.top);
return new ImmutableViewportMetrics(
pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
newViewport.left, newViewport.top, newViewport.right, newViewport.bottom,
fixedLayerMarginLeft, fixedLayerMarginTop, fixedLayerMarginRight, fixedLayerMarginBottom,
zoomFactor);
marginLeft, marginTop, marginRight, marginBottom,
zoomFactor, isRTL);
}
public ImmutableViewportMetrics clamp() {
@ -284,8 +337,8 @@ public class ImmutableViewportMetrics {
}
public ImmutableViewportMetrics clampWithMargins() {
return clamp(fixedLayerMarginLeft, fixedLayerMarginTop,
fixedLayerMarginRight, fixedLayerMarginBottom);
return clamp(marginLeft, marginTop,
marginRight, marginBottom);
}
public boolean fuzzyEquals(ImmutableViewportMetrics other) {
@ -293,9 +346,8 @@ public class ImmutableViewportMetrics {
// of the cssPageRectXXX values and the zoomFactor, except with more rounding
// error. Checking those is both inefficient and can lead to false negatives.
//
// This doesn't return false if the fixed layer margins differ as none
// of the users of this function are interested in the margins in that
// way.
// This doesn't return false if the margins differ as none of the users
// of this function are interested in the margins in that way.
return FloatUtils.fuzzyEquals(cssPageRectLeft, other.cssPageRectLeft)
&& FloatUtils.fuzzyEquals(cssPageRectTop, other.cssPageRectTop)
&& FloatUtils.fuzzyEquals(cssPageRectRight, other.cssPageRectRight)
@ -313,8 +365,8 @@ public class ImmutableViewportMetrics {
+ viewportRectRight + "," + viewportRectBottom + ") p=(" + pageRectLeft + ","
+ pageRectTop + "," + pageRectRight + "," + pageRectBottom + ") c=("
+ cssPageRectLeft + "," + cssPageRectTop + "," + cssPageRectRight + ","
+ cssPageRectBottom + ") m=(" + fixedLayerMarginLeft + ","
+ fixedLayerMarginTop + "," + fixedLayerMarginRight + ","
+ fixedLayerMarginBottom + ") z=" + zoomFactor;
+ cssPageRectBottom + ") m=(" + marginLeft + ","
+ marginTop + "," + marginRight + ","
+ marginBottom + ") z=" + zoomFactor + ", rtl=" + isRTL;
}
}

View File

@ -197,6 +197,11 @@ class JavaPanZoomController
if (state != mState) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PanZoom:StateChange", state.toString()));
mState = state;
// Let the target know we've finished with it (for now)
if (state == PanZoomState.NOTHING) {
mTarget.panZoomStopped();
}
}
}
@ -706,8 +711,7 @@ class JavaPanZoomController
}
private void scrollBy(float dx, float dy) {
ImmutableViewportMetrics scrolled = getMetrics().offsetViewportBy(dx, dy);
mTarget.setViewportMetrics(scrolled);
mTarget.scrollBy(dx, dy);
}
private void fling() {
@ -998,8 +1002,8 @@ class JavaPanZoomController
// Ensure minZoomFactor keeps the page at least as big as the viewport.
if (pageRect.width() > 0) {
float pageWidth = pageRect.width() +
viewportMetrics.fixedLayerMarginLeft +
viewportMetrics.fixedLayerMarginRight;
viewportMetrics.marginLeft +
viewportMetrics.marginRight;
float scaleFactor = viewport.width() / pageWidth;
minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
if (viewport.width() > pageWidth)
@ -1007,8 +1011,8 @@ class JavaPanZoomController
}
if (pageRect.height() > 0) {
float pageHeight = pageRect.height() +
viewportMetrics.fixedLayerMarginTop +
viewportMetrics.fixedLayerMarginBottom;
viewportMetrics.marginTop +
viewportMetrics.marginBottom;
float scaleFactor = viewport.height() / pageHeight;
minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
if (viewport.height() > pageHeight)
@ -1043,15 +1047,9 @@ class JavaPanZoomController
@Override
protected float getViewportLength() { return getMetrics().getWidth(); }
@Override
protected float getPageStart() {
ImmutableViewportMetrics metrics = getMetrics();
return metrics.pageRectLeft - metrics.fixedLayerMarginLeft;
}
protected float getPageStart() { return getMetrics().pageRectLeft; }
@Override
protected float getPageLength() {
ImmutableViewportMetrics metrics = getMetrics();
return metrics.getPageWidth() + metrics.fixedLayerMarginLeft + metrics.fixedLayerMarginRight;
}
protected float getPageLength() { return getMetrics().getPageWidthWithMargins(); }
}
private class AxisY extends Axis {
@ -1061,15 +1059,9 @@ class JavaPanZoomController
@Override
protected float getViewportLength() { return getMetrics().getHeight(); }
@Override
protected float getPageStart() {
ImmutableViewportMetrics metrics = getMetrics();
return metrics.pageRectTop - metrics.fixedLayerMarginTop;
}
protected float getPageStart() { return getMetrics().pageRectTop; }
@Override
protected float getPageLength() {
ImmutableViewportMetrics metrics = getMetrics();
return metrics.getPageHeight() + metrics.fixedLayerMarginTop + metrics.fixedLayerMarginBottom;
}
protected float getPageLength() { return getMetrics().getPageHeightWithMargins(); }
}
/*

View File

@ -7,6 +7,7 @@ package org.mozilla.gecko.gfx;
import org.mozilla.gecko.util.FloatUtils;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@ -176,15 +177,17 @@ public abstract class Layer {
public final RectF viewport;
public final RectF pageRect;
public final float zoomFactor;
public final PointF offset;
public final int positionHandle;
public final int textureHandle;
public final FloatBuffer coordBuffer;
public RenderContext(RectF aViewport, RectF aPageRect, float aZoomFactor,
public RenderContext(RectF aViewport, RectF aPageRect, float aZoomFactor, PointF aOffset,
int aPositionHandle, int aTextureHandle, FloatBuffer aCoordBuffer) {
viewport = aViewport;
pageRect = aPageRect;
zoomFactor = aZoomFactor;
offset = aOffset;
positionHandle = aPositionHandle;
textureHandle = aTextureHandle;
coordBuffer = aCoordBuffer;
@ -196,7 +199,8 @@ public abstract class Layer {
}
return RectUtils.fuzzyEquals(viewport, other.viewport)
&& RectUtils.fuzzyEquals(pageRect, other.pageRect)
&& FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor);
&& FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor)
&& FloatUtils.fuzzyEquals(offset, other.offset);
}
}
}

View File

@ -0,0 +1,186 @@
/* -*- 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.gfx;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.util.FloatUtils;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.SystemClock;
import android.util.Log;
import android.view.animation.DecelerateInterpolator;
import java.util.Timer;
import java.util.TimerTask;
public class LayerMarginsAnimator {
private static final String LOGTAG = "GeckoLayerMarginsAnimator";
private static final float MS_PER_FRAME = 1000.0f / 60.0f;
private static final long MARGIN_ANIMATION_DURATION = 250;
/* This rect stores the maximum value margins can grow to when scrolling */
private final RectF mMaxMargins;
/* If this boolean is true, scroll changes will not affect margins */
private boolean mMarginsPinned;
/* The timer that handles showing/hiding margins */
private Timer mAnimationTimer;
/* This interpolator is used for the above mentioned animation */
private final DecelerateInterpolator mInterpolator;
/* The GeckoLayerClient whose margins will be animated */
private final GeckoLayerClient mTarget;
public LayerMarginsAnimator(GeckoLayerClient aTarget) {
// Assign member variables from parameters
mTarget = aTarget;
// Create other member variables
mMaxMargins = new RectF();
mInterpolator = new DecelerateInterpolator();
}
/**
* Sets the maximum values for margins to grow to, in pixels.
*/
public synchronized void setMaxMargins(float left, float top, float right, float bottom) {
mMaxMargins.set(left, top, right, bottom);
// Update the Gecko-side global for fixed viewport margins.
GeckoAppShell.sendEventToGecko(
GeckoEvent.createBroadcastEvent("Viewport:FixedMarginsChanged",
"{ \"top\" : " + top + ", \"right\" : " + right
+ ", \"bottom\" : " + bottom + ", \"left\" : " + left + " }"));
}
private void animateMargins(final float left, final float top, final float right, final float bottom, boolean immediately) {
if (mAnimationTimer != null) {
mAnimationTimer.cancel();
mAnimationTimer = null;
}
if (immediately) {
ImmutableViewportMetrics newMetrics = mTarget.getViewportMetrics().setMargins(left, top, right, bottom);
mTarget.forceViewportMetrics(newMetrics, true, true);
return;
}
ImmutableViewportMetrics metrics = mTarget.getViewportMetrics();
final long startTime = SystemClock.uptimeMillis();
final float startLeft = metrics.marginLeft;
final float startTop = metrics.marginTop;
final float startRight = metrics.marginRight;
final float startBottom = metrics.marginBottom;
mAnimationTimer = new Timer("Margin Animation Timer");
mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
float progress = mInterpolator.getInterpolation(
Math.min(1.0f, (SystemClock.uptimeMillis() - startTime)
/ (float)MARGIN_ANIMATION_DURATION));
synchronized(mTarget.getLock()) {
ImmutableViewportMetrics oldMetrics = mTarget.getViewportMetrics();
ImmutableViewportMetrics newMetrics = oldMetrics.setMargins(
FloatUtils.interpolate(startLeft, left, progress),
FloatUtils.interpolate(startTop, top, progress),
FloatUtils.interpolate(startRight, right, progress),
FloatUtils.interpolate(startBottom, bottom, progress));
PointF oldOffset = oldMetrics.getMarginOffset();
PointF newOffset = newMetrics.getMarginOffset();
newMetrics =
newMetrics.offsetViewportByAndClamp(newOffset.x - oldOffset.x,
newOffset.y - oldOffset.y);
if (progress >= 1.0f) {
if (mAnimationTimer != null) {
mAnimationTimer.cancel();
mAnimationTimer = null;
}
// Force a redraw and update Gecko
mTarget.forceViewportMetrics(newMetrics, true, true);
} else {
mTarget.forceViewportMetrics(newMetrics, false, false);
}
}
}
}, 0, (int)MS_PER_FRAME);
}
/**
* Exposes the margin area by growing the margin components of the current
* metrics to the values set in setMaxMargins.
*/
public synchronized void showMargins(boolean immediately) {
animateMargins(mMaxMargins.left, mMaxMargins.top, mMaxMargins.right, mMaxMargins.bottom, immediately);
}
public synchronized void hideMargins(boolean immediately) {
animateMargins(0, 0, 0, 0, immediately);
}
public void setMarginsPinned(boolean pin) {
mMarginsPinned = pin;
}
/*
* Taking maximum margins into account, offsets the margins and then the
* viewport origin and returns the modified metrics.
*/
ImmutableViewportMetrics scrollBy(ImmutableViewportMetrics aMetrics, float aDx, float aDy) {
// Make sure to cancel any margin animations when scrolling begins
if (mAnimationTimer != null) {
mAnimationTimer.cancel();
mAnimationTimer = null;
}
float newMarginLeft = aMetrics.marginLeft;
float newMarginTop = aMetrics.marginTop;
float newMarginRight = aMetrics.marginRight;
float newMarginBottom = aMetrics.marginBottom;
// Only alter margins if the toolbar isn't pinned
if (!mMarginsPinned) {
RectF overscroll = aMetrics.getOverscroll();
if (aDx >= 0) {
// Scrolling right.
float marginDx = Math.max(0, aDx - overscroll.left);
newMarginLeft = aMetrics.marginLeft - Math.min(marginDx, aMetrics.marginLeft);
newMarginRight = aMetrics.marginRight + Math.min(marginDx, mMaxMargins.right - aMetrics.marginRight);
aDx -= aMetrics.marginLeft - newMarginLeft;
} else {
// Scrolling left.
float marginDx = Math.max(0, -aDx - overscroll.right);
newMarginLeft = aMetrics.marginLeft + Math.min(marginDx, mMaxMargins.left - aMetrics.marginLeft);
newMarginRight = aMetrics.marginRight - Math.min(marginDx, aMetrics.marginRight);
aDx -= aMetrics.marginLeft - newMarginLeft;
}
if (aDy >= 0) {
// Scrolling down.
float marginDy = Math.max(0, aDy - overscroll.top);
newMarginTop = aMetrics.marginTop - Math.min(marginDy, aMetrics.marginTop);
newMarginBottom = aMetrics.marginBottom + Math.min(marginDy, mMaxMargins.bottom - aMetrics.marginBottom);
aDy -= aMetrics.marginTop - newMarginTop;
} else {
// Scrolling up.
float marginDy = Math.max(0, -aDy - overscroll.bottom);
newMarginTop = aMetrics.marginTop + Math.min(marginDy, mMaxMargins.top - aMetrics.marginTop);
newMarginBottom = aMetrics.marginBottom - Math.min(marginDy, aMetrics.marginBottom);
aDy -= aMetrics.marginTop - newMarginTop;
}
}
return aMetrics.setMargins(newMarginLeft, newMarginTop, newMarginRight, newMarginBottom).offsetViewportBy(aDx, aDy);
}
}

View File

@ -19,6 +19,7 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.opengl.GLES20;
@ -277,21 +278,23 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
return pixelBuffer;
}
private RenderContext createScreenContext(ImmutableViewportMetrics metrics) {
private RenderContext createScreenContext(ImmutableViewportMetrics metrics, PointF offset) {
RectF viewport = new RectF(0.0f, 0.0f, metrics.getWidth(), metrics.getHeight());
RectF pageRect = new RectF(metrics.getPageRect());
return createContext(viewport, pageRect, 1.0f);
RectF pageRect = metrics.getPageRect();
return createContext(viewport, pageRect, 1.0f, offset);
}
private RenderContext createPageContext(ImmutableViewportMetrics metrics) {
Rect viewport = RectUtils.round(metrics.getViewport());
private RenderContext createPageContext(ImmutableViewportMetrics metrics, PointF offset) {
RectF viewport = metrics.getViewport();
RectF pageRect = metrics.getPageRect();
float zoomFactor = metrics.zoomFactor;
return createContext(new RectF(viewport), pageRect, zoomFactor);
return createContext(new RectF(RectUtils.round(viewport)), pageRect, zoomFactor, offset);
}
private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor) {
return new RenderContext(viewport, pageRect, zoomFactor, mPositionHandle, mTextureHandle,
private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor, PointF offset) {
return new RenderContext(viewport, pageRect, zoomFactor, offset, mPositionHandle, mTextureHandle,
mCoordBuffer);
}
@ -410,17 +413,27 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
private boolean mUpdated;
private final Rect mPageRect;
private final Rect mAbsolutePageRect;
private final PointF mRenderOffset;
public Frame(ImmutableViewportMetrics metrics) {
mFrameMetrics = metrics;
mPageContext = createPageContext(metrics);
mScreenContext = createScreenContext(metrics);
Point origin = PointUtils.round(mFrameMetrics.getOrigin());
Rect pageRect = RectUtils.round(mFrameMetrics.getPageRect());
mAbsolutePageRect = new Rect(pageRect);
// Work out the offset due to margins
Layer rootLayer = mView.getLayerClient().getRoot();
mRenderOffset = mFrameMetrics.getMarginOffset();
float scaleDiff = mFrameMetrics.zoomFactor / rootLayer.getResolution();
mRenderOffset.set(mRenderOffset.x * scaleDiff,
mRenderOffset.y * scaleDiff);
mPageContext = createPageContext(metrics, mRenderOffset);
mScreenContext = createScreenContext(metrics, mRenderOffset);
RectF pageRect = mFrameMetrics.getPageRect();
mAbsolutePageRect = RectUtils.round(pageRect);
PointF origin = mFrameMetrics.getOrigin();
pageRect.offset(-origin.x, -origin.y);
mPageRect = pageRect;
mPageRect = RectUtils.round(pageRect);
}
private void setScissorRect() {
@ -438,8 +451,11 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
int right = Math.min(screenSize.width, rect.right);
int bottom = Math.min(screenSize.height, rect.bottom);
return new Rect(left, screenSize.height - bottom, right,
(screenSize.height - bottom) + (bottom - top));
Rect scissorRect = new Rect(left, screenSize.height - bottom, right,
(screenSize.height - bottom) + (bottom - top));
scissorRect.offset(Math.round(-mRenderOffset.x), Math.round(-mRenderOffset.y));
return scissorRect;
}
/** This function is invoked via JNI; be careful when modifying signature. */
@ -544,7 +560,9 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
// Draw the drop shadow, if we need to.
if (!new RectF(mAbsolutePageRect).contains(mFrameMetrics.getViewport()))
RectF offsetAbsPageRect = new RectF(mAbsolutePageRect);
offsetAbsPageRect.offset(mRenderOffset.x, mRenderOffset.y);
if (!offsetAbsPageRect.contains(mFrameMetrics.getViewport()))
mShadowLayer.draw(mPageContext);
}

View File

@ -47,6 +47,7 @@ public class LayerView extends FrameLayout {
private GeckoLayerClient mLayerClient;
private PanZoomController mPanZoomController;
private LayerMarginsAnimator mMarginsAnimator;
private GLController mGLController;
private InputConnectionHandler mInputConnectionHandler;
private LayerRenderer mRenderer;
@ -100,6 +101,7 @@ public class LayerView extends FrameLayout {
public void initializeView(EventDispatcher eventDispatcher) {
mLayerClient = new GeckoLayerClient(getContext(), this, eventDispatcher);
mPanZoomController = mLayerClient.getPanZoomController();
mMarginsAnimator = mLayerClient.getLayerMarginsAnimator();
mRenderer = new LayerRenderer(this);
mInputConnectionHandler = null;
@ -207,6 +209,7 @@ public class LayerView extends FrameLayout {
public GeckoLayerClient getLayerClient() { return mLayerClient; }
public PanZoomController getPanZoomController() { return mPanZoomController; }
public LayerMarginsAnimator getLayerMarginsAnimator() { return mMarginsAnimator; }
public ImmutableViewportMetrics getViewportMetrics() {
return mLayerClient.getViewportMetrics();
@ -236,6 +239,10 @@ public class LayerView extends FrameLayout {
mLayerClient.setZoomConstraints(constraints);
}
public void setIsRTL(boolean aIsRTL) {
mLayerClient.setIsRTL(aIsRTL);
}
public void setInputConnectionHandler(InputConnectionHandler inputConnectionHandler) {
mInputConnectionHandler = inputConnectionHandler;
mLayerClient.forceRedraw();

View File

@ -75,8 +75,8 @@ public class NinePatchTileLayer extends TileLayer {
float tileX, float tileY, float tileWidth, float tileHeight) {
RectF viewport = context.viewport;
float viewportHeight = viewport.height();
float drawX = tileX - viewport.left;
float drawY = viewportHeight - (tileY + tileHeight - viewport.top);
float drawX = tileX - viewport.left - context.offset.x;
float drawY = viewportHeight - (tileY + tileHeight - viewport.top) - context.offset.y;
float[] coords = {
//x, y, z, texture_x, texture_y

View File

@ -16,6 +16,8 @@ public interface PanZoomTarget {
public void setAnimationTarget(ImmutableViewportMetrics viewport);
public void setViewportMetrics(ImmutableViewportMetrics viewport);
public void scrollBy(float dx, float dy);
public void panZoomStopped();
/** This triggers an (asynchronous) viewport update/redraw. */
public void forceRedraw();

View File

@ -188,6 +188,7 @@ public class ScrollbarLayer extends TileLayer {
float viewHeight = context.viewport.height();
mBarRectF.set(mBarRect.left, viewHeight - mBarRect.top, mBarRect.right, viewHeight - mBarRect.bottom);
mBarRectF.offset(context.offset.x, -context.offset.y);
// We take a 1-pixel slice from the center of the image and scale it to become the bar
fillRectCoordBuffer(mCoords, mBarRectF, viewWidth, viewHeight, mBodyTexCoords, mTexWidth, mTexHeight);

View File

@ -13,6 +13,8 @@ public class ViewTransform {
public float fixedLayerMarginTop;
public float fixedLayerMarginRight;
public float fixedLayerMarginBottom;
public float offsetX;
public float offsetY;
public ViewTransform(float inX, float inY, float inScale) {
x = inX;
@ -22,6 +24,8 @@ public class ViewTransform {
fixedLayerMarginTop = 0;
fixedLayerMarginRight = 0;
fixedLayerMarginBottom = 0;
offsetX = 0;
offsetY = 0;
}
}

View File

@ -3462,6 +3462,8 @@ Tab.prototype = {
if (aMetadata.maxZoom > 0)
aMetadata.maxZoom *= scaleRatio;
aMetadata.isRTL = this.browser.contentDocument.documentElement.dir == "rtl";
ViewportHandler.setMetadataForDocument(this.browser.contentDocument, aMetadata);
this.updateViewportSize(gScreenWidth, aInitialLoad);
this.sendViewportMetadata();
@ -3590,6 +3592,7 @@ Tab.prototype = {
defaultZoom: metadata.defaultZoom || metadata.scaleRatio,
minZoom: metadata.minZoom || 0,
maxZoom: metadata.maxZoom || 0,
isRTL: metadata.isRTL,
tabID: this.id
});
},
@ -5111,6 +5114,8 @@ var ViewportHandler = {
(!widthStr && (heightStr == "device-height" || scale == 1.0)));
}
let isRTL = aWindow.document.documentElement.dir == "rtl";
return new ViewportMetadata({
defaultZoom: scale,
minZoom: minScale,
@ -5119,7 +5124,8 @@ var ViewportHandler = {
height: height,
autoSize: autoSize,
allowZoom: allowZoom,
isSpecified: hasMetaViewport
isSpecified: hasMetaViewport,
isRTL: isRTL
});
},
@ -5191,6 +5197,7 @@ function ViewportMetadata(aMetadata = {}) {
this.allowZoom = ("allowZoom" in aMetadata) ? aMetadata.allowZoom : true;
this.isSpecified = ("isSpecified" in aMetadata) ? aMetadata.isSpecified : false;
this.scaleRatio = ViewportHandler.getScaleRatio();
this.isRTL = ("isRTL" in aMetadata) ? aMetadata.isRTL : false;
Object.seal(this);
}
@ -5204,6 +5211,7 @@ ViewportMetadata.prototype = {
allowZoom: null,
isSpecified: null,
scaleRatio: null,
isRTL: null,
};

View File

@ -2109,14 +2109,15 @@ AndroidBridge::SetPageRect(const gfx::Rect& aCssPageRect)
void
AndroidBridge::SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
gfx::Margin& aFixedLayerMargins)
gfx::Margin& aFixedLayerMargins, float& aOffsetX, float& aOffsetY)
{
AndroidGeckoLayerClient *client = mLayerClient;
if (!client)
return;
client->SyncViewportInfo(aDisplayPort, aDisplayResolution, aLayersUpdated,
aScrollOffset, aScaleX, aScaleY, aFixedLayerMargins);
aScrollOffset, aScaleX, aScaleY, aFixedLayerMargins,
aOffsetX, aOffsetY);
}
AndroidBridge::AndroidBridge()

View File

@ -342,7 +342,7 @@ public:
void SetPageRect(const gfx::Rect& aCssPageRect);
void SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
gfx::Margin& aFixedLayerMargins);
gfx::Margin& aFixedLayerMargins, float& aOffsetX, float& aOffsetY);
void AddPluginView(jobject view, const gfxRect& rect, bool isFullScreen);
void RemovePluginView(jobject view, bool isFullScreen);

View File

@ -112,6 +112,8 @@ jfieldID AndroidViewTransform::jFixedLayerMarginLeft = 0;
jfieldID AndroidViewTransform::jFixedLayerMarginTop = 0;
jfieldID AndroidViewTransform::jFixedLayerMarginRight = 0;
jfieldID AndroidViewTransform::jFixedLayerMarginBottom = 0;
jfieldID AndroidViewTransform::jOffsetXField = 0;
jfieldID AndroidViewTransform::jOffsetYField = 0;
jclass AndroidProgressiveUpdateData::jProgressiveUpdateDataClass = 0;
jfieldID AndroidProgressiveUpdateData::jXField = 0;
@ -389,6 +391,8 @@ AndroidViewTransform::InitViewTransformClass(JNIEnv *jEnv)
jFixedLayerMarginTop = getField("fixedLayerMarginTop", "F");
jFixedLayerMarginRight = getField("fixedLayerMarginRight", "F");
jFixedLayerMarginBottom = getField("fixedLayerMarginBottom", "F");
jOffsetXField = getField("offsetX", "F");
jOffsetYField = getField("offsetY", "F");
}
void
@ -824,7 +828,7 @@ AndroidGeckoLayerClient::SetPageRect(const gfx::Rect& aCssPageRect)
void
AndroidGeckoLayerClient::SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
gfx::Margin& aFixedLayerMargins)
gfx::Margin& aFixedLayerMargins, float& aOffsetX, float& aOffsetY)
{
NS_ASSERTION(!isNull(), "SyncViewportInfo called on null layer client!");
JNIEnv *env = GetJNIForThread(); // this is called on the compositor thread
@ -848,6 +852,9 @@ AndroidGeckoLayerClient::SyncViewportInfo(const nsIntRect& aDisplayPort, float a
aScrollOffset = nsIntPoint(viewTransform.GetX(env), viewTransform.GetY(env));
aScaleX = aScaleY = viewTransform.GetScale(env);
viewTransform.GetFixedLayerMargins(env, aFixedLayerMargins);
aOffsetX = viewTransform.GetOffsetX(env);
aOffsetY = viewTransform.GetOffsetY(env);
}
bool
@ -1094,6 +1101,22 @@ AndroidViewTransform::GetFixedLayerMargins(JNIEnv *env, gfx::Margin &aFixedLayer
aFixedLayerMargins.left = env->GetFloatField(wrapped_obj, jFixedLayerMarginLeft);
}
float
AndroidViewTransform::GetOffsetX(JNIEnv *env)
{
if (!env)
return 0.0f;
return env->GetFloatField(wrapped_obj, jOffsetXField);
}
float
AndroidViewTransform::GetOffsetY(JNIEnv *env)
{
if (!env)
return 0.0f;
return env->GetFloatField(wrapped_obj, jOffsetYField);
}
float
AndroidProgressiveUpdateData::GetX(JNIEnv *env)
{

View File

@ -195,6 +195,8 @@ public:
float GetY(JNIEnv *env);
float GetScale(JNIEnv *env);
void GetFixedLayerMargins(JNIEnv *env, gfx::Margin &aFixedLayerMargins);
float GetOffsetX(JNIEnv *env);
float GetOffsetY(JNIEnv *env);
private:
static jclass jViewTransformClass;
@ -205,6 +207,8 @@ private:
static jfieldID jFixedLayerMarginTop;
static jfieldID jFixedLayerMarginRight;
static jfieldID jFixedLayerMarginBottom;
static jfieldID jOffsetXField;
static jfieldID jOffsetYField;
};
class AndroidProgressiveUpdateData : public WrappedJavaObject {
@ -266,7 +270,7 @@ public:
void SetPageRect(const gfx::Rect& aCssPageRect);
void SyncViewportInfo(const nsIntRect& aDisplayPort, float aDisplayResolution, bool aLayersUpdated,
nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY,
gfx::Margin& aFixedLayerMargins);
gfx::Margin& aFixedLayerMargins, float& aOffsetX, float& aOffsetY);
bool ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution, bool aDrawingCritical, gfx::Rect& aViewport, float& aScaleX, float& aScaleY);
bool CreateFrame(AutoLocalJNIFrame *jniFrame, AndroidLayerRendererFrame& aFrame);
bool ActivateProgram(AutoLocalJNIFrame *jniFrame);