michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.gfx; michael@0: michael@0: import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; michael@0: import org.mozilla.gecko.util.FloatUtils; michael@0: michael@0: import android.graphics.PointF; michael@0: import android.graphics.RectF; michael@0: import android.util.DisplayMetrics; michael@0: michael@0: /** michael@0: * ImmutableViewportMetrics are used to store the viewport metrics michael@0: * in way that we can access a version of them from multiple threads michael@0: * without having to take a lock michael@0: */ michael@0: public class ImmutableViewportMetrics { michael@0: michael@0: // We need to flatten the RectF and FloatSize structures michael@0: // because Java doesn't have the concept of const classes michael@0: public final float pageRectLeft; michael@0: public final float pageRectTop; michael@0: public final float pageRectRight; michael@0: public final float pageRectBottom; michael@0: public final float cssPageRectLeft; michael@0: public final float cssPageRectTop; michael@0: public final float cssPageRectRight; michael@0: public final float cssPageRectBottom; michael@0: public final float viewportRectLeft; michael@0: public final float viewportRectTop; michael@0: public final float viewportRectRight; michael@0: public final float viewportRectBottom; michael@0: public final float marginLeft; michael@0: public final float marginTop; michael@0: public final float marginRight; michael@0: public final float marginBottom; michael@0: public final float zoomFactor; michael@0: public final boolean isRTL; michael@0: michael@0: public ImmutableViewportMetrics(DisplayMetrics metrics) { michael@0: viewportRectLeft = pageRectLeft = cssPageRectLeft = 0; michael@0: viewportRectTop = pageRectTop = cssPageRectTop = 0; michael@0: viewportRectRight = pageRectRight = cssPageRectRight = metrics.widthPixels; michael@0: viewportRectBottom = pageRectBottom = cssPageRectBottom = metrics.heightPixels; michael@0: marginLeft = marginTop = marginRight = marginBottom = 0; michael@0: zoomFactor = 1.0f; michael@0: isRTL = false; michael@0: } michael@0: michael@0: /** This constructor is used by native code in AndroidJavaWrappers.cpp, be michael@0: * careful when modifying the signature. michael@0: */ michael@0: @WrapElementForJNI(allowMultithread = true) michael@0: public ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop, michael@0: float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft, michael@0: float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom, michael@0: float aViewportRectLeft, float aViewportRectTop, float aViewportRectRight, michael@0: float aViewportRectBottom, float aZoomFactor) michael@0: { michael@0: this(aPageRectLeft, aPageRectTop, michael@0: aPageRectRight, aPageRectBottom, aCssPageRectLeft, michael@0: aCssPageRectTop, aCssPageRectRight, aCssPageRectBottom, michael@0: aViewportRectLeft, aViewportRectTop, aViewportRectRight, michael@0: aViewportRectBottom, 0.0f, 0.0f, 0.0f, 0.0f, aZoomFactor, false); michael@0: } michael@0: michael@0: private ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop, michael@0: float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft, michael@0: float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom, michael@0: float aViewportRectLeft, float aViewportRectTop, float aViewportRectRight, michael@0: float aViewportRectBottom, float aMarginLeft, michael@0: float aMarginTop, float aMarginRight, michael@0: float aMarginBottom, float aZoomFactor, boolean aIsRTL) michael@0: { michael@0: pageRectLeft = aPageRectLeft; michael@0: pageRectTop = aPageRectTop; michael@0: pageRectRight = aPageRectRight; michael@0: pageRectBottom = aPageRectBottom; michael@0: cssPageRectLeft = aCssPageRectLeft; michael@0: cssPageRectTop = aCssPageRectTop; michael@0: cssPageRectRight = aCssPageRectRight; michael@0: cssPageRectBottom = aCssPageRectBottom; michael@0: viewportRectLeft = aViewportRectLeft; michael@0: viewportRectTop = aViewportRectTop; michael@0: viewportRectRight = aViewportRectRight; michael@0: viewportRectBottom = aViewportRectBottom; michael@0: marginLeft = aMarginLeft; michael@0: marginTop = aMarginTop; michael@0: marginRight = aMarginRight; michael@0: marginBottom = aMarginBottom; michael@0: zoomFactor = aZoomFactor; michael@0: isRTL = aIsRTL; michael@0: } michael@0: michael@0: public float getWidth() { michael@0: return viewportRectRight - viewportRectLeft; michael@0: } michael@0: michael@0: public float getHeight() { michael@0: return viewportRectBottom - viewportRectTop; michael@0: } michael@0: michael@0: public float getWidthWithoutMargins() { michael@0: return viewportRectRight - viewportRectLeft - marginLeft - marginRight; michael@0: } michael@0: michael@0: public float getHeightWithoutMargins() { michael@0: return viewportRectBottom - viewportRectTop - marginTop - marginBottom; michael@0: } michael@0: michael@0: public PointF getOrigin() { michael@0: return new PointF(viewportRectLeft, viewportRectTop); michael@0: } michael@0: michael@0: public PointF getMarginOffset() { michael@0: if (isRTL) { michael@0: return new PointF(marginLeft - marginRight, marginTop); michael@0: } michael@0: return new PointF(marginLeft, marginTop); michael@0: } michael@0: michael@0: public FloatSize getSize() { michael@0: return new FloatSize(viewportRectRight - viewportRectLeft, viewportRectBottom - viewportRectTop); michael@0: } michael@0: michael@0: public RectF getViewport() { michael@0: return new RectF(viewportRectLeft, michael@0: viewportRectTop, michael@0: viewportRectRight, michael@0: viewportRectBottom); michael@0: } michael@0: michael@0: public RectF getCssViewport() { michael@0: return RectUtils.scale(getViewport(), 1/zoomFactor); michael@0: } michael@0: michael@0: public RectF getPageRect() { michael@0: return new RectF(pageRectLeft, pageRectTop, pageRectRight, pageRectBottom); michael@0: } michael@0: michael@0: public float getPageWidth() { michael@0: return pageRectRight - pageRectLeft; michael@0: } michael@0: michael@0: public float getPageWidthWithMargins() { michael@0: return (pageRectRight - pageRectLeft) + marginLeft + marginRight; michael@0: } michael@0: michael@0: public float getPageHeight() { michael@0: return pageRectBottom - pageRectTop; michael@0: } michael@0: michael@0: public float getPageHeightWithMargins() { michael@0: return (pageRectBottom - pageRectTop) + marginTop + marginBottom; michael@0: } michael@0: michael@0: public RectF getCssPageRect() { michael@0: return new RectF(cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom); michael@0: } michael@0: michael@0: public RectF getOverscroll() { michael@0: return new RectF(Math.max(0, pageRectLeft - viewportRectLeft), michael@0: Math.max(0, pageRectTop - viewportRectTop), michael@0: Math.max(0, viewportRectRight - pageRectRight), michael@0: Math.max(0, viewportRectBottom - pageRectBottom)); michael@0: } michael@0: michael@0: /* michael@0: * Returns the viewport metrics that represent a linear transition between "this" and "to" at michael@0: * time "t", which is on the scale [0, 1). This function interpolates all values stored in michael@0: * the viewport metrics. michael@0: */ michael@0: public ImmutableViewportMetrics interpolate(ImmutableViewportMetrics to, float t) { michael@0: return new ImmutableViewportMetrics( michael@0: FloatUtils.interpolate(pageRectLeft, to.pageRectLeft, t), michael@0: FloatUtils.interpolate(pageRectTop, to.pageRectTop, t), michael@0: FloatUtils.interpolate(pageRectRight, to.pageRectRight, t), michael@0: FloatUtils.interpolate(pageRectBottom, to.pageRectBottom, t), michael@0: FloatUtils.interpolate(cssPageRectLeft, to.cssPageRectLeft, t), michael@0: FloatUtils.interpolate(cssPageRectTop, to.cssPageRectTop, t), michael@0: FloatUtils.interpolate(cssPageRectRight, to.cssPageRectRight, t), michael@0: FloatUtils.interpolate(cssPageRectBottom, to.cssPageRectBottom, t), michael@0: FloatUtils.interpolate(viewportRectLeft, to.viewportRectLeft, t), michael@0: FloatUtils.interpolate(viewportRectTop, to.viewportRectTop, t), michael@0: FloatUtils.interpolate(viewportRectRight, to.viewportRectRight, t), michael@0: FloatUtils.interpolate(viewportRectBottom, to.viewportRectBottom, t), michael@0: FloatUtils.interpolate(marginLeft, to.marginLeft, t), michael@0: FloatUtils.interpolate(marginTop, to.marginTop, t), michael@0: FloatUtils.interpolate(marginRight, to.marginRight, t), michael@0: FloatUtils.interpolate(marginBottom, to.marginBottom, t), michael@0: FloatUtils.interpolate(zoomFactor, to.zoomFactor, t), michael@0: t >= 0.5 ? to.isRTL : isRTL); michael@0: } michael@0: michael@0: public ImmutableViewportMetrics setViewportSize(float width, float height) { michael@0: if (FloatUtils.fuzzyEquals(width, getWidth()) && FloatUtils.fuzzyEquals(height, getHeight())) { michael@0: return this; michael@0: } michael@0: michael@0: return new ImmutableViewportMetrics( michael@0: pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, michael@0: cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, michael@0: viewportRectLeft, viewportRectTop, viewportRectLeft + width, viewportRectTop + height, michael@0: marginLeft, marginTop, marginRight, marginBottom, michael@0: zoomFactor, isRTL); michael@0: } michael@0: michael@0: public ImmutableViewportMetrics setViewportOrigin(float newOriginX, float newOriginY) { michael@0: return new ImmutableViewportMetrics( michael@0: pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, michael@0: cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, michael@0: newOriginX, newOriginY, newOriginX + getWidth(), newOriginY + getHeight(), michael@0: marginLeft, marginTop, marginRight, marginBottom, michael@0: zoomFactor, isRTL); michael@0: } michael@0: michael@0: public ImmutableViewportMetrics setZoomFactor(float newZoomFactor) { michael@0: return new ImmutableViewportMetrics( michael@0: pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, michael@0: cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, michael@0: viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom, michael@0: marginLeft, marginTop, marginRight, marginBottom, michael@0: newZoomFactor, isRTL); michael@0: } michael@0: michael@0: public ImmutableViewportMetrics offsetViewportBy(float dx, float dy) { michael@0: return setViewportOrigin(viewportRectLeft + dx, viewportRectTop + dy); michael@0: } michael@0: michael@0: public ImmutableViewportMetrics offsetViewportByAndClamp(float dx, float dy) { michael@0: if (isRTL) { michael@0: return setViewportOrigin( michael@0: Math.min(pageRectRight - getWidthWithoutMargins(), Math.max(viewportRectLeft + dx, pageRectLeft)), michael@0: Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeightWithoutMargins()))); michael@0: } michael@0: return setViewportOrigin( michael@0: Math.max(pageRectLeft, Math.min(viewportRectLeft + dx, pageRectRight - getWidthWithoutMargins())), michael@0: Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeightWithoutMargins()))); michael@0: } michael@0: michael@0: public ImmutableViewportMetrics setPageRect(RectF pageRect, RectF cssPageRect) { michael@0: return new ImmutableViewportMetrics( michael@0: pageRect.left, pageRect.top, pageRect.right, pageRect.bottom, michael@0: cssPageRect.left, cssPageRect.top, cssPageRect.right, cssPageRect.bottom, michael@0: viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom, michael@0: marginLeft, marginTop, marginRight, marginBottom, michael@0: zoomFactor, isRTL); michael@0: } michael@0: michael@0: public ImmutableViewportMetrics setMargins(float left, float top, float right, float bottom) { michael@0: if (FloatUtils.fuzzyEquals(left, marginLeft) michael@0: && FloatUtils.fuzzyEquals(top, marginTop) michael@0: && FloatUtils.fuzzyEquals(right, marginRight) michael@0: && FloatUtils.fuzzyEquals(bottom, marginBottom)) { michael@0: return this; michael@0: } michael@0: michael@0: return new ImmutableViewportMetrics( michael@0: pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, michael@0: cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, michael@0: viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom, michael@0: left, top, right, bottom, zoomFactor, isRTL); michael@0: } michael@0: michael@0: public ImmutableViewportMetrics setMarginsFrom(ImmutableViewportMetrics fromMetrics) { michael@0: return setMargins(fromMetrics.marginLeft, michael@0: fromMetrics.marginTop, michael@0: fromMetrics.marginRight, michael@0: fromMetrics.marginBottom); michael@0: } michael@0: michael@0: public ImmutableViewportMetrics setIsRTL(boolean aIsRTL) { michael@0: if (isRTL == aIsRTL) { michael@0: return this; michael@0: } michael@0: michael@0: return new ImmutableViewportMetrics( michael@0: pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, michael@0: cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, michael@0: viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom, michael@0: marginLeft, marginTop, marginRight, marginBottom, zoomFactor, aIsRTL); michael@0: } michael@0: michael@0: /* This will set the zoom factor and re-scale page-size and viewport offset michael@0: * accordingly. The given focus will remain at the same point on the screen michael@0: * after scaling. michael@0: */ michael@0: public ImmutableViewportMetrics scaleTo(float newZoomFactor, PointF focus) { michael@0: // cssPageRect* is invariant, since we're setting the scale factor michael@0: // here. The page rect is based on the CSS page rect. michael@0: float newPageRectLeft = cssPageRectLeft * newZoomFactor; michael@0: float newPageRectTop = cssPageRectTop * newZoomFactor; michael@0: float newPageRectRight = cssPageRectLeft + ((cssPageRectRight - cssPageRectLeft) * newZoomFactor); michael@0: float newPageRectBottom = cssPageRectTop + ((cssPageRectBottom - cssPageRectTop) * newZoomFactor); michael@0: michael@0: PointF origin = getOrigin(); michael@0: origin.offset(focus.x, focus.y); michael@0: origin = PointUtils.scale(origin, newZoomFactor / zoomFactor); michael@0: origin.offset(-focus.x, -focus.y); michael@0: michael@0: return new ImmutableViewportMetrics( michael@0: newPageRectLeft, newPageRectTop, newPageRectRight, newPageRectBottom, michael@0: cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, michael@0: origin.x, origin.y, origin.x + getWidth(), origin.y + getHeight(), michael@0: marginLeft, marginTop, marginRight, marginBottom, michael@0: newZoomFactor, isRTL); michael@0: } michael@0: michael@0: /** Clamps the viewport to remain within the page rect. */ michael@0: private ImmutableViewportMetrics clamp(float marginLeft, float marginTop, michael@0: float marginRight, float marginBottom) { michael@0: RectF newViewport = getViewport(); michael@0: PointF offset = getMarginOffset(); michael@0: michael@0: // The viewport bounds ought to never exceed the page bounds. michael@0: if (newViewport.right > pageRectRight + marginLeft + marginRight) michael@0: newViewport.offset((pageRectRight + marginLeft + marginRight) - newViewport.right, 0); michael@0: if (newViewport.left < pageRectLeft) michael@0: newViewport.offset(pageRectLeft - newViewport.left, 0); michael@0: michael@0: if (newViewport.bottom > pageRectBottom + marginTop + marginBottom) michael@0: newViewport.offset(0, (pageRectBottom + marginTop + marginBottom) - newViewport.bottom); michael@0: if (newViewport.top < pageRectTop) michael@0: newViewport.offset(0, pageRectTop - newViewport.top); michael@0: michael@0: return new ImmutableViewportMetrics( michael@0: pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, michael@0: cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, michael@0: newViewport.left, newViewport.top, newViewport.right, newViewport.bottom, michael@0: marginLeft, marginTop, marginRight, marginBottom, michael@0: zoomFactor, isRTL); michael@0: } michael@0: michael@0: public ImmutableViewportMetrics clamp() { michael@0: return clamp(0, 0, 0, 0); michael@0: } michael@0: michael@0: public ImmutableViewportMetrics clampWithMargins() { michael@0: return clamp(marginLeft, marginTop, michael@0: marginRight, marginBottom); michael@0: } michael@0: michael@0: public boolean fuzzyEquals(ImmutableViewportMetrics other) { michael@0: // Don't bother checking the pageRectXXX values because they are a product michael@0: // of the cssPageRectXXX values and the zoomFactor, except with more rounding michael@0: // error. Checking those is both inefficient and can lead to false negatives. michael@0: // michael@0: // This doesn't return false if the margins differ as none of the users michael@0: // of this function are interested in the margins in that way. michael@0: return FloatUtils.fuzzyEquals(cssPageRectLeft, other.cssPageRectLeft) michael@0: && FloatUtils.fuzzyEquals(cssPageRectTop, other.cssPageRectTop) michael@0: && FloatUtils.fuzzyEquals(cssPageRectRight, other.cssPageRectRight) michael@0: && FloatUtils.fuzzyEquals(cssPageRectBottom, other.cssPageRectBottom) michael@0: && FloatUtils.fuzzyEquals(viewportRectLeft, other.viewportRectLeft) michael@0: && FloatUtils.fuzzyEquals(viewportRectTop, other.viewportRectTop) michael@0: && FloatUtils.fuzzyEquals(viewportRectRight, other.viewportRectRight) michael@0: && FloatUtils.fuzzyEquals(viewportRectBottom, other.viewportRectBottom) michael@0: && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor); michael@0: } michael@0: michael@0: @Override michael@0: public String toString() { michael@0: return "ImmutableViewportMetrics v=(" + viewportRectLeft + "," + viewportRectTop + "," michael@0: + viewportRectRight + "," + viewportRectBottom + ") p=(" + pageRectLeft + "," michael@0: + pageRectTop + "," + pageRectRight + "," + pageRectBottom + ") c=(" michael@0: + cssPageRectLeft + "," + cssPageRectTop + "," + cssPageRectRight + "," michael@0: + cssPageRectBottom + ") m=(" + marginLeft + "," michael@0: + marginTop + "," + marginRight + "," michael@0: + marginBottom + ") z=" + zoomFactor + ", rtl=" + isRTL; michael@0: } michael@0: }