android/experimental/LOAndroid3/AndroidManifest.xml | 3 android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java | 34 android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java | 39 android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java | 53 android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java | 5 android/experimental/LOAndroid3/src/java/org/libreoffice/TileProvider.java | 5 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java | 97 + android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java | 70 + android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java | 116 - android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java | 384 ++--- android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java | 378 +++-- android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java | 60 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java | 66 - android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/NinePatchTileLayer.java | 99 + android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java | 253 +++ android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java | 544 ++++---- android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/SingleTileLayer.java | 119 + android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TileLayer.java | 119 - android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java | 173 -- android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/VirtualLayer.java | 80 - android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java | 653 ++++------ 21 files changed, 1830 insertions(+), 1520 deletions(-)
New commits: commit f02cef962b4f930eb5637dea1bb9d8c382839286 Author: Tomaž Vajngerl <tomaz.vajng...@collabora.com> Date: Thu Sep 18 22:22:08 2014 +0200 android: convert to ImmutableViewportMetrics Change-Id: Idd5e604541577f6b812a971e585cee9b089d2b4b diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java index c5918e69..c12170f 100644 --- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java +++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java @@ -7,6 +7,7 @@ import android.util.Log; import org.mozilla.gecko.gfx.FloatSize; import org.mozilla.gecko.gfx.GeckoLayerClient; +import org.mozilla.gecko.gfx.ImmutableViewportMetrics; import org.mozilla.gecko.gfx.SubTile; import org.mozilla.gecko.gfx.ViewportMetrics; @@ -29,9 +30,9 @@ public class LOKitThread extends Thread { mInputFile = inputFile; } - RectF normlizeRect(ViewportMetrics metrics) { + RectF normlizeRect(ImmutableViewportMetrics metrics) { RectF rect = metrics.getViewport(); - float zoomFactor = metrics.getZoomFactor(); + float zoomFactor = metrics.zoomFactor; return new RectF(rect.left / zoomFactor, rect.top / zoomFactor, rect.right / zoomFactor, rect.bottom / zoomFactor); } @@ -68,7 +69,7 @@ public class LOKitThread extends Thread { GeckoLayerClient layerClient = mApplication.getLayerClient(); layerClient.beginDrawing(mViewportMetrics); - ViewportMetrics metrics = mApplication.getLayerController().getViewportMetrics(); + ImmutableViewportMetrics metrics = mApplication.getLayerController().getViewportMetrics(); RectF viewport = normlizeRect(metrics); Rect rect = inflate(roundToTileSize(viewport, TILE_SIZE), TILE_SIZE); diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java index 1ebb9a1..e7a9059 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java @@ -149,7 +149,7 @@ public class GeckoLayerClient { mGeckoViewport = mNewGeckoViewport; mGeckoViewport.setSize(viewportSize); - PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin(); + PointF displayportOrigin = mGeckoViewport.getOrigin(); RectF position = mGeckoViewport.getViewport(); mTileLayer.setPosition(RectUtils.round(position)); mTileLayer.setResolution(mGeckoViewport.getZoomFactor()); @@ -288,7 +288,7 @@ public class GeckoLayerClient { viewportMetrics.setViewport(viewportMetrics.getClampedViewport()); - mDisplayPort = calculateDisplayPort(new ImmutableViewportMetrics(mLayerController.getViewportMetrics())); + mDisplayPort = calculateDisplayPort(mLayerController.getViewportMetrics()); LOKitShell.sendEvent(LOEvent.viewport(viewportMetrics)); if (mViewportSizeChanged) { diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java index 9c497f7..e9d0cb6 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java @@ -70,7 +70,20 @@ public class LayerController { private Layer mRootLayer; /* The root layer. */ private LayerView mView; /* The main rendering view. */ private Context mContext; /* The current context. */ - private ViewportMetrics mViewportMetrics; /* The current viewport metrics. */ + + /* This is volatile so that we can read and write to it from different threads. + * We avoid synchronization to make getting the viewport metrics from + * the compositor as cheap as possible. The viewport is immutable so + * we don't need to worry about anyone mutating it while we're reading from it. + * Specifically: + * 1) reading mViewportMetrics from any thread is fine without synchronization + * 2) writing to mViewportMetrics requires synchronizing on the layer controller object + * 3) whenver reading multiple fields from mViewportMetrics without synchronization (i.e. in + * case 1 above) you should always frist grab a local copy of the reference, and then use + * that because mViewportMetrics might get reassigned in between reading the different + * fields. */ + private volatile ImmutableViewportMetrics mViewportMetrics; /* The current viewport metrics. */ + private boolean mWaitForTouchListeners; private PanZoomController mPanZoomController; @@ -84,7 +97,7 @@ public class LayerController { /* The new color for the checkerboard. */ private int mCheckerboardColor; - private boolean mCheckerboardShouldShowChecks; + private boolean mCheckerboardShouldShowChecks = true; private boolean mForceRedraw; @@ -113,10 +126,9 @@ public class LayerController { mContext = context; mForceRedraw = true; - mViewportMetrics = new ViewportMetrics(); + mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics()); mPanZoomController = new PanZoomController(this); mView = new LayerView(context, this); - mCheckerboardShouldShowChecks = true; } public void onDestroy() { @@ -136,7 +148,7 @@ public class LayerController { public Layer getRoot() { return mRootLayer; } public LayerView getView() { return mView; } public Context getContext() { return mContext; } - public ViewportMetrics getViewportMetrics() { return mViewportMetrics; } + public ImmutableViewportMetrics getViewportMetrics() { return mViewportMetrics; } public RectF getViewport() { return mViewportMetrics.getViewport(); @@ -155,7 +167,7 @@ public class LayerController { } public float getZoomFactor() { - return mViewportMetrics.getZoomFactor(); + return mViewportMetrics.zoomFactor; } public Bitmap getBackgroundPattern() { return getDrawable("background"); } @@ -185,13 +197,49 @@ public class LayerController { * result in an infinite loop. */ public void setViewportSize(FloatSize size) { + // Resize the viewport, and modify its zoom factor so that the page retains proportionally + // zoomed relative to the screen. ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); + float oldHeight = viewportMetrics.getSize().height; + float oldWidth = viewportMetrics.getSize().width; + float oldZoomFactor = viewportMetrics.getZoomFactor(); viewportMetrics.setSize(size); - mViewportMetrics = new ViewportMetrics(viewportMetrics); + + // if the viewport got larger (presumably because the vkb went away), and the page + // is smaller than the new viewport size, increase the page size so that the panzoomcontroller + // doesn't zoom in to make it fit (bug 718270). this page size change is in anticipation of + // gecko increasing the page size to match the new viewport size, which will happen the next + // time we get a draw update. + if (size.width >= oldWidth && size.height >= oldHeight) { + FloatSize pageSize = viewportMetrics.getPageSize(); + if (pageSize.width < size.width || pageSize.height < size.height) { + viewportMetrics.setPageSize(new FloatSize(Math.max(pageSize.width, size.width), + Math.max(pageSize.height, size.height))); + } + } + + // For rotations, we want the focus point to be at the top left. + boolean rotation = (size.width > oldWidth && size.height < oldHeight) || + (size.width < oldWidth && size.height > oldHeight); + PointF newFocus; + if (rotation) { + newFocus = new PointF(0, 0); + } else { + newFocus = new PointF(size.width / 2.0f, size.height / 2.0f); + } + float newZoomFactor = size.width * oldZoomFactor / oldWidth; + viewportMetrics.scaleTo(newZoomFactor, newFocus); + mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); + + setForceRedraw(); if (mLayerClient != null) { mLayerClient.viewportSizeChanged(); + notifyLayerClientOfGeometryChange(); } + + mPanZoomController.abortAnimation(); + mView.requestRender(); } /** Scrolls the viewport by the given offset. You must hold the monitor while calling this. */ @@ -200,7 +248,7 @@ public class LayerController { PointF origin = viewportMetrics.getOrigin(); origin.offset(point.x, point.y); viewportMetrics.setOrigin(origin); - mViewportMetrics = new ViewportMetrics(viewportMetrics); + mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); notifyLayerClientOfGeometryChange(); mView.requestRender(); @@ -213,7 +261,7 @@ public class LayerController { ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); viewportMetrics.setPageSize(size); - mViewportMetrics = new ViewportMetrics(viewportMetrics); + mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); // Page size is owned by the layer client, so no need to notify it of // this change. @@ -233,7 +281,7 @@ public class LayerController { * while calling this. */ public void setViewportMetrics(ViewportMetrics viewport) { - mViewportMetrics = new ViewportMetrics(viewport); + mViewportMetrics = new ImmutableViewportMetrics(viewport); Log.d(LOGTAG, "setViewportMetrics: " + mViewportMetrics); mView.requestRender(); } @@ -245,7 +293,7 @@ public class LayerController { public void scaleWithFocus(float zoomFactor, PointF focus) { ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); viewportMetrics.scaleTo(zoomFactor, focus); - mViewportMetrics = new ViewportMetrics(viewportMetrics); + mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); Log.d(LOGTAG, "scaleWithFocus: " + mViewportMetrics + "; zf=" + zoomFactor); // We assume the zoom level will only be modified by the @@ -317,31 +365,26 @@ public class LayerController { * Converts a point from layer view coordinates to layer coordinates. In other words, given a * point measured in pixels from the top left corner of the layer view, returns the point in * pixels measured from the top left corner of the root layer, in the coordinate system of the - * layer itself (CSS pixels). This method is used as part of the process of translating touch - * events to Gecko's coordinate system. + * layer itself. This method is used by the viewport controller as part of the process of + * translating touch events to Gecko's coordinate system. */ public PointF convertViewPointToLayerPoint(PointF viewPoint) { if (mRootLayer == null) return null; - ViewportMetrics viewportMetrics = mViewportMetrics; + ImmutableViewportMetrics viewportMetrics = mViewportMetrics; + // Undo the transforms. PointF origin = viewportMetrics.getOrigin(); PointF newPoint = new PointF(origin.x, origin.y); - float zoom = viewportMetrics.getZoomFactor(); + float zoom = viewportMetrics.zoomFactor; + viewPoint.x /= zoom; + viewPoint.y /= zoom; + newPoint.offset(viewPoint.x, viewPoint.y); Rect rootPosition = mRootLayer.getPosition(); - float rootScale = mRootLayer.getResolution(); - - // viewPoint + origin 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. - // rootPosition / rootScale 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 - // the current Gecko coordinate in CSS pixels. - PointF layerPoint = new PointF( - ((viewPoint.x + origin.x) / zoom) - (rootPosition.left / rootScale), - ((viewPoint.y + origin.y) / zoom) - (rootPosition.top / rootScale)); - - return layerPoint; + newPoint.offset(-rootPosition.left, -rootPosition.top); + + return newPoint; } /* diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java index 44973bf..6ff9a8a 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java @@ -286,7 +286,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer { * Called whenever a new frame is about to be drawn. */ public void onDrawFrame(GL10 gl) { - Frame frame = createFrame(new ImmutableViewportMetrics(mView.getController().getViewportMetrics())); + Frame frame = createFrame(mView.getController().getViewportMetrics()); synchronized (mView.getController()) { frame.beginDrawing(); frame.drawBackground(); diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java index d98b474..8c3004e 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java @@ -53,14 +53,10 @@ import org.mozilla.gecko.util.FloatUtils; */ public class ViewportMetrics { private static final String LOGTAG = "GeckoViewportMetrics"; - private static final float MAX_BIAS = 0.8f; + private FloatSize mPageSize; private RectF mViewportRect; - private PointF mViewportOffset; private float mZoomFactor; - // A scale from -1,-1 to 1,1 that represents what edge of the displayport - // we want the viewport to be biased towards. - private PointF mViewportBias; public ViewportMetrics() { DisplayMetrics metrics = new DisplayMetrics(); @@ -68,119 +64,52 @@ public class ViewportMetrics { mPageSize = new FloatSize(metrics.widthPixels, metrics.heightPixels); mViewportRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels); - mViewportOffset = new PointF(0, 0); mZoomFactor = 1.0f; - mViewportBias = new PointF(0.0f, 0.0f); } public ViewportMetrics(ViewportMetrics viewport) { mPageSize = new FloatSize(viewport.getPageSize()); mViewportRect = new RectF(viewport.getViewport()); - PointF offset = viewport.getViewportOffset(); - mViewportOffset = new PointF(offset.x, offset.y); mZoomFactor = viewport.getZoomFactor(); - mViewportBias = viewport.mViewportBias; } + public ViewportMetrics(ImmutableViewportMetrics viewport) { + mPageSize = new FloatSize(viewport.pageSizeWidth, viewport.pageSizeHeight); + mViewportRect = new RectF(viewport.viewportRectLeft, + viewport.viewportRectTop, + viewport.viewportRectRight, + viewport.viewportRectBottom); + mZoomFactor = viewport.zoomFactor; + } + + public ViewportMetrics(JSONObject json) throws JSONException { - float x = (float) json.getDouble("x"); - float y = (float) json.getDouble("y"); - float width = (float) json.getDouble("width"); - float height = (float) json.getDouble("height"); - float pageWidth = (float) json.getDouble("pageWidth"); - float pageHeight = (float) json.getDouble("pageHeight"); - float offsetX = (float) json.getDouble("offsetX"); - float offsetY = (float) json.getDouble("offsetY"); - float zoom = (float) json.getDouble("zoom"); + float x = (float)json.getDouble("x"); + float y = (float)json.getDouble("y"); + float width = (float)json.getDouble("width"); + float height = (float)json.getDouble("height"); + float pageWidth = (float)json.getDouble("pageWidth"); + float pageHeight = (float)json.getDouble("pageHeight"); + float zoom = (float)json.getDouble("zoom"); mPageSize = new FloatSize(pageWidth, pageHeight); mViewportRect = new RectF(x, y, x + width, y + height); - mViewportOffset = new PointF(offsetX, offsetY); mZoomFactor = zoom; - mViewportBias = new PointF(0.0f, 0.0f); - } - - public PointF getOptimumViewportOffset(IntSize displayportSize) { - /* XXX Until bug #524925 is fixed, changing the viewport origin will - * cause unnecessary relayouts. This may cause rendering time to - * increase and should be considered. - */ - RectF viewport = getClampedViewport(); - - FloatSize bufferSpace = new FloatSize(displayportSize.width - viewport.width(), - displayportSize.height - viewport.height()); - PointF optimumOffset = - new PointF(bufferSpace.width * ((mViewportBias.x + 1.0f) / 2.0f), - bufferSpace.height * ((mViewportBias.y + 1.0f) / 2.0f)); - - // Make sure this offset won't cause wasted pixels in the displayport - // (i.e. make sure the resultant displayport intersects with the page - // as much as possible) - if (viewport.left - optimumOffset.x < 0) - optimumOffset.x = viewport.left; - else if ((bufferSpace.width - optimumOffset.x) + viewport.right > mPageSize.width) - optimumOffset.x = bufferSpace.width - (mPageSize.width - viewport.right); - - if (viewport.top - optimumOffset.y < 0) - optimumOffset.y = viewport.top; - else if ((bufferSpace.height - optimumOffset.y) + viewport.bottom > mPageSize.height) - optimumOffset.y = bufferSpace.height - (mPageSize.height - viewport.bottom); - - return new PointF(Math.round(optimumOffset.x), Math.round(optimumOffset.y)); } public PointF getOrigin() { return new PointF(mViewportRect.left, mViewportRect.top); } - public void setOrigin(PointF origin) { - // When the origin is set, we compare it with the last value set and - // change the viewport bias accordingly, so that any viewport based - // on these metrics will have a larger buffer in the direction of - // movement. - - // XXX Note the comment about bug #524925 in getOptimumViewportOffset. - // Ideally, the viewport bias would be a sliding scale, but we - // don't want to change it too often at the moment. - if (FloatUtils.fuzzyEquals(origin.x, mViewportRect.left)) - mViewportBias.x = 0; - else - mViewportBias.x = ((mViewportRect.left - origin.x) > 0) ? MAX_BIAS : -MAX_BIAS; - if (FloatUtils.fuzzyEquals(origin.y, mViewportRect.top)) - mViewportBias.y = 0; - else - mViewportBias.y = ((mViewportRect.top - origin.y) > 0) ? MAX_BIAS : -MAX_BIAS; - - mViewportRect.set(origin.x, origin.y, - origin.x + mViewportRect.width(), - origin.y + mViewportRect.height()); - } - - public PointF getDisplayportOrigin() { - return new PointF(mViewportRect.left - mViewportOffset.x, - mViewportRect.top - mViewportOffset.y); - } - public FloatSize getSize() { return new FloatSize(mViewportRect.width(), mViewportRect.height()); } - public void setSize(FloatSize size) { - mViewportRect.right = mViewportRect.left + size.width; - mViewportRect.bottom = mViewportRect.top + size.height; - } - public RectF getViewport() { return mViewportRect; } - public void setViewport(RectF viewport) { - mViewportRect = viewport; - } - - /** - * Returns the viewport rectangle, clamped within the page-size. - */ + /** Returns the viewport rectangle, clamped within the page-size. */ public RectF getClampedViewport() { RectF clampedViewport = new RectF(mViewportRect); @@ -200,24 +129,31 @@ public class ViewportMetrics { return clampedViewport; } - public PointF getViewportOffset() { - return mViewportOffset; - } - - public void setViewportOffset(PointF offset) { - mViewportOffset = offset; - } - public FloatSize getPageSize() { return mPageSize; } + public float getZoomFactor() { + return mZoomFactor; + } + public void setPageSize(FloatSize pageSize) { mPageSize = pageSize; } - public float getZoomFactor() { - return mZoomFactor; + public void setViewport(RectF viewport) { + mViewportRect = viewport; + } + + public void setOrigin(PointF origin) { + mViewportRect.set(origin.x, origin.y, + origin.x + mViewportRect.width(), + origin.y + mViewportRect.height()); + } + + public void setSize(FloatSize size) { + mViewportRect.right = mViewportRect.left + size.width; + mViewportRect.bottom = mViewportRect.top + size.height; } public void setZoomFactor(float zoomFactor) { @@ -240,15 +176,6 @@ public class ViewportMetrics { setOrigin(origin); mZoomFactor = newZoomFactor; - - // Similar to setOrigin, set the viewport bias based on the focal point - // of the zoom so that a viewport based on these metrics will have a - // larger buffer based on the direction of movement when scaling. - // - // This is biased towards scaling outwards, as zooming in doesn't - // really require a viewport bias. - mViewportBias.set(((focus.x / mViewportRect.width()) * (2.0f * MAX_BIAS)) - MAX_BIAS, - ((focus.y / mViewportRect.height()) * (2.0f * MAX_BIAS)) - MAX_BIAS); } /* @@ -261,15 +188,13 @@ public class ViewportMetrics { result.mPageSize = mPageSize.interpolate(to.mPageSize, t); result.mZoomFactor = FloatUtils.interpolate(mZoomFactor, to.mZoomFactor, t); result.mViewportRect = RectUtils.interpolate(mViewportRect, to.mViewportRect, t); - result.mViewportOffset = PointUtils.interpolate(mViewportOffset, to.mViewportOffset, t); return result; } public boolean fuzzyEquals(ViewportMetrics other) { return mPageSize.fuzzyEquals(other.mPageSize) - && RectUtils.fuzzyEquals(mViewportRect, other.mViewportRect) - && FloatUtils.fuzzyEquals(mViewportOffset, other.mViewportOffset) - && FloatUtils.fuzzyEquals(mZoomFactor, other.mZoomFactor); + && RectUtils.fuzzyEquals(mViewportRect, other.mViewportRect) + && FloatUtils.fuzzyEquals(mZoomFactor, other.mZoomFactor); } public String toJSON() { @@ -280,15 +205,13 @@ public class ViewportMetrics { StringBuffer sb = new StringBuffer(256); sb.append("{ \"x\" : ").append(mViewportRect.left) - .append(", \"y\" : ").append(mViewportRect.top) - .append(", \"width\" : ").append(width) - .append(", \"height\" : ").append(height) - .append(", \"pageWidth\" : ").append(mPageSize.width) - .append(", \"pageHeight\" : ").append(mPageSize.height) - .append(", \"offsetX\" : ").append(mViewportOffset.x) - .append(", \"offsetY\" : ").append(mViewportOffset.y) - .append(", \"zoom\" : ").append(mZoomFactor) - .append(" }"); + .append(", \"y\" : ").append(mViewportRect.top) + .append(", \"width\" : ").append(width) + .append(", \"height\" : ").append(height) + .append(", \"pageWidth\" : ").append(mPageSize.width) + .append(", \"pageHeight\" : ").append(mPageSize.height) + .append(", \"zoom\" : ").append(mZoomFactor) + .append(" }"); return sb.toString(); } @@ -296,10 +219,8 @@ public class ViewportMetrics { public String toString() { StringBuffer buff = new StringBuffer(128); buff.append("v=").append(mViewportRect.toString()) - .append(" p=").append(mPageSize.toString()) - .append(" z=").append(mZoomFactor) - .append(" o=").append(mViewportOffset.x) - .append(',').append(mViewportOffset.y); + .append(" p=").append(mPageSize.toString()) + .append(" z=").append(mZoomFactor); return buff.toString(); } } diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java index 7d15bdb..1eb0331 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java @@ -61,45 +61,69 @@ import java.util.TimerTask; * Many ideas are from Joe Hewitt's Scrollability: * https://github.com/joehewitt/scrollability/ */ -public class PanZoomController extends GestureDetector.SimpleOnGestureListener implements SimpleScaleGestureDetector.SimpleScaleGestureListener { - // The distance the user has to pan before we recognize it as such (e.g. to avoid 1-pixel pans - // between the touch-down and touch-up of a click). In units of density-independent pixels. - public static final float PAN_THRESHOLD = 1 / 16f * LOKitShell.getDpi(); +public class PanZoomController + extends GestureDetector.SimpleOnGestureListener + implements SimpleScaleGestureDetector.SimpleScaleGestureListener +{ private static final String LOGTAG = "GeckoPanZoomController"; + + // Animation stops if the velocity is below this value when overscrolled or panning. private static final float STOPPED_THRESHOLD = 4.0f; + // Animation stops is the velocity is below this threshold when flinging. private static final float FLING_STOPPED_THRESHOLD = 0.1f; + + // The distance the user has to pan before we recognize it as such (e.g. to avoid 1-pixel pans + // between the touch-down and touch-up of a click). In units of density-independent pixels. + public static final float PAN_THRESHOLD = 1/16f * LOKitShell.getDpi(); + // Angle from axis within which we stay axis-locked private static final double AXIS_LOCK_ANGLE = Math.PI / 6.0; // 30 degrees + // The maximum amount we allow you to zoom into a page private static final float MAX_ZOOM = 4.0f; + /* 16 precomputed frames of the _ease-out_ animation from the CSS Transitions specification. */ private static final float[] EASE_OUT_ANIMATION_FRAMES = { - 0.00000f, /* 0 */ - 0.10211f, /* 1 */ - 0.19864f, /* 2 */ - 0.29043f, /* 3 */ - 0.37816f, /* 4 */ - 0.46155f, /* 5 */ - 0.54054f, /* 6 */ - 0.61496f, /* 7 */ - 0.68467f, /* 8 */ - 0.74910f, /* 9 */ - 0.80794f, /* 10 */ - 0.86069f, /* 11 */ - 0.90651f, /* 12 */ - 0.94471f, /* 13 */ - 0.97401f, /* 14 */ - 0.99309f, /* 15 */ + 0.00000f, /* 0 */ + 0.10211f, /* 1 */ + 0.19864f, /* 2 */ + 0.29043f, /* 3 */ + 0.37816f, /* 4 */ + 0.46155f, /* 5 */ + 0.54054f, /* 6 */ + 0.61496f, /* 7 */ + 0.68467f, /* 8 */ + 0.74910f, /* 9 */ + 0.80794f, /* 10 */ + 0.86069f, /* 11 */ + 0.90651f, /* 12 */ + 0.94471f, /* 13 */ + 0.97401f, /* 14 */ + 0.99309f, /* 15 */ }; - private static String MESSAGE_ZOOM_RECT = "Browser:ZoomToRect"; - private static String MESSAGE_ZOOM_PAGE = "Browser:ZoomToPageWidth"; + + private enum PanZoomState { + NOTHING, /* no touch-start events received */ + FLING, /* all touches removed, but we're still scrolling page */ + TOUCHING, /* one touch-start event received */ + PANNING_LOCKED, /* touch-start followed by move (i.e. panning with axis lock) */ + PANNING, /* panning without axis lock */ + PANNING_HOLD, /* in panning, but not moving. + * similar to TOUCHING but after starting a pan */ + PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */ + PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */ + ANIMATED_ZOOM /* animated zoom to a new rect */ + } + private final LayerController mController; private final SubdocumentScrollHelper mSubscroller; private final Axis mX; private final Axis mY; + private Thread mMainThread; + /* The timer that handles flings or bounces. */ private Timer mAnimationTimer; /* The runnable being scheduled by the animation timer. */ @@ -118,59 +142,48 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i mY = new AxisY(mSubscroller); mMainThread = LibreOfficeMainActivity.mAppContext.getMainLooper().getThread(); + checkMainThread(); mState = PanZoomState.NOTHING; } + // for debugging bug 713011; it can be taken out once that is resolved. + private void checkMainThread() { + if (mMainThread != Thread.currentThread()) { + // log with full stack trace + Log.e(LOGTAG, "Uh-oh, we're running on the wrong thread!", new Exception()); + } + } + public boolean onTouchEvent(MotionEvent event) { switch (event.getAction() & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: - return onTouchStart(event); - case MotionEvent.ACTION_MOVE: - return onTouchMove(event); - case MotionEvent.ACTION_UP: - return onTouchEnd(event); - case MotionEvent.ACTION_CANCEL: - return onTouchCancel(event); - default: - return false; + case MotionEvent.ACTION_DOWN: return onTouchStart(event); + case MotionEvent.ACTION_MOVE: return onTouchMove(event); + case MotionEvent.ACTION_UP: return onTouchEnd(event); + case MotionEvent.ACTION_CANCEL: return onTouchCancel(event); + default: return false; } } - /** - * This function must be called from the UI thread. - */ + /** This function must be called from the UI thread. */ public void abortAnimation() { + checkMainThread(); // this happens when gecko changes the viewport on us or if the device is rotated. // if that's the case, abort any animation in progress and re-zoom so that the page // snaps to edges. for other cases (where the user's finger(s) are down) don't do // anything special. - switch (mState) { - case FLING: - mX.stopFling(); - mY.stopFling(); - mState = PanZoomState.NOTHING; - // fall through - case ANIMATED_ZOOM: - // the zoom that's in progress likely makes no sense any more (such as if - // the screen orientation changed) so abort it - // fall through - case NOTHING: - // Don't do animations here; they're distracting and can cause flashes on page - // transitions. - mController.setViewportMetrics(getValidViewportMetrics()); - mController.notifyLayerClientOfGeometryChange(); - break; + if (mState == PanZoomState.FLING) { + mX.stopFling(); + mY.stopFling(); + mState = PanZoomState.NOTHING; } } - /** - * This must be called on the UI thread. - */ + /** This must be called on the UI thread. */ public void pageSizeUpdated() { if (mState == PanZoomState.NOTHING) { ViewportMetrics validated = getValidViewportMetrics(); - if (!mController.getViewportMetrics().fuzzyEquals(validated)) { + if (! (new ViewportMetrics(mController.getViewportMetrics())).fuzzyEquals(validated)) { // page size changed such that we are now in overscroll. snap to the // the nearest valid viewport mController.setViewportMetrics(validated); @@ -179,116 +192,110 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i } } + /* + * Panning/scrolling + */ + private boolean onTouchStart(MotionEvent event) { - Log.d(LOGTAG, "onTouchStart in state " + mState); // user is taking control of movement, so stop // any auto-movement we have going stopAnimationTimer(); mSubscroller.cancel(); switch (mState) { - case ANIMATED_ZOOM: - return false; - case FLING: - case NOTHING: - startTouch(event.getX(0), event.getY(0), event.getEventTime()); - return false; - case TOUCHING: - case PANNING: - case PANNING_LOCKED: - case PANNING_HOLD: - case PANNING_HOLD_LOCKED: - case PINCHING: - Log.e(LOGTAG, "Received impossible touch down while in " + mState); - return false; + case ANIMATED_ZOOM: + return false; + case FLING: + case NOTHING: + startTouch(event.getX(0), event.getY(0), event.getEventTime()); + return false; + case TOUCHING: + case PANNING: + case PANNING_LOCKED: + case PANNING_HOLD: + case PANNING_HOLD_LOCKED: + case PINCHING: + Log.e(LOGTAG, "Received impossible touch down while in " + mState); + return false; } Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchStart"); return false; } - /* - * Panning/scrolling - */ private boolean onTouchMove(MotionEvent event) { - Log.d(LOGTAG, "onTouchMove in state " + mState); switch (mState) { - case NOTHING: - case FLING: - // should never happen - Log.e(LOGTAG, "Received impossible touch move while in " + mState); - return false; + case NOTHING: + case FLING: + // should never happen + Log.e(LOGTAG, "Received impossible touch move while in " + mState); + return false; - case TOUCHING: - if (panDistance(event) < PAN_THRESHOLD) { - return false; - } - cancelTouch(); - startPanning(event.getX(0), event.getY(0), event.getEventTime()); - track(event); - return true; - - case PANNING_HOLD_LOCKED: - //GeckoApp.mAutoCompletePopup.hide(); - mState = PanZoomState.PANNING_LOCKED; - // fall through - case PANNING_LOCKED: - track(event); - return true; - - case PANNING_HOLD: - //GeckoApp.mAutoCompletePopup.hide(); - mState = PanZoomState.PANNING; - // fall through - case PANNING: - track(event); - return true; - - case ANIMATED_ZOOM: - case PINCHING: - // scale gesture listener will handle this + case TOUCHING: + if (panDistance(event) < PAN_THRESHOLD) { return false; + } + cancelTouch(); + startPanning(event.getX(0), event.getY(0), event.getEventTime()); + track(event); + return true; + + case PANNING_HOLD_LOCKED: + mState = PanZoomState.PANNING_LOCKED; + // fall through + case PANNING_LOCKED: + track(event); + return true; + + case PANNING_HOLD: + mState = PanZoomState.PANNING; + // fall through + case PANNING: + track(event); + return true; + + case ANIMATED_ZOOM: + case PINCHING: + // scale gesture listener will handle this + return false; } Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchMove"); return false; } private boolean onTouchEnd(MotionEvent event) { - Log.d(LOGTAG, "onTouchEnd in " + mState); switch (mState) { - case NOTHING: - case FLING: - // should never happen - Log.e(LOGTAG, "Received impossible touch end while in " + mState); - return false; - case TOUCHING: - mState = PanZoomState.NOTHING; - // the switch into TOUCHING might have happened while the page was - // snapping back after overscroll. we need to finish the snap if that - // was the case - bounce(); - return false; - case PANNING: - case PANNING_LOCKED: - case PANNING_HOLD: - case PANNING_HOLD_LOCKED: - mState = PanZoomState.FLING; - fling(); - return true; - case PINCHING: - mState = PanZoomState.NOTHING; - return true; - case ANIMATED_ZOOM: - return false; + case NOTHING: + case FLING: + // should never happen + Log.e(LOGTAG, "Received impossible touch end while in " + mState); + return false; + case TOUCHING: + mState = PanZoomState.NOTHING; + // the switch into TOUCHING might have happened while the page was + // snapping back after overscroll. we need to finish the snap if that + // was the case + bounce(); + return false; + case PANNING: + case PANNING_LOCKED: + case PANNING_HOLD: + case PANNING_HOLD_LOCKED: + mState = PanZoomState.FLING; + fling(); + return true; + case PINCHING: + mState = PanZoomState.NOTHING; + return true; + case ANIMATED_ZOOM: + return false; } Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchEnd"); return false; } private boolean onTouchCancel(MotionEvent event) { - Log.d(LOGTAG, "onTouchCancel in " + mState); - mState = PanZoomState.NOTHING; // ensure we snap back if we're overscrolled bounce(); @@ -332,7 +339,7 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i } private void track(float x, float y, long time) { - float timeDelta = (float) (time - mLastEventTime); + float timeDelta = (float)(time - mLastEventTime); if (FloatUtils.fuzzyEquals(timeDelta, 0)) { // probably a duplicate event, ignore it. using a zero timeDelta will mess // up our velocity @@ -350,8 +357,8 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i for (int i = 0; i < event.getHistorySize(); i++) { track(event.getHistoricalX(0, i), - event.getHistoricalY(0, i), - event.getHistoricalEventTime(i)); + event.getHistoricalY(0, i), + event.getHistoricalEventTime(i)); } track(event.getX(0), event.getY(0), event.getEventTime()); @@ -395,7 +402,6 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i } mState = PanZoomState.FLING; - Log.d(LOGTAG, "end bounce at " + metrics); startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics)); } @@ -412,16 +418,12 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i stopAnimationTimer(); } - //GeckoApp.mAppContext.hidePlugins(false /* don't hide layers */); - mAnimationTimer = new Timer("Animation Timer"); mAnimationRunnable = runnable; mAnimationTimer.scheduleAtFixedRate(new TimerTask() { @Override - public void run() { - mController.post(runnable); - } - }, 0, 1000L / 60L); + public void run() { mController.post(runnable); } + }, 0, 1000L/60L); } /* Stops the fling or bounce animation. */ @@ -437,9 +439,9 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i } private float getVelocity() { - float xVelocity = mX.getRealVelocity(); - float yVelocity = mY.getRealVelocity(); - return FloatMath.sqrt(xVelocity * xVelocity + yVelocity * yVelocity); + float xvel = mX.getRealVelocity(); + float yvel = mY.getRealVelocity(); + return FloatMath.sqrt(xvel * xvel + yvel * yvel); } private boolean stopped() { @@ -454,14 +456,150 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i mX.displace(); mY.displace(); PointF displacement = getDisplacement(); - if (!mSubscroller.scrollBy(displacement)) { + if (! mSubscroller.scrollBy(displacement)) { synchronized (mController) { mController.scrollBy(displacement); } } } + private abstract class AnimationRunnable implements Runnable { + private boolean mAnimationTerminated; + + /* This should always run on the UI thread */ + public final void run() { + /* + * Since the animation timer queues this runnable on the UI thread, it + * is possible that even when the animation timer is cancelled, there + * are multiple instances of this queued, so we need to have another + * mechanism to abort. This is done by using the mAnimationTerminated flag. + */ + if (mAnimationTerminated) { + return; + } + animateFrame(); + } + + protected abstract void animateFrame(); + + /* This should always run on the UI thread */ + protected final void terminate() { + mAnimationTerminated = true; + } + } + + /* The callback that performs the bounce animation. */ + private class BounceRunnable extends AnimationRunnable { + /* The current frame of the bounce-back animation */ + private int mBounceFrame; + /* + * The viewport metrics that represent the start and end of the bounce-back animation, + * respectively. + */ + private ViewportMetrics mBounceStartMetrics; + private ViewportMetrics mBounceEndMetrics; + + BounceRunnable(ViewportMetrics startMetrics, ViewportMetrics endMetrics) { + mBounceStartMetrics = startMetrics; + mBounceEndMetrics = endMetrics; + } + + protected void animateFrame() { + /* + * The pan/zoom controller might have signaled to us that it wants to abort the + * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail + * out. + */ + if (mState != PanZoomState.FLING) { + finishAnimation(); + return; + } + + /* Perform the next frame of the bounce-back animation. */ + if (mBounceFrame < EASE_OUT_ANIMATION_FRAMES.length) { + advanceBounce(); + return; + } + + /* Finally, if there's nothing else to do, complete the animation and go to sleep. */ + finishBounce(); + finishAnimation(); + mState = PanZoomState.NOTHING; + } + + /* Performs one frame of a bounce animation. */ + private void advanceBounce() { + synchronized (mController) { + float t = EASE_OUT_ANIMATION_FRAMES[mBounceFrame]; + ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t); + mController.setViewportMetrics(newMetrics); + mController.notifyLayerClientOfGeometryChange(); + mBounceFrame++; + } + } + + /* Concludes a bounce animation and snaps the viewport into place. */ + private void finishBounce() { + synchronized (mController) { + mController.setViewportMetrics(mBounceEndMetrics); + mController.notifyLayerClientOfGeometryChange(); + mBounceFrame = -1; + } + } + } + + // The callback that performs the fling animation. + private class FlingRunnable extends AnimationRunnable { + protected void animateFrame() { + /* + * The pan/zoom controller might have signaled to us that it wants to abort the + * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail + * out. + */ + if (mState != PanZoomState.FLING) { + finishAnimation(); + return; + } + + /* Advance flings, if necessary. */ + boolean flingingX = mX.advanceFling(); + boolean flingingY = mY.advanceFling(); + + boolean overscrolled = (mX.overscrolled() || mY.overscrolled()); + + /* If we're still flinging in any direction, update the origin. */ + if (flingingX || flingingY) { + updatePosition(); + + /* + * Check to see if we're still flinging with an appreciable velocity. The threshold is + * higher in the case of overscroll, so we bounce back eagerly when overscrolling but + * coast smoothly to a stop when not. In other words, require a greater velocity to + * maintain the fling once we enter overscroll. + */ + float threshold = (overscrolled && !mSubscroller.scrolling() ? STOPPED_THRESHOLD : FLING_STOPPED_THRESHOLD); + if (getVelocity() >= threshold) { + // we're still flinging + return; + } + + mX.stopFling(); + mY.stopFling(); + } + + /* Perform a bounce-back animation if overscrolled. */ + if (overscrolled) { + bounce(); + } else { + finishAnimation(); + mState = PanZoomState.NOTHING; + } + } + } + private void finishAnimation() { + checkMainThread(); + Log.d(LOGTAG, "Finishing animation at " + mController.getViewportMetrics()); stopAnimationTimer(); @@ -517,6 +655,26 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i return viewportMetrics; } + private class AxisX extends Axis { + AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); } + @Override + public float getOrigin() { return mController.getOrigin().x; } + @Override + protected float getViewportLength() { return mController.getViewportSize().width; } + @Override + protected float getPageLength() { return mController.getPageSize().width; } + } + + private class AxisY extends Axis { + AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); } + @Override + public float getOrigin() { return mController.getOrigin().y; } + @Override + protected float getViewportLength() { return mController.getViewportSize().height; } + @Override + protected float getPageLength() { return mController.getPageSize().height; } + } + /* * Zooming */ @@ -555,11 +713,10 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i * factor toward 1.0. */ float resistance = Math.min(mX.getEdgeResistance(), mY.getEdgeResistance()); - if (spanRatio > 1.0f) { + if (spanRatio > 1.0f) spanRatio = 1.0f + (spanRatio - 1.0f) * resistance; - } else { + else spanRatio = 1.0f - (1.0f - spanRatio) * resistance; - } synchronized (mController) { float newZoomFactor = mController.getZoomFactor() * spanRatio; @@ -568,12 +725,12 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i // such that it asymptotically reaches MAX_ZOOM + 1.0 // but never exceeds that float excessZoom = newZoomFactor - MAX_ZOOM; - excessZoom = 1.0f - (float) Math.exp(-excessZoom); + excessZoom = 1.0f - (float)Math.exp(-excessZoom); newZoomFactor = MAX_ZOOM + excessZoom; } mController.scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(), - mLastZoomFocus.y - detector.getFocusY())); + mLastZoomFocus.y - detector.getFocusY())); PointF focus = new PointF(detector.getFocusX(), detector.getFocusY()); mController.scaleWithFocus(newZoomFactor, focus); } @@ -594,7 +751,6 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i startTouch(detector.getFocusX(), detector.getFocusY(), detector.getEventTime()); // Force a viewport synchronisation - //GeckoApp.mAppContext.showPlugins(); mController.setForceRedraw(); mController.notifyLayerClientOfGeometryChange(); } @@ -663,193 +819,4 @@ public class PanZoomController extends GestureDetector.SimpleOnGestureListener i bounce(finalMetrics); return true; } - - private enum PanZoomState { - NOTHING, /* no touch-start events received */ - FLING, /* all touches removed, but we're still scrolling page */ - TOUCHING, /* one touch-start event received */ - PANNING_LOCKED, /* touch-start followed by move (i.e. panning with axis lock) */ - PANNING, /* panning without axis lock */ - PANNING_HOLD, /* in panning, but not moving. - * similar to TOUCHING but after starting a pan */ - PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */ - PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */ - ANIMATED_ZOOM /* animated zoom to a new rect */ - } - - private abstract class AnimationRunnable implements Runnable { - private boolean mAnimationTerminated; - - /* This should always run on the UI thread */ - public final void run() { - /* - * Since the animation timer queues this runnable on the UI thread, it - * is possible that even when the animation timer is cancelled, there - * are multiple instances of this queued, so we need to have another - * mechanism to abort. This is done by using the mAnimationTerminated flag. - */ - if (mAnimationTerminated) { - return; - } - animateFrame(); - } - - protected abstract void animateFrame(); - - /* This should always run on the UI thread */ - protected final void terminate() { - mAnimationTerminated = true; - } - } - - /* The callback that performs the bounce animation. */ - private class BounceRunnable extends AnimationRunnable { - /* The current frame of the bounce-back animation */ - private int mBounceFrame; - /* - * The viewport metrics that represent the start and end of the bounce-back animation, - * respectively. - */ - private ViewportMetrics mBounceStartMetrics; - private ViewportMetrics mBounceEndMetrics; - - BounceRunnable(ViewportMetrics startMetrics, ViewportMetrics endMetrics) { - mBounceStartMetrics = startMetrics; - mBounceEndMetrics = endMetrics; - } - - protected void animateFrame() { - /* - * The pan/zoom controller might have signaled to us that it wants to abort the - * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail - * out. - */ - if (mState != PanZoomState.FLING) { - finishAnimation(); - return; - } - - /* Perform the next frame of the bounce-back animation. */ - if (mBounceFrame < EASE_OUT_ANIMATION_FRAMES.length) { - advanceBounce(); - return; - } - - /* Finally, if there's nothing else to do, complete the animation and go to sleep. */ - finishBounce(); - finishAnimation(); - mState = PanZoomState.NOTHING; - } - - /* Performs one frame of a bounce animation. */ - private void advanceBounce() { - synchronized (mController) { - float t = EASE_OUT_ANIMATION_FRAMES[mBounceFrame]; - ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t); - mController.setViewportMetrics(newMetrics); - mController.notifyLayerClientOfGeometryChange(); - mBounceFrame++; - } - } - - /* Concludes a bounce animation and snaps the viewport into place. */ - private void finishBounce() { - synchronized (mController) { - mController.setViewportMetrics(mBounceEndMetrics); - mController.notifyLayerClientOfGeometryChange(); - mBounceFrame = -1; - } - } - } - - // The callback that performs the fling animation. - private class FlingRunnable extends AnimationRunnable { - protected void animateFrame() { - /* - * The pan/zoom controller might have signaled to us that it wants to abort the - * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail - * out. - */ - if (mState != PanZoomState.FLING) { - finishAnimation(); - return; - } - - /* Advance flings, if necessary. */ - boolean flingingX = mX.advanceFling(); - boolean flingingY = mY.advanceFling(); - - boolean overscrolled = (mX.overscrolled() || mY.overscrolled()); - - /* If we're still flinging in any direction, update the origin. */ - if (flingingX || flingingY) { - updatePosition(); - - /* - * Check to see if we're still flinging with an appreciable velocity. The threshold is - * higher in the case of overscroll, so we bounce back eagerly when overscrolling but - * coast smoothly to a stop when not. In other words, require a greater velocity to - * maintain the fling once we enter overscroll. - */ - float threshold = (overscrolled && !mSubscroller.scrolling() ? STOPPED_THRESHOLD : FLING_STOPPED_THRESHOLD); - if (getVelocity() >= threshold) { - // we're still flinging - return; - } - - mX.stopFling(); - mY.stopFling(); - } - - /* Perform a bounce-back animation if overscrolled. */ - if (overscrolled) { - bounce(); - } else { - finishAnimation(); - mState = PanZoomState.NOTHING; - } - } - } - - private class AxisX extends Axis { - AxisX(SubdocumentScrollHelper subscroller) { - super(subscroller); - } - - @Override - public float getOrigin() { - return mController.getOrigin().x; - } - - @Override - protected float getViewportLength() { - return mController.getViewportSize().width; - } - - @Override - protected float getPageLength() { - return mController.getPageSize().width; - } - } - - private class AxisY extends Axis { - AxisY(SubdocumentScrollHelper subscroller) { - super(subscroller); - } - - @Override - public float getOrigin() { - return mController.getOrigin().y; - } - - @Override - protected float getViewportLength() { - return mController.getViewportSize().height; - } - - @Override - protected float getPageLength() { - return mController.getPageSize().height; - } - } } commit 046e19ef2cb02d11560b5812364aa5211b20e4ac Author: Tomaž Vajngerl <tomaz.vajng...@collabora.com> Date: Thu Sep 18 22:10:49 2014 +0200 android: thumbnail as background when tile is not available Change-Id: Id38e40b3fdb46adeb19e6a1e106775391c5da455 diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java index e75276d..c5918e69 100644 --- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java +++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java @@ -1,5 +1,6 @@ package org.libreoffice; +import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; @@ -22,6 +23,7 @@ public class LOKitThread extends Thread { private ViewportMetrics mViewportMetrics; private String mInputFile; private Rect mOldRect; + private boolean mCheckboardImageSet = false; LOKitThread(String inputFile) { mInputFile = inputFile; @@ -115,6 +117,7 @@ public class LOKitThread extends Thread { layerClient.endDrawing(); Log.i(LOGTAG, "tilerender end draw"); + return true; } @@ -128,7 +131,22 @@ public class LOKitThread extends Thread { private boolean initialize() { mApplication = LibreOfficeMainActivity.mAppContext; mTileProvider = new LOKitTileProvider(mApplication.getLayerController(), mInputFile); - return mTileProvider.isReady(); + boolean isReady = mTileProvider.isReady(); + if (isReady) + { + if (!mCheckboardImageSet) { + Log.i(LOGTAG, "Generate thumbnail!"); + Bitmap bitmap = mTileProvider.thumbnail(); + Log.i(LOGTAG, "Done generate thumbnail!"); + if (bitmap != null) { + Log.i(LOGTAG, "Setting checkboard image!"); + mApplication.getLayerController().getView().changeCheckerboardBitmap(bitmap); + Log.i(LOGTAG, "Done setting checkboard image!!"); + mCheckboardImageSet = true; + } + } + } + return isReady; } public void run() { commit 6ca57cb25ef8826aac5584dee5b41b6ad6114555 Author: Tomaž Vajngerl <tomaz.vajng...@collabora.com> Date: Thu Sep 18 22:09:48 2014 +0200 android: fix thumbnail() to produce a valid bitmap Change-Id: I578ac9482f334765c71a66421a3fa2dfb85e22b3 diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java index 5a906c9..f13dd8a 100644 --- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java +++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java @@ -122,9 +122,6 @@ public class LOKitTileProvider implements TileProvider { @Override public Bitmap thumbnail() { - ByteBuffer buffer = ByteBuffer.allocateDirect(TILE_SIZE * TILE_SIZE * 4); - Bitmap bitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Bitmap.Config.ARGB_8888); - int widthPixel = getPageWidth(); int heightPixel = getPageHeight(); @@ -138,8 +135,14 @@ public class LOKitTileProvider implements TileProvider { widthPixel = (int) (heightPixel * ratio); } + ByteBuffer buffer = ByteBuffer.allocateDirect(widthPixel * heightPixel * 4); mDocument.paintTile(buffer, widthPixel, heightPixel, 0, 0, (int) mWidthTwip, (int) mHeightTwip); + Bitmap bitmap = Bitmap.createBitmap(widthPixel, heightPixel, Bitmap.Config.ARGB_8888); + bitmap.copyPixelsFromBuffer(buffer); + if (bitmap == null) { + Log.w(LOGTAG, "Thumbnail not created!"); + } return bitmap; } diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java index 60abbdf..2a89e77 100644 --- a/android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java +++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java @@ -63,7 +63,7 @@ public class MockTileProvider implements TileProvider { @Override public Bitmap thumbnail() { - return null; + return layerController.getDrawable("dummy_page"); } @Override commit 58f5e531f792567beb99a6eee346ac61e6f94938 Author: Tomaž Vajngerl <tomaz.vajng...@collabora.com> Date: Thu Sep 18 22:08:01 2014 +0200 android: import changes from Fennec to get ScreenShotLayer Change-Id: I1b72af85a906aef289d1be086274941094df4f96 diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java index cf13afb..1ebb9a1 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java @@ -40,6 +40,7 @@ package org.mozilla.gecko.gfx; import android.content.Context; import android.graphics.PointF; +import android.graphics.RectF; import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.Log; @@ -53,11 +54,13 @@ import java.util.List; public class GeckoLayerClient { private static final String LOGTAG = "GeckoLayerClient"; - private static final long MIN_VIEWPORT_CHANGE_DELAY = 25L; + private static final int DEFAULT_DISPLAY_PORT_MARGIN = 300; + private static final long MIN_VIEWPORT_CHANGE_DELAY = 25L; private static final IntSize TILE_SIZE = new IntSize(256, 256); protected IntSize mScreenSize; + private RectF mDisplayPort; protected Layer mTileLayer; /* The viewport that Gecko is currently displaying. */ protected ViewportMetrics mGeckoViewport; @@ -79,6 +82,7 @@ public class GeckoLayerClient { public GeckoLayerClient(Context context) { mContext = context; mScreenSize = new IntSize(0, 0); + mDisplayPort = new RectF(); } protected void setupLayer() { @@ -110,7 +114,7 @@ public class GeckoLayerClient { layerController.setViewportMetrics(mGeckoViewport); } - sendResizeEventIfNecessary(false); + sendResizeEventIfNecessary(true); } public void beginDrawing(ViewportMetrics viewportMetrics) { @@ -132,6 +136,10 @@ public class GeckoLayerClient { Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing"); } + RectF getDisplayPort() { + return mDisplayPort; + } + protected void updateViewport(boolean onlyUpdatePageSize) { // save and restore the viewport size stored in java; never let the // JS-side viewport dimensions override the java-side ones because @@ -142,7 +150,8 @@ public class GeckoLayerClient { mGeckoViewport.setSize(viewportSize); PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin(); - mTileLayer.setOrigin(PointUtils.round(displayportOrigin)); + RectF position = mGeckoViewport.getViewport(); + mTileLayer.setPosition(RectUtils.round(position)); mTileLayer.setResolution(mGeckoViewport.getZoomFactor()); Log.e(LOGTAG, "### updateViewport onlyUpdatePageSize=" + onlyUpdatePageSize + " getTileViewport " + mGeckoViewport); @@ -160,7 +169,7 @@ public class GeckoLayerClient { } /* Informs Gecko that the screen size has changed. */ - protected void sendResizeEventIfNecessary(boolean force) { + private void sendResizeEventIfNecessary(boolean force) { Log.e(LOGTAG, "### sendResizeEventIfNecessary " + force); DisplayMetrics metrics = new DisplayMetrics(); @@ -172,9 +181,8 @@ public class GeckoLayerClient { // size is zero (which indicates that the rendering surface hasn't been // allocated yet). boolean screenSizeChanged = !mScreenSize.equals(newScreenSize); - boolean viewportSizeValid = (mLayerController != null && mLayerController.getViewportSize().isPositive()); - if (!(force || (screenSizeChanged && viewportSizeValid))) { + if (!force && !screenSizeChanged) { return; } @@ -213,11 +221,75 @@ public class GeckoLayerClient { mViewportSizeChanged = true; } + private static RectF calculateDisplayPort(ImmutableViewportMetrics metrics) { + float desiredXMargins = 2 * DEFAULT_DISPLAY_PORT_MARGIN; + float desiredYMargins = 2 * DEFAULT_DISPLAY_PORT_MARGIN; + + // we need to avoid having a display port that is larger than the page, or we will end up + // painting things outside the page bounds (bug 729169). we simultaneously need to make + // the display port as large as possible so that we redraw less. + + // figure out how much of the desired buffer amount we can actually use on the horizontal axis + float xBufferAmount = Math.min(desiredXMargins, metrics.pageSizeWidth - metrics.getWidth()); + // if we reduced the buffer amount on the horizontal axis, we should take that saved memory and + // use it on the vertical axis + float savedPixels = (desiredXMargins - xBufferAmount) * (metrics.getHeight() + desiredYMargins); + float extraYAmount = (float)Math.floor(savedPixels / (metrics.getWidth() + xBufferAmount)); + float yBufferAmount = Math.min(desiredYMargins + extraYAmount, metrics.pageSizeHeight - metrics.getHeight()); + // and the reverse - if we shrunk the buffer on the vertical axis we can add it to the horizontal + if (xBufferAmount == desiredXMargins && yBufferAmount < desiredYMargins) { + savedPixels = (desiredYMargins - yBufferAmount) * (metrics.getWidth() + xBufferAmount); + float extraXAmount = (float)Math.floor(savedPixels / (metrics.getHeight() + yBufferAmount)); + xBufferAmount = Math.min(xBufferAmount + extraXAmount, metrics.pageSizeWidth - metrics.getWidth()); + } + + // and now calculate the display port margins based on how much buffer we've decided to use and + // the page bounds, ensuring we use all of the available buffer amounts on one side or the other + // on any given axis. (i.e. if we're scrolled to the top of the page, the vertical buffer is + // entirely below the visible viewport, but if we're halfway down the page, the vertical buffer + // is split). + float leftMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.viewportRectLeft); + float rightMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.pageSizeWidth - (metrics.viewportRectLeft + metrics.getWidth())); + if (leftMargin < DEFAULT_DISPLAY_PORT_MARGIN) { + rightMargin = xBufferAmount - leftMargin; + } else if (rightMargin < DEFAULT_DISPLAY_PORT_MARGIN) { + leftMargin = xBufferAmount - rightMargin; + } else if (!FloatUtils.fuzzyEquals(leftMargin + rightMargin, xBufferAmount)) { + float delta = xBufferAmount - leftMargin - rightMargin; + leftMargin += delta / 2; + rightMargin += delta / 2; + } + + float topMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.viewportRectTop); + float bottomMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.pageSizeHeight - (metrics.viewportRectTop + metrics.getHeight())); + if (topMargin < DEFAULT_DISPLAY_PORT_MARGIN) { + bottomMargin = yBufferAmount - topMargin; + } else if (bottomMargin < DEFAULT_DISPLAY_PORT_MARGIN) { + topMargin = yBufferAmount - bottomMargin; + } else if (!FloatUtils.fuzzyEquals(topMargin + bottomMargin, yBufferAmount)) { + float delta = yBufferAmount - topMargin - bottomMargin; + topMargin += delta / 2; + bottomMargin += delta / 2; + } + + // note that unless the viewport size changes, or the page dimensions change (either because of + // content changes or zooming), the size of the display port should remain constant. this + // is intentional to avoid re-creating textures and all sorts of other reallocations in the + // draw and composition code. + return new RectF(metrics.viewportRectLeft - leftMargin, + metrics.viewportRectTop - topMargin, + metrics.viewportRectRight + rightMargin, + metrics.viewportRectBottom + bottomMargin); + } + private void adjustViewport() { - ViewportMetrics viewportMetrics = new ViewportMetrics(mLayerController.getViewportMetrics()); + ViewportMetrics viewportMetrics = + new ViewportMetrics(mLayerController.getViewportMetrics()); viewportMetrics.setViewport(viewportMetrics.getClampedViewport()); + mDisplayPort = calculateDisplayPort(new ImmutableViewportMetrics(mLayerController.getViewportMetrics())); + LOKitShell.sendEvent(LOEvent.viewport(viewportMetrics)); if (mViewportSizeChanged) { mViewportSizeChanged = false; @@ -228,19 +300,18 @@ public class GeckoLayerClient { } public void geometryChanged() { - sendResizeEventIfNecessary(); - render(); + sendResizeEventIfNecessary(false); + if (mLayerController.getRedrawHint()) + adjustViewport(); } public ViewportMetrics getGeckoViewportMetrics() { + // Return a copy, as we modify this inside the Gecko thread if (mGeckoViewport != null) return new ViewportMetrics(mGeckoViewport); return null; } - private void sendResizeEventIfNecessary() { - sendResizeEventIfNecessary(false); - } public void addTile(SubTile tile) { if (mTileLayer instanceof MultiTileLayer) { diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java new file mode 100644 index 0000000..b9eb34d --- /dev/null +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java @@ -0,0 +1,70 @@ +/* -*- 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 android.graphics.PointF; +import android.graphics.RectF; + +/** + * ImmutableViewportMetrics are used to store the viewport metrics + * in way that we can access a version of them from multiple threads + * without having to take a lock + */ +public class ImmutableViewportMetrics { + + // We need to flatten the RectF and FloatSize structures + // because Java doesn't have the concept of const classes + public final float pageSizeWidth; + public final float pageSizeHeight; + public final float viewportRectBottom; + public final float viewportRectLeft; + public final float viewportRectRight; + public final float viewportRectTop; + public final float zoomFactor; + + public ImmutableViewportMetrics(ViewportMetrics m) { + RectF viewportRect = m.getViewport(); + viewportRectBottom = viewportRect.bottom; + viewportRectLeft = viewportRect.left; + viewportRectRight = viewportRect.right; + viewportRectTop = viewportRect.top; + + FloatSize pageSize = m.getPageSize(); + pageSizeWidth = pageSize.width; + pageSizeHeight = pageSize.height; + + zoomFactor = m.getZoomFactor(); + } + + public float getWidth() { + return viewportRectRight - viewportRectLeft; + } + + public float getHeight() { + return viewportRectBottom - viewportRectTop; + } + + // some helpers to make ImmutableViewportMetrics act more like ViewportMetrics + + public PointF getOrigin() { + return new PointF(viewportRectLeft, viewportRectTop); + } + + public FloatSize getSize() { + return new FloatSize(viewportRectRight - viewportRectLeft, viewportRectBottom - viewportRectTop); + } + + public RectF getViewport() { + return new RectF(viewportRectLeft, + viewportRectTop, + viewportRectRight, + viewportRectBottom); + } + + public FloatSize getPageSize() { + return new FloatSize(pageSizeWidth, pageSizeHeight); + } +} diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java index cc689ed..7e575b5 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/Layer.java @@ -39,7 +39,7 @@ package org.mozilla.gecko.gfx; -import android.graphics.Point; +import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; @@ -50,16 +50,24 @@ import java.util.concurrent.locks.ReentrantLock; public abstract class Layer { private final ReentrantLock mTransactionLock; - protected Point mOrigin; - protected float mResolution; private boolean mInTransaction; - private Point mNewOrigin; + private Rect mNewPosition; private float mNewResolution; - private LayerView mView; + + protected Rect mPosition; + protected float mResolution; public Layer() { + this(null); + } + + public Layer(IntSize size) { mTransactionLock = new ReentrantLock(); - mOrigin = new Point(0, 0); + if (size == null) { + mPosition = new Rect(); + } else { + mPosition = new Rect(0, 0, size.width, size.height); + } mResolution = 1.0f; } @@ -69,12 +77,14 @@ public abstract class Layer { */ public final boolean update(RenderContext context) { if (mTransactionLock.isHeldByCurrentThread()) { - throw new RuntimeException("draw() called while transaction lock held by this thread?!"); + throw new RuntimeException("draw() called while transaction lock held by this " + + "thread?!"); } if (mTransactionLock.tryLock()) { try { - return performUpdates(context); + performUpdates(context); + return true; } finally { mTransactionLock.unlock(); } @@ -83,24 +93,12 @@ public abstract class Layer { return false; } - /** - * Subclasses override this function to draw the layer. - */ + /** Subclasses override this function to draw the layer. */ public abstract void draw(RenderContext context); - /** - * Subclasses override this function to provide access to the size of the layer. - */ - public abstract IntSize getSize(); - - /** - * Given the intrinsic size of the layer, returns the pixel boundaries of the layer rect. - */ - protected RectF getBounds(RenderContext context, FloatSize size) { - float scaleFactor = context.zoomFactor / mResolution; - float x = mOrigin.x * scaleFactor, y = mOrigin.y * scaleFactor; - float width = size.width * scaleFactor, height = size.height * scaleFactor; - return new RectF(x, y, x + width, y + height); + /** Given the intrinsic size of the layer, returns the pixel boundaries of the layer rect. */ + protected RectF getBounds(RenderContext context) { + return RectUtils.scale(new RectF(mPosition), context.zoomFactor / mResolution); } /** @@ -109,68 +107,50 @@ public abstract class Layer { * may be overridden. */ public Region getValidRegion(RenderContext context) { - return new Region(RectUtils.round(getBounds(context, new FloatSize(getSize())))); + return new Region(RectUtils.round(getBounds(context))); } /** * Call this before modifying the layer. Note that, for TileLayers, "modifying the layer" * includes altering the underlying CairoImage in any way. Thus you must call this function * before modifying the byte buffer associated with this layer. - * <p/> + * * This function may block, so you should never call this on the main UI thread. */ - public void beginTransaction(LayerView aView) { - //if (mTransactionLock.isHeldByCurrentThread()) - // throw new RuntimeException("Nested transactions are not supported"); + public void beginTransaction() { + if (mTransactionLock.isHeldByCurrentThread()) + throw new RuntimeException("Nested transactions are not supported"); mTransactionLock.lock(); - mView = aView; mInTransaction = true; mNewResolution = mResolution; } - public void beginTransaction() { - beginTransaction(null); - } - - /** - * Call this when you're done modifying the layer. - */ + /** Call this when you're done modifying the layer. */ public void endTransaction() { if (!mInTransaction) throw new RuntimeException("endTransaction() called outside a transaction"); mInTransaction = false; mTransactionLock.unlock(); - - if (mView != null) - mView.requestRender(); } - /** - * Returns true if the layer is currently in a transaction and false otherwise. - */ + /** Returns true if the layer is currently in a transaction and false otherwise. */ protected boolean inTransaction() { return mInTransaction; } - /** - * Returns the current layer origin. - */ - public Point getOrigin() { - return mOrigin; + /** Returns the current layer position. */ + public Rect getPosition() { + return mPosition; } - /** - * Sets the origin. Only valid inside a transaction. - */ - public void setOrigin(Point newOrigin) { + /** Sets the position. Only valid inside a transaction. */ + public void setPosition(Rect newPosition) { if (!mInTransaction) - throw new RuntimeException("setOrigin() is only valid inside a transaction"); - mNewOrigin = newOrigin; + throw new RuntimeException("setPosition() is only valid inside a transaction"); + mNewPosition = newPosition; } - /** - * Returns the current layer's resolution. - */ + /** Returns the current layer's resolution. */ public float getResolution() { return mResolution; } @@ -179,8 +159,7 @@ public abstract class Layer { * Sets the layer resolution. This value is used to determine how many pixels per * device pixel this layer was rendered at. This will be reflected by scaling by * the reciprocal of the resolution in the layer's transform() function. - * Only valid inside a transaction. - */ + * Only valid inside a transaction. */ public void setResolution(float newResolution) { if (!mInTransaction) throw new RuntimeException("setResolution() is only valid inside a transaction"); @@ -193,22 +172,15 @@ public abstract class Layer { * superclass implementation. Returns false if there is still work to be done after this * update is complete. */ - protected boolean performUpdates(RenderContext context) { - - if (mNewOrigin != null) { - mOrigin = mNewOrigin; - mNewOrigin = null; + protected void performUpdates(RenderContext context) { + if (mNewPosition != null) { + mPosition = mNewPosition; + mNewPosition = null; } if (mNewResolution != 0.0f) { mResolution = mNewResolution; mNewResolution = 0.0f; } - - return true; - } - - protected boolean dimensionChangesPending() { - return (mNewOrigin != null) || (mNewResolution != 0.0f); } public static class RenderContext { @@ -234,8 +206,8 @@ public abstract class Layer { return false; } return RectUtils.fuzzyEquals(viewport, other.viewport) - && pageSize.fuzzyEquals(other.pageSize) - && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor); + && pageSize.fuzzyEquals(other.pageSize) + && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor); } } } diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java index 54def4a..9c497f7 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java @@ -42,8 +42,8 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Point; import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.GestureDetector; @@ -55,172 +55,167 @@ import org.mozilla.gecko.ui.SimpleScaleGestureDetector; import java.util.Timer; import java.util.TimerTask; +import java.util.regex.Pattern; /** * The layer controller manages a tile that represents the visible page. It does panning and * zooming natively by delegating to a panning/zooming controller. Touch events can be dispatched * to a higher-level view. - * <p/> + * * Many methods require that the monitor be held, with a synchronized (controller) { ... } block. */ public class LayerController { - /* The extra area on the sides of the page that we want to buffer to help with - * smooth, asynchronous scrolling. Depending on a device's support for NPOT - * textures, this may be rounded up to the nearest power of two. - */ private static final String LOGTAG = "GeckoLayerController"; - /* If the visible rect is within the danger zone (measured in pixels from each edge of a tile), - * we start aggressively redrawing to minimize checkerboarding. */ - private static final int DANGER_ZONE_X = 75; - private static final int DANGER_ZONE_Y = 150; - /* The time limit for pages to respond with preventDefault on touchevents - * before we begin panning the page */ - private static final int PREVENT_DEFAULT_TIMEOUT = 200; + private Layer mRootLayer; /* The root layer. */ private LayerView mView; /* The main rendering view. */ - private Context mContext; /* The current context. */ private ViewportMetrics mViewportMetrics; /* The current viewport metrics. */ private boolean mWaitForTouchListeners; + private PanZoomController mPanZoomController; + /* + * The panning and zooming controller, which interprets pan and zoom gestures for us and + * updates our visible rect appropriately. + */ + private OnTouchListener mOnTouchListener; /* The touch listener. */ - private GeckoLayerClient mLayerClient; /* The layer client. */ + private GeckoLayerClient mLayerClient; /* The layer client. */ + /* The new color for the checkerboard. */ private int mCheckerboardColor; private boolean mCheckerboardShouldShowChecks; + private boolean mForceRedraw; + + /* The extra area on the sides of the page that we want to buffer to help with + * smooth, asynchronous scrolling. Depending on a device's support for NPOT + * textures, this may be rounded up to the nearest power of two. + */ + public static final IntSize MIN_BUFFER = new IntSize(512, 1024); + + /* If the visible rect is within the danger zone (measured in pixels from each edge of a tile), + * we start aggressively redrawing to minimize checkerboarding. */ + private static final int DANGER_ZONE_X = 75; + private static final int DANGER_ZONE_Y = 150; + + /* The time limit for pages to respond with preventDefault on touchevents + * before we begin panning the page */ + private int mTimeout = 200; + private boolean allowDefaultActions = true; - private Timer allowDefaultTimer = null; - private boolean inTouchSession = false; + private Timer allowDefaultTimer = null; private PointF initialTouchLocation = null; + private static Pattern sColorPattern; + public LayerController(Context context) { mContext = context; + mForceRedraw = true; mViewportMetrics = new ViewportMetrics(); mPanZoomController = new PanZoomController(this); mView = new LayerView(context, this); + mCheckerboardShouldShowChecks = true; } - public void setForceRedraw() { - mForceRedraw = true; + public void onDestroy() { } - public GeckoLayerClient getLayerClient() { - return mLayerClient; - } + public void setRoot(Layer layer) { mRootLayer = layer; } public void setLayerClient(GeckoLayerClient layerClient) { mLayerClient = layerClient; layerClient.setLayerController(this); } - public Layer getRoot() { - return mRootLayer; + public void setForceRedraw() { + mForceRedraw = true; } - public void setRoot(Layer layer) { - mRootLayer = layer; + public Layer getRoot() { return mRootLayer; } + public LayerView getView() { return mView; } + public Context getContext() { return mContext; } + public ViewportMetrics getViewportMetrics() { return mViewportMetrics; } + + public RectF getViewport() { + return mViewportMetrics.getViewport(); } - public LayerView getView() { - return mView; + public FloatSize getViewportSize() { + return mViewportMetrics.getSize(); } - public Context getContext() { - return mContext; + public FloatSize getPageSize() { + return mViewportMetrics.getPageSize(); } - public ViewportMetrics getViewportMetrics() { - return mViewportMetrics; + public PointF getOrigin() { + return mViewportMetrics.getOrigin(); } - /** - * Sets the entire viewport metrics at once. This function does not notify the layer client or - * the pan/zoom controller, so you will need to call notifyLayerClientOfGeometryChange() or - * notifyPanZoomControllerOfGeometryChange() after calling this. You must hold the monitor - * while calling this. - */ - public void setViewportMetrics(ViewportMetrics viewport) { - mViewportMetrics = new ViewportMetrics(viewport); - Log.d(LOGTAG, "setViewportMetrics: " + mViewportMetrics); - // this function may or may not be called on the UI thread, - // but repositionPluginViews must only be called on the UI thread. - //GeckoApp.mAppContext.runOnUiThread(new Runnable() { - // public void run() { - // GeckoApp.mAppContext.repositionPluginViews(false); - // } - //}); - mView.requestRender(); + public float getZoomFactor() { + return mViewportMetrics.getZoomFactor(); } - public RectF getViewport() { - return mViewportMetrics.getViewport(); + public Bitmap getBackgroundPattern() { return getDrawable("background"); } + public Bitmap getShadowPattern() { return getDrawable("shadow"); } + + public PanZoomController getPanZoomController() { return mPanZoomController; } + public GestureDetector.OnGestureListener getGestureListener() { return mPanZoomController; } + public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() { + return mPanZoomController; } + public GestureDetector.OnDoubleTapListener getDoubleTapListener() { return mPanZoomController; } - public FloatSize getViewportSize() { - return mViewportMetrics.getSize(); + public Bitmap getDrawable(String name) { + Resources resources = mContext.getResources(); + int resourceID = resources.getIdentifier(name, "drawable", mContext.getPackageName()); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = false; + return BitmapFactory.decodeResource(mContext.getResources(), resourceID, options); } /** * The view calls this function to indicate that the viewport changed size. It must hold the * monitor while calling it. - * <p/> + * * TODO: Refactor this to use an interface. Expose that interface only to the view and not * to the layer client. That way, the layer client won't be tempted to call this, which might * result in an infinite loop. */ public void setViewportSize(FloatSize size) { - // Resize the viewport, and modify its zoom factor so that the page retains proportionally - // zoomed relative to the screen. - float oldHeight = mViewportMetrics.getSize().height; - float oldWidth = mViewportMetrics.getSize().width; - float oldZoomFactor = mViewportMetrics.getZoomFactor(); - mViewportMetrics.setSize(size); - - // if the viewport got larger (presumably because the vkb went away), and the page - // is smaller than the new viewport size, increase the page size so that the panzoomcontroller - // doesn't zoom in to make it fit (bug 718270). this page size change is in anticipation of - // gecko increasing the page size to match the new viewport size, which will happen the next - // time we get a draw update. - if (size.width >= oldWidth && size.height >= oldHeight) { - FloatSize pageSize = mViewportMetrics.getPageSize(); - if (pageSize.width < size.width || pageSize.height < size.height) { - mViewportMetrics.setPageSize(new FloatSize(Math.max(pageSize.width, size.width), - Math.max(pageSize.height, size.height))); - } - } - - PointF newFocus = new PointF(size.width / 2.0f, size.height / 2.0f); - float newZoomFactor = size.width * oldZoomFactor / oldWidth; - mViewportMetrics.scaleTo(newZoomFactor, newFocus); + ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); + viewportMetrics.setSize(size); + mViewportMetrics = new ViewportMetrics(viewportMetrics); - Log.d(LOGTAG, "setViewportSize: " + mViewportMetrics); - setForceRedraw(); - - if (mLayerClient != null) + if (mLayerClient != null) { mLayerClient.viewportSizeChanged(); + } + } + + /** Scrolls the viewport by the given offset. You must hold the monitor while calling this. */ + public void scrollBy(PointF point) { + ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); + PointF origin = viewportMetrics.getOrigin(); + origin.offset(point.x, point.y); + viewportMetrics.setOrigin(origin); + mViewportMetrics = new ViewportMetrics(viewportMetrics); notifyLayerClientOfGeometryChange(); - mPanZoomController.abortAnimation(); mView.requestRender(); } - public FloatSize getPageSize() { - return mViewportMetrics.getPageSize(); - } - - /** - * Sets the current page size. You must hold the monitor while calling this. - */ + /** Sets the current page size. You must hold the monitor while calling this. */ public void setPageSize(FloatSize size) { if (mViewportMetrics.getPageSize().fuzzyEquals(size)) return; - mViewportMetrics.setPageSize(size); - Log.d(LOGTAG, "setPageSize: " + mViewportMetrics); + ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); + viewportMetrics.setPageSize(size); + mViewportMetrics = new ViewportMetrics(viewportMetrics); - // Page size is owned by the LayerClient, so no need to notify it of + // Page size is owned by the layer client, so no need to notify it of // this change. mView.post(new Runnable() { @@ -231,59 +226,15 @@ public class LayerController { }); } - public PointF getOrigin() { - return mViewportMetrics.getOrigin(); - } - - public float getZoomFactor() { - return mViewportMetrics.getZoomFactor(); - } - - public Bitmap getBackgroundPattern() { - return getDrawable("background"); - } - - public Bitmap getShadowPattern() { - return getDrawable("shadow"); - } - - public PanZoomController getPanZoomController() { - return mPanZoomController; - } - - public GestureDetector.OnGestureListener getGestureListener() { - return mPanZoomController; - } - - public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() { - return mPanZoomController; - } - - public GestureDetector.OnDoubleTapListener getDoubleTapListener() { - return mPanZoomController; - } - - public Bitmap getDrawable(String name) { - Resources resources = mContext.getResources(); - int resourceID = resources.getIdentifier(name, "drawable", mContext.getPackageName()); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = false; - return BitmapFactory.decodeResource(mContext.getResources(), resourceID, options); - } - /** - * Scrolls the viewport by the given offset. You must hold the monitor while calling this. + * Sets the entire viewport metrics at once. This function does not notify the layer client or + * the pan/zoom controller, so you will need to call notifyLayerClientOfGeometryChange() or + * notifyPanZoomControllerOfGeometryChange() after calling this. You must hold the monitor + * while calling this. */ - public void scrollBy(PointF point) { - if (point.equals(0,0)) { - return; - } - - PointF origin = mViewportMetrics.getOrigin(); - origin.offset(point.x, point.y); - mViewportMetrics.setOrigin(origin); - Log.d(LOGTAG, "scrollBy: " + mViewportMetrics); - notifyLayerClientOfGeometryChange(); + public void setViewportMetrics(ViewportMetrics viewport) { + mViewportMetrics = new ViewportMetrics(viewport); + Log.d(LOGTAG, "setViewportMetrics: " + mViewportMetrics); mView.requestRender(); } @@ -292,7 +243,9 @@ public class LayerController { * scale operation. You must hold the monitor while calling this. */ public void scaleWithFocus(float zoomFactor, PointF focus) { - mViewportMetrics.scaleTo(zoomFactor, focus); + ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); + viewportMetrics.scaleTo(zoomFactor, focus); + mViewportMetrics = new ViewportMetrics(viewportMetrics); Log.d(LOGTAG, "scaleWithFocus: " + mViewportMetrics + "; zf=" + zoomFactor); // We assume the zoom level will only be modified by the @@ -301,9 +254,7 @@ public class LayerController { mView.requestRender(); } - public boolean post(Runnable action) { - return mView.post(action); - } + public boolean post(Runnable action) { return mView.post(action); } public void setOnTouchListener(OnTouchListener onTouchListener) { mOnTouchListener = onTouchListener; @@ -314,14 +265,11 @@ public class LayerController { * the geometry changed. */ public void notifyLayerClientOfGeometryChange() { - if (mLayerClient != null) { + if (mLayerClient != null) mLayerClient.geometryChanged(); - } } - /** - * Aborts any pan/zoom animation that is currently in progress. - */ + /** Aborts any pan/zoom animation that is currently in progress. */ public void abortPanZoomAnimation() { if (mPanZoomController != null) { mView.post(new Runnable() { @@ -337,24 +285,16 @@ public class LayerController { * would prefer that the action didn't take place. */ public boolean getRedrawHint() { - // FIXME: Allow redraw while a finger is down, but only if we're about to checkerboard. - // This requires fixing aboutToCheckerboard() to know about the new buffer size. - if (mForceRedraw) { mForceRedraw = false; return true; } - return mPanZoomController.getRedrawHint(); - } - - private RectF getTileRect() { - if (mRootLayer == null) - return new RectF(); + if (!mPanZoomController.getRedrawHint()) { + return false; + } - float x = mRootLayer.getOrigin().x, y = mRootLayer.getOrigin().y; - IntSize layerSize = mRootLayer.getSize(); - return new RectF(x, y, x + layerSize.width, y + layerSize.height); + return aboutToCheckerboard(); } // Returns true if a checkerboard is about to be visible. ... etc. - the rest is truncated
_______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/libreoffice-commits