/* -*- 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.libreoffice.LibreOfficeMainActivity; import android.graphics.PointF; import android.graphics.RectF; import android.util.DisplayMetrics; import org.libreoffice.LOKitShell; import org.mozilla.gecko.ZoomConstraints; import java.util.List; public class GeckoLayerClient implements PanZoomTarget { private static final String LOGTAG = GeckoLayerClient.class.getSimpleName(); private LayerRenderer mLayerRenderer; private LibreOfficeMainActivity mContext; private IntSize mScreenSize; private DisplayPortMetrics mDisplayPort; private ComposedTileLayer mLowResLayer; private ComposedTileLayer mRootLayer; private boolean mForceRedraw; /* 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) whenever reading multiple fields from mViewportMetrics without synchronization (i.e. in * case 1 above) you should always first 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; private ZoomConstraints mZoomConstraints; private boolean mIsReady; private PanZoomController mPanZoomController; private LayerView mView; private final DisplayPortCalculator mDisplayPortCalculator; public GeckoLayerClient(LibreOfficeMainActivity context) { // we can fill these in with dummy values because they are always written // to before being read mContext = context; mScreenSize = new IntSize(0, 0); mDisplayPort = new DisplayPortMetrics(); mDisplayPortCalculator = new DisplayPortCalculator(mContext); mForceRedraw = true; DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); mViewportMetrics = new ImmutableViewportMetrics(displayMetrics); } public void setView(LayerView view) { mView = view; mPanZoomController = PanZoomController.Factory.create(mContext, this, view); mView.connect(this); } public void notifyReady() { mIsReady = true; mRootLayer = new DynamicTileLayer(mContext); mLowResLayer = new FixedZoomTileLayer(mContext); mLayerRenderer = new LayerRenderer(mView); mView.setLayerRenderer(mLayerRenderer); sendResizeEventIfNecessary(false); mView.requestRender(); } public void destroy() { mPanZoomController.destroy(); } Layer getRoot() { return mIsReady ? mRootLayer : null; } Layer getLowResLayer() { return mIsReady ? mLowResLayer : null; } public LayerView getView() { return mView; } /** * Returns true if this controller is fine with performing a redraw operation or false if it * would prefer that the action didn't take place. */ private boolean getRedrawHint() { if (mForceRedraw) { mForceRedraw = false; return true; } if (!mPanZoomController.getRedrawHint()) { return false; } return mDisplayPortCalculator.aboutToCheckerboard(mViewportMetrics, mPanZoomController.getVelocityVector(), getDisplayPort()); } /** * The view calls this function to indicate that the viewport changed size. It must hold the * monitor while calling it. * * 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. */ void setViewportSize(FloatSize size, boolean forceResizeEvent) { mViewportMetrics = mViewportMetrics.setViewportSize(size.width, size.height); sendResizeEventIfNecessary(forceResizeEvent); } PanZoomController getPanZoomController() { return mPanZoomController; } /* Informs Gecko that the screen size has changed. * @param force: If true, a resize event will always be sent, otherwise * it is only sent if size has changed. */ private void sendResizeEventIfNecessary(boolean force) { DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels); if (!force && mScreenSize.equals(newScreenSize)) { return; } mScreenSize = newScreenSize; LOKitShell.sendSizeChangedEvent(mScreenSize.width, mScreenSize.height); } /** * Sets the current page rect. You must hold the monitor while calling this. */ private void setPageRect(RectF rect, RectF cssRect) { // Since the "rect" is always just a multiple of "cssRect" we don't need to // check both; this function assumes that both "rect" and "cssRect" are relative // the zoom factor in mViewportMetrics. if (mViewportMetrics.getCssPageRect().equals(cssRect)) return; mViewportMetrics = mViewportMetrics.setPageRect(rect, cssRect); // Page size is owned by the layer client, so no need to notify it of // this change. post(new Runnable() { public void run() { mPanZoomController.pageRectUpdated(); mView.requestRender(); } }); } private void adjustViewport(DisplayPortMetrics displayPort) { ImmutableViewportMetrics metrics = getViewportMetrics(); ImmutableViewportMetrics clampedMetrics = metrics.clamp(); if (displayPort == null) { displayPort = mDisplayPortCalculator.calculate(metrics, mPanZoomController.getVelocityVector()); } mDisplayPort = displayPort; reevaluateTiles(); } /** * Aborts any pan/zoom animation that is currently in progress. */ public void abortPanZoomAnimation() { if (mPanZoomController != null) { mView.post(new Runnable() { public void run() { mPanZoomController.abortAnimation(); } }); } } public void setZoomConstraints(ZoomConstraints constraints) { mZoomConstraints = constraints; } /** The compositor invokes this function whenever it determines that the page rect * has changed (based on the information it gets from layout). If setFirstPaintViewport * is invoked on a frame, then this function will not be. For any given frame, this * function will be invoked before syncViewportInfo. */ public void setPageRect(float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) { synchronized (getLock()) { RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom); float ourZoom = getViewportMetrics().zoomFactor; setPageRect(RectUtils.scale(cssPageRect, ourZoom), cssPageRect); // Here the page size of the document has changed, but the document being displayed // is still the same. Therefore, we don't need to send anything to browser.js; any // changes we need to make to the display port will get sent the next time we call // adjustViewport(). } } private DisplayPortMetrics getDisplayPort() { return mDisplayPort; } public void beginDrawing() { mLowResLayer.beginTransaction(); mRootLayer.beginTransaction(); } public void endDrawing() { mLowResLayer.endTransaction(); mRootLayer.endTransaction(); } private void geometryChanged() { sendResizeEventIfNecessary(false); if (getRedrawHint()) { adjustViewport(null); } } /** Implementation of PanZoomTarget */ @Override public ImmutableViewportMetrics getViewportMetrics() { return mViewportMetrics; } /** Implementation of PanZoomTarget */ @Override public ZoomConstraints getZoomConstraints() { return mZoomConstraints; } /** Implementation of PanZoomTarget */ @Override public void setAnimationTarget(ImmutableViewportMetrics viewport) { if (mIsReady) { // We know what the final viewport of the animation is going to be, so // immediately request a draw of that area by setting the display port // accordingly. This way we should have the content pre-rendered by the // time the animation is done. DisplayPortMetrics displayPort = mDisplayPortCalculator.calculate(viewport, null); adjustViewport(displayPort); } } /** Implementation of PanZoomTarget * You must hold the monitor while calling this. */ @Override public void setViewportMetrics(ImmutableViewportMetrics viewport) { mViewportMetrics = viewport; mView.requestRender(); if (mIsReady) { geometryChanged(); } } /** Implementation of PanZoomTarget */ @Override public void forceRedraw() { mForceRedraw = true; if (mIsReady) { geometryChanged(); } } /** Implementation of PanZoomTarget */ @Override public boolean post(Runnable action) { return mView.post(action); } /** Implementation of PanZoomTarget */ @Override public Object getLock() { return this; } public PointF convertViewPointToLayerPoint(PointF viewPoint) { ImmutableViewportMetrics viewportMetrics = mViewportMetrics; PointF origin = viewportMetrics.getOrigin(); float zoom = viewportMetrics.zoomFactor; return new PointF( ((viewPoint.x + origin.x) / zoom), ((viewPoint.y + origin.y) / zoom)); } /** Implementation of PanZoomTarget */ @Override public boolean isFullScreen() { return false; } public void zoomTo(RectF rect) { if (mPanZoomController instanceof JavaPanZoomController) { ((JavaPanZoomController) mPanZoomController).animatedZoomTo(rect); } } /** * Move the viewport to the desired point, and change the zoom level. */ public void moveTo(PointF point, Float zoom) { if (mPanZoomController instanceof JavaPanZoomController) { ((JavaPanZoomController) mPanZoomController).animatedMove(point, zoom); } } public void zoomTo(float pageWidth, float pageHeight) { zoomTo(new RectF(0, 0, pageWidth, pageHeight)); } public void forceRender() { mView.requestRender(); } /* Root Layer Access */ private void reevaluateTiles() { mLowResLayer.reevaluateTiles(mViewportMetrics, mDisplayPort); mRootLayer.reevaluateTiles(mViewportMetrics, mDisplayPort); } public void clearAndResetlayers() { mLowResLayer.clearAndReset(); mRootLayer.clearAndReset(); } public void invalidateTiles(List tilesToInvalidate, RectF rect) { mLowResLayer.invalidateTiles(tilesToInvalidate, rect); mRootLayer.invalidateTiles(tilesToInvalidate, rect); } }