/* * A touch point (x,y) occurs in LayerView, this point should be displayed * in the center of the zoomed view. The returned point is the position of * the Top-Left zoomed view point on the screen device */ private PointF getZoomedViewTopLeftPositionFromTouchPosition(float x, float y) { ImmutableViewportMetrics metrics = layerView.getViewportMetrics(); final float parentWidth = metrics.getWidth(); final float parentHeight = metrics.getHeight(); // See comments in getUnzoomedPositionFromPointInZoomedView, but the // transformations here are largely the reverse of that function. float visibleContentPixels = viewWidth / zoomFactor; float maxContentOffset = parentWidth - visibleContentPixels; float maxZoomedViewOffset = parentWidth - viewContainerWidth; float contentPixelOffset = x - (visibleContentPixels / 2.0f); returnValue.x = (int) (contentPixelOffset * (maxZoomedViewOffset / maxContentOffset)); visibleContentPixels = viewHeight / zoomFactor; maxContentOffset = parentHeight - visibleContentPixels; maxZoomedViewOffset = parentHeight - (viewContainerHeight - toolbarHeight); contentPixelOffset = y - (visibleContentPixels / 2.0f); float unscaledViewOffset = layerView.getSurfaceTranslation() - offsetDueToToolBarPosition; returnValue.y = (int) ((contentPixelOffset * (maxZoomedViewOffset / maxContentOffset)) + unscaledViewOffset); return returnValue; }
public void handleMessage(String event, JSONObject message) { try { if (MESSAGE_ZOOM_RECT.equals(event)) { float x = (float) message.getDouble("x"); float y = (float) message.getDouble("y"); final RectF zoomRect = new RectF(x, y, x + (float) message.getDouble("w"), y + (float) message.getDouble("h")); mTarget.post( new Runnable() { public void run() { animatedZoomTo(zoomRect); } }); } else if (MESSAGE_ZOOM_PAGE.equals(event)) { ImmutableViewportMetrics metrics = getMetrics(); RectF cssPageRect = metrics.getCssPageRect(); RectF viewableRect = metrics.getCssViewport(); float y = viewableRect.top; // attempt to keep zoom keep focused on the center of the viewport float newHeight = viewableRect.height() * cssPageRect.width() / viewableRect.width(); float dh = viewableRect.height() - newHeight; // increase in the height final RectF r = new RectF(0.0f, y + dh / 2, cssPageRect.width(), y + dh / 2 + newHeight); mTarget.post( new Runnable() { public void run() { animatedZoomTo(r); } }); } } catch (Exception e) { Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); } }
@Override public void requestZoomedViewRender() { if (stopUpdateView) { return; } // remove pending runnable ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable); // "requestZoomedViewRender" can be called very often by Gecko (endDrawing in LayerRender) // without // any thing changed in the zoomed area (useless calls from the "zoomed area" point of view). // "requestZoomedViewRender" can take time to re-render the zoomed view, it depends of the // complexity // of the html on this area. // To avoid to slow down the application, the 2 following cases are tested: // 1- Last render is still running, plan another render later. if (isRendering()) { // post a new runnable DELAY_BEFORE_NEXT_RENDER_REQUEST_MS later // We need to post with a delay to be sure that the last call to requestZoomedViewRender will // be done. // For a static html page WITHOUT any animation/video, there is a last call to endDrawing and // we need to make // the zoomed render on this last call. ThreadUtils.postDelayedToUiThread(requestRenderRunnable, DELAY_BEFORE_NEXT_RENDER_REQUEST_MS); return; } // 2- Current render occurs too early, plan another render later. if (renderFrequencyTooHigh()) { // post a new runnable DELAY_BEFORE_NEXT_RENDER_REQUEST_MS later // We need to post with a delay to be sure that the last call to requestZoomedViewRender will // be done. // For a page WITH animation/video, the animation/video can be stopped, and we need to make // the zoomed render on this last call. ThreadUtils.postDelayedToUiThread(requestRenderRunnable, DELAY_BEFORE_NEXT_RENDER_REQUEST_MS); return; } startTimeReRender = System.nanoTime(); // Allocate the buffer if it's the first call. // Change the buffer size if it's not the right size. updateBufferSize(); int tabId = Tabs.getInstance().getSelectedTab().getId(); ImmutableViewportMetrics metrics = layerView.getViewportMetrics(); PointF origin = metrics.getOrigin(); final int xPos = (int) origin.x + lastPosition.x; final int yPos = (int) origin.y + lastPosition.y; GeckoEvent e = GeckoEvent.createZoomedViewEvent( tabId, xPos, yPos, viewWidth, viewHeight, zoomFactor * metrics.zoomFactor, buffer); GeckoAppShell.sendEventToGecko(e); }
private ImmutableViewportMetrics getValidViewportMetrics( ImmutableViewportMetrics viewportMetrics) { /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */ float zoomFactor = viewportMetrics.zoomFactor; RectF pageRect = viewportMetrics.getPageRect(); RectF viewport = viewportMetrics.getViewport(); float focusX = viewport.width() / 2.0f; float focusY = viewport.height() / 2.0f; float minZoomFactor = 0.0f; float maxZoomFactor = MAX_ZOOM; ZoomConstraints constraints = mTarget.getZoomConstraints(); if (constraints.getMinZoom() > 0) minZoomFactor = constraints.getMinZoom(); if (constraints.getMaxZoom() > 0) maxZoomFactor = constraints.getMaxZoom(); if (!constraints.getAllowZoom()) { // If allowZoom is false, clamp to the default zoom level. maxZoomFactor = minZoomFactor = constraints.getDefaultZoom(); } // Ensure minZoomFactor keeps the page at least as big as the viewport. if (pageRect.width() > 0) { float scaleFactor = viewport.width() / pageRect.width(); minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor); if (viewport.width() > pageRect.width()) focusX = 0.0f; } if (pageRect.height() > 0) { float scaleFactor = viewport.height() / pageRect.height(); minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor); if (viewport.height() > pageRect.height()) focusY = 0.0f; } maxZoomFactor = Math.max(maxZoomFactor, minZoomFactor); if (zoomFactor < minZoomFactor) { // if one (or both) of the page dimensions is smaller than the viewport, // zoom using the top/left as the focus on that axis. this prevents the // scenario where, if both dimensions are smaller than the viewport, but // by different scale factors, we end up scrolled to the end on one axis // after applying the scale PointF center = new PointF(focusX, focusY); viewportMetrics = viewportMetrics.scaleTo(minZoomFactor, center); } else if (zoomFactor > maxZoomFactor) { PointF center = new PointF(viewport.width() / 2.0f, viewport.height() / 2.0f); viewportMetrics = viewportMetrics.scaleTo(maxZoomFactor, center); } /* Now we pan to the right origin. */ viewportMetrics = viewportMetrics.clamp(); return viewportMetrics; }
private void moveUsingGeckoPosition(int leftFromGecko, int topFromGecko) { ImmutableViewportMetrics metrics = layerView.getViewportMetrics(); final float parentHeight = metrics.getHeight(); // moveToolbar is called before getZoomedViewTopLeftPositionFromTouchPosition in order to // correctly center vertically the zoomed area moveToolbar((topFromGecko * metrics.zoomFactor > parentHeight / 2)); PointF convertedPosition = getZoomedViewTopLeftPositionFromTouchPosition( (leftFromGecko * metrics.zoomFactor), (topFromGecko * metrics.zoomFactor)); moveZoomedView( metrics, convertedPosition.x, convertedPosition.y, StartPointUpdate.GECKO_POSITION); }
private void setCapturedSize(ImmutableViewportMetrics metrics) { float parentMinSize = Math.min(metrics.getWidth(), metrics.getHeight()); viewWidth = (int) ((parentMinSize * W_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor); viewHeight = (int) ((parentMinSize * H_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor); viewContainerHeight = viewHeight + toolbarHeight + 2 * containterSize; // Top and bottom shadows viewContainerWidth = viewWidth + 2 * containterSize; // Right and left shadows // Display in zoomedview is corrupted when width is an odd number // More details about this issue here: bug 776906 comment 11 viewWidth &= ~0x1; }
/* Performs a bounce-back animation to the given viewport metrics. */ private void bounce(ImmutableViewportMetrics metrics) { stopAnimationTimer(); ImmutableViewportMetrics bounceStartMetrics = getMetrics(); if (bounceStartMetrics.fuzzyEquals(metrics)) { setState(PanZoomState.NOTHING); return; } // At this point we have already set mState to BOUNCE or ANIMATED_ZOOM, so // getRedrawHint() is returning false. This means we can safely call // setAnimationTarget to set the new final display port and not have it get // clobbered by display ports from intermediate animation frames. mTarget.setAnimationTarget(metrics); startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics)); }
/* Performs one frame of a bounce animation. */ private void advanceBounce() { synchronized (mTarget.getLock()) { float t = easeOut(mBounceFrame * Axis.MS_PER_FRAME / 256f); ImmutableViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t); mTarget.setViewportMetrics(newMetrics); mBounceFrame++; } }
/* * Convert a click from ZoomedView. Return the position of the click in the * LayerView */ private PointF getUnzoomedPositionFromPointInZoomedView(float x, float y) { ImmutableViewportMetrics metrics = layerView.getViewportMetrics(); final float parentWidth = metrics.getWidth(); final float parentHeight = metrics.getHeight(); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams(); // The number of unzoomed content pixels that can be displayed in the // zoomed area. float visibleContentPixels = viewWidth / zoomFactor; // The offset in content pixels of the leftmost zoomed pixel from the // layerview's left edge when the zoomed view is moved to the right as // far as it can go. float maxContentOffset = parentWidth - visibleContentPixels; // The maximum offset in screen pixels that the zoomed view can have float maxZoomedViewOffset = parentWidth - viewContainerWidth; // The above values allow us to compute the term // maxContentOffset / maxZoomedViewOffset // which is the number of content pixels that we should move over by // for every screen pixel that the zoomed view is moved over by. // This allows a smooth transition from when the zoomed view is at the // leftmost extent to when it is at the rightmost extent. // This is the offset in content pixels of the leftmost zoomed pixel // visible in the zoomed view. This value is relative to the layerview // edge. float zoomedContentOffset = ((float) params.leftMargin) * maxContentOffset / maxZoomedViewOffset; returnValue.x = (int) (zoomedContentOffset + (x / zoomFactor)); // Same comments here vertically visibleContentPixels = viewHeight / zoomFactor; maxContentOffset = parentHeight - visibleContentPixels; maxZoomedViewOffset = parentHeight - (viewContainerHeight - toolbarHeight); float zoomedAreaOffset = (float) params.topMargin + offsetDueToToolBarPosition - layerView.getSurfaceTranslation(); zoomedContentOffset = zoomedAreaOffset * maxContentOffset / maxZoomedViewOffset; returnValue.y = (int) (zoomedContentOffset + ((y - offsetDueToToolBarPosition) / zoomFactor)); return returnValue; }
/** * Zoom to a specified rect IN CSS PIXELS. * * <p>While we usually use device pixels, @zoomToRect must be specified in CSS pixels. */ private boolean animatedZoomTo(RectF zoomToRect) { setState(PanZoomState.ANIMATED_ZOOM); final float startZoom = getMetrics().zoomFactor; RectF viewport = getMetrics().getViewport(); // 1. adjust the aspect ratio of zoomToRect to match that of the current viewport, // enlarging as necessary (if it gets too big, it will get shrunk in the next step). // while enlarging make sure we enlarge equally on both sides to keep the target rect // centered. float targetRatio = viewport.width() / viewport.height(); float rectRatio = zoomToRect.width() / zoomToRect.height(); if (FloatUtils.fuzzyEquals(targetRatio, rectRatio)) { // all good, do nothing } else if (targetRatio < rectRatio) { // need to increase zoomToRect height float newHeight = zoomToRect.width() / targetRatio; zoomToRect.top -= (newHeight - zoomToRect.height()) / 2; zoomToRect.bottom = zoomToRect.top + newHeight; } else { // targetRatio > rectRatio) { // need to increase zoomToRect width float newWidth = targetRatio * zoomToRect.height(); zoomToRect.left -= (newWidth - zoomToRect.width()) / 2; zoomToRect.right = zoomToRect.left + newWidth; } float finalZoom = viewport.width() / zoomToRect.width(); ImmutableViewportMetrics finalMetrics = getMetrics(); finalMetrics = finalMetrics.setViewportOrigin( zoomToRect.left * finalMetrics.zoomFactor, zoomToRect.top * finalMetrics.zoomFactor); finalMetrics = finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f)); // 2. now run getValidViewportMetrics on it, so that the target viewport is // clamped down to prevent overscroll, over-zoom, and other bad conditions. finalMetrics = getValidViewportMetrics(finalMetrics); bounce(finalMetrics); return true; }
/** * Scales the viewport, keeping the given focus point in the same place before and after the scale * operation. You must hold the monitor while calling this. */ private void scaleWithFocus(float zoomFactor, PointF focus) { ImmutableViewportMetrics viewportMetrics = getMetrics(); viewportMetrics = viewportMetrics.scaleTo(zoomFactor, focus); mTarget.setViewportMetrics(viewportMetrics); }