1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/gfx/ImmutableViewportMetrics.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,374 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.gfx; 1.10 + 1.11 +import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; 1.12 +import org.mozilla.gecko.util.FloatUtils; 1.13 + 1.14 +import android.graphics.PointF; 1.15 +import android.graphics.RectF; 1.16 +import android.util.DisplayMetrics; 1.17 + 1.18 +/** 1.19 + * ImmutableViewportMetrics are used to store the viewport metrics 1.20 + * in way that we can access a version of them from multiple threads 1.21 + * without having to take a lock 1.22 + */ 1.23 +public class ImmutableViewportMetrics { 1.24 + 1.25 + // We need to flatten the RectF and FloatSize structures 1.26 + // because Java doesn't have the concept of const classes 1.27 + public final float pageRectLeft; 1.28 + public final float pageRectTop; 1.29 + public final float pageRectRight; 1.30 + public final float pageRectBottom; 1.31 + public final float cssPageRectLeft; 1.32 + public final float cssPageRectTop; 1.33 + public final float cssPageRectRight; 1.34 + public final float cssPageRectBottom; 1.35 + public final float viewportRectLeft; 1.36 + public final float viewportRectTop; 1.37 + public final float viewportRectRight; 1.38 + public final float viewportRectBottom; 1.39 + public final float marginLeft; 1.40 + public final float marginTop; 1.41 + public final float marginRight; 1.42 + public final float marginBottom; 1.43 + public final float zoomFactor; 1.44 + public final boolean isRTL; 1.45 + 1.46 + public ImmutableViewportMetrics(DisplayMetrics metrics) { 1.47 + viewportRectLeft = pageRectLeft = cssPageRectLeft = 0; 1.48 + viewportRectTop = pageRectTop = cssPageRectTop = 0; 1.49 + viewportRectRight = pageRectRight = cssPageRectRight = metrics.widthPixels; 1.50 + viewportRectBottom = pageRectBottom = cssPageRectBottom = metrics.heightPixels; 1.51 + marginLeft = marginTop = marginRight = marginBottom = 0; 1.52 + zoomFactor = 1.0f; 1.53 + isRTL = false; 1.54 + } 1.55 + 1.56 + /** This constructor is used by native code in AndroidJavaWrappers.cpp, be 1.57 + * careful when modifying the signature. 1.58 + */ 1.59 + @WrapElementForJNI(allowMultithread = true) 1.60 + public ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop, 1.61 + float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft, 1.62 + float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom, 1.63 + float aViewportRectLeft, float aViewportRectTop, float aViewportRectRight, 1.64 + float aViewportRectBottom, float aZoomFactor) 1.65 + { 1.66 + this(aPageRectLeft, aPageRectTop, 1.67 + aPageRectRight, aPageRectBottom, aCssPageRectLeft, 1.68 + aCssPageRectTop, aCssPageRectRight, aCssPageRectBottom, 1.69 + aViewportRectLeft, aViewportRectTop, aViewportRectRight, 1.70 + aViewportRectBottom, 0.0f, 0.0f, 0.0f, 0.0f, aZoomFactor, false); 1.71 + } 1.72 + 1.73 + private ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop, 1.74 + float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft, 1.75 + float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom, 1.76 + float aViewportRectLeft, float aViewportRectTop, float aViewportRectRight, 1.77 + float aViewportRectBottom, float aMarginLeft, 1.78 + float aMarginTop, float aMarginRight, 1.79 + float aMarginBottom, float aZoomFactor, boolean aIsRTL) 1.80 + { 1.81 + pageRectLeft = aPageRectLeft; 1.82 + pageRectTop = aPageRectTop; 1.83 + pageRectRight = aPageRectRight; 1.84 + pageRectBottom = aPageRectBottom; 1.85 + cssPageRectLeft = aCssPageRectLeft; 1.86 + cssPageRectTop = aCssPageRectTop; 1.87 + cssPageRectRight = aCssPageRectRight; 1.88 + cssPageRectBottom = aCssPageRectBottom; 1.89 + viewportRectLeft = aViewportRectLeft; 1.90 + viewportRectTop = aViewportRectTop; 1.91 + viewportRectRight = aViewportRectRight; 1.92 + viewportRectBottom = aViewportRectBottom; 1.93 + marginLeft = aMarginLeft; 1.94 + marginTop = aMarginTop; 1.95 + marginRight = aMarginRight; 1.96 + marginBottom = aMarginBottom; 1.97 + zoomFactor = aZoomFactor; 1.98 + isRTL = aIsRTL; 1.99 + } 1.100 + 1.101 + public float getWidth() { 1.102 + return viewportRectRight - viewportRectLeft; 1.103 + } 1.104 + 1.105 + public float getHeight() { 1.106 + return viewportRectBottom - viewportRectTop; 1.107 + } 1.108 + 1.109 + public float getWidthWithoutMargins() { 1.110 + return viewportRectRight - viewportRectLeft - marginLeft - marginRight; 1.111 + } 1.112 + 1.113 + public float getHeightWithoutMargins() { 1.114 + return viewportRectBottom - viewportRectTop - marginTop - marginBottom; 1.115 + } 1.116 + 1.117 + public PointF getOrigin() { 1.118 + return new PointF(viewportRectLeft, viewportRectTop); 1.119 + } 1.120 + 1.121 + public PointF getMarginOffset() { 1.122 + if (isRTL) { 1.123 + return new PointF(marginLeft - marginRight, marginTop); 1.124 + } 1.125 + return new PointF(marginLeft, marginTop); 1.126 + } 1.127 + 1.128 + public FloatSize getSize() { 1.129 + return new FloatSize(viewportRectRight - viewportRectLeft, viewportRectBottom - viewportRectTop); 1.130 + } 1.131 + 1.132 + public RectF getViewport() { 1.133 + return new RectF(viewportRectLeft, 1.134 + viewportRectTop, 1.135 + viewportRectRight, 1.136 + viewportRectBottom); 1.137 + } 1.138 + 1.139 + public RectF getCssViewport() { 1.140 + return RectUtils.scale(getViewport(), 1/zoomFactor); 1.141 + } 1.142 + 1.143 + public RectF getPageRect() { 1.144 + return new RectF(pageRectLeft, pageRectTop, pageRectRight, pageRectBottom); 1.145 + } 1.146 + 1.147 + public float getPageWidth() { 1.148 + return pageRectRight - pageRectLeft; 1.149 + } 1.150 + 1.151 + public float getPageWidthWithMargins() { 1.152 + return (pageRectRight - pageRectLeft) + marginLeft + marginRight; 1.153 + } 1.154 + 1.155 + public float getPageHeight() { 1.156 + return pageRectBottom - pageRectTop; 1.157 + } 1.158 + 1.159 + public float getPageHeightWithMargins() { 1.160 + return (pageRectBottom - pageRectTop) + marginTop + marginBottom; 1.161 + } 1.162 + 1.163 + public RectF getCssPageRect() { 1.164 + return new RectF(cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom); 1.165 + } 1.166 + 1.167 + public RectF getOverscroll() { 1.168 + return new RectF(Math.max(0, pageRectLeft - viewportRectLeft), 1.169 + Math.max(0, pageRectTop - viewportRectTop), 1.170 + Math.max(0, viewportRectRight - pageRectRight), 1.171 + Math.max(0, viewportRectBottom - pageRectBottom)); 1.172 + } 1.173 + 1.174 + /* 1.175 + * Returns the viewport metrics that represent a linear transition between "this" and "to" at 1.176 + * time "t", which is on the scale [0, 1). This function interpolates all values stored in 1.177 + * the viewport metrics. 1.178 + */ 1.179 + public ImmutableViewportMetrics interpolate(ImmutableViewportMetrics to, float t) { 1.180 + return new ImmutableViewportMetrics( 1.181 + FloatUtils.interpolate(pageRectLeft, to.pageRectLeft, t), 1.182 + FloatUtils.interpolate(pageRectTop, to.pageRectTop, t), 1.183 + FloatUtils.interpolate(pageRectRight, to.pageRectRight, t), 1.184 + FloatUtils.interpolate(pageRectBottom, to.pageRectBottom, t), 1.185 + FloatUtils.interpolate(cssPageRectLeft, to.cssPageRectLeft, t), 1.186 + FloatUtils.interpolate(cssPageRectTop, to.cssPageRectTop, t), 1.187 + FloatUtils.interpolate(cssPageRectRight, to.cssPageRectRight, t), 1.188 + FloatUtils.interpolate(cssPageRectBottom, to.cssPageRectBottom, t), 1.189 + FloatUtils.interpolate(viewportRectLeft, to.viewportRectLeft, t), 1.190 + FloatUtils.interpolate(viewportRectTop, to.viewportRectTop, t), 1.191 + FloatUtils.interpolate(viewportRectRight, to.viewportRectRight, t), 1.192 + FloatUtils.interpolate(viewportRectBottom, to.viewportRectBottom, t), 1.193 + FloatUtils.interpolate(marginLeft, to.marginLeft, t), 1.194 + FloatUtils.interpolate(marginTop, to.marginTop, t), 1.195 + FloatUtils.interpolate(marginRight, to.marginRight, t), 1.196 + FloatUtils.interpolate(marginBottom, to.marginBottom, t), 1.197 + FloatUtils.interpolate(zoomFactor, to.zoomFactor, t), 1.198 + t >= 0.5 ? to.isRTL : isRTL); 1.199 + } 1.200 + 1.201 + public ImmutableViewportMetrics setViewportSize(float width, float height) { 1.202 + if (FloatUtils.fuzzyEquals(width, getWidth()) && FloatUtils.fuzzyEquals(height, getHeight())) { 1.203 + return this; 1.204 + } 1.205 + 1.206 + return new ImmutableViewportMetrics( 1.207 + pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, 1.208 + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, 1.209 + viewportRectLeft, viewportRectTop, viewportRectLeft + width, viewportRectTop + height, 1.210 + marginLeft, marginTop, marginRight, marginBottom, 1.211 + zoomFactor, isRTL); 1.212 + } 1.213 + 1.214 + public ImmutableViewportMetrics setViewportOrigin(float newOriginX, float newOriginY) { 1.215 + return new ImmutableViewportMetrics( 1.216 + pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, 1.217 + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, 1.218 + newOriginX, newOriginY, newOriginX + getWidth(), newOriginY + getHeight(), 1.219 + marginLeft, marginTop, marginRight, marginBottom, 1.220 + zoomFactor, isRTL); 1.221 + } 1.222 + 1.223 + public ImmutableViewportMetrics setZoomFactor(float newZoomFactor) { 1.224 + return new ImmutableViewportMetrics( 1.225 + pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, 1.226 + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, 1.227 + viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom, 1.228 + marginLeft, marginTop, marginRight, marginBottom, 1.229 + newZoomFactor, isRTL); 1.230 + } 1.231 + 1.232 + public ImmutableViewportMetrics offsetViewportBy(float dx, float dy) { 1.233 + return setViewportOrigin(viewportRectLeft + dx, viewportRectTop + dy); 1.234 + } 1.235 + 1.236 + public ImmutableViewportMetrics offsetViewportByAndClamp(float dx, float dy) { 1.237 + if (isRTL) { 1.238 + return setViewportOrigin( 1.239 + Math.min(pageRectRight - getWidthWithoutMargins(), Math.max(viewportRectLeft + dx, pageRectLeft)), 1.240 + Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeightWithoutMargins()))); 1.241 + } 1.242 + return setViewportOrigin( 1.243 + Math.max(pageRectLeft, Math.min(viewportRectLeft + dx, pageRectRight - getWidthWithoutMargins())), 1.244 + Math.max(pageRectTop, Math.min(viewportRectTop + dy, pageRectBottom - getHeightWithoutMargins()))); 1.245 + } 1.246 + 1.247 + public ImmutableViewportMetrics setPageRect(RectF pageRect, RectF cssPageRect) { 1.248 + return new ImmutableViewportMetrics( 1.249 + pageRect.left, pageRect.top, pageRect.right, pageRect.bottom, 1.250 + cssPageRect.left, cssPageRect.top, cssPageRect.right, cssPageRect.bottom, 1.251 + viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom, 1.252 + marginLeft, marginTop, marginRight, marginBottom, 1.253 + zoomFactor, isRTL); 1.254 + } 1.255 + 1.256 + public ImmutableViewportMetrics setMargins(float left, float top, float right, float bottom) { 1.257 + if (FloatUtils.fuzzyEquals(left, marginLeft) 1.258 + && FloatUtils.fuzzyEquals(top, marginTop) 1.259 + && FloatUtils.fuzzyEquals(right, marginRight) 1.260 + && FloatUtils.fuzzyEquals(bottom, marginBottom)) { 1.261 + return this; 1.262 + } 1.263 + 1.264 + return new ImmutableViewportMetrics( 1.265 + pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, 1.266 + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, 1.267 + viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom, 1.268 + left, top, right, bottom, zoomFactor, isRTL); 1.269 + } 1.270 + 1.271 + public ImmutableViewportMetrics setMarginsFrom(ImmutableViewportMetrics fromMetrics) { 1.272 + return setMargins(fromMetrics.marginLeft, 1.273 + fromMetrics.marginTop, 1.274 + fromMetrics.marginRight, 1.275 + fromMetrics.marginBottom); 1.276 + } 1.277 + 1.278 + public ImmutableViewportMetrics setIsRTL(boolean aIsRTL) { 1.279 + if (isRTL == aIsRTL) { 1.280 + return this; 1.281 + } 1.282 + 1.283 + return new ImmutableViewportMetrics( 1.284 + pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, 1.285 + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, 1.286 + viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom, 1.287 + marginLeft, marginTop, marginRight, marginBottom, zoomFactor, aIsRTL); 1.288 + } 1.289 + 1.290 + /* This will set the zoom factor and re-scale page-size and viewport offset 1.291 + * accordingly. The given focus will remain at the same point on the screen 1.292 + * after scaling. 1.293 + */ 1.294 + public ImmutableViewportMetrics scaleTo(float newZoomFactor, PointF focus) { 1.295 + // cssPageRect* is invariant, since we're setting the scale factor 1.296 + // here. The page rect is based on the CSS page rect. 1.297 + float newPageRectLeft = cssPageRectLeft * newZoomFactor; 1.298 + float newPageRectTop = cssPageRectTop * newZoomFactor; 1.299 + float newPageRectRight = cssPageRectLeft + ((cssPageRectRight - cssPageRectLeft) * newZoomFactor); 1.300 + float newPageRectBottom = cssPageRectTop + ((cssPageRectBottom - cssPageRectTop) * newZoomFactor); 1.301 + 1.302 + PointF origin = getOrigin(); 1.303 + origin.offset(focus.x, focus.y); 1.304 + origin = PointUtils.scale(origin, newZoomFactor / zoomFactor); 1.305 + origin.offset(-focus.x, -focus.y); 1.306 + 1.307 + return new ImmutableViewportMetrics( 1.308 + newPageRectLeft, newPageRectTop, newPageRectRight, newPageRectBottom, 1.309 + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, 1.310 + origin.x, origin.y, origin.x + getWidth(), origin.y + getHeight(), 1.311 + marginLeft, marginTop, marginRight, marginBottom, 1.312 + newZoomFactor, isRTL); 1.313 + } 1.314 + 1.315 + /** Clamps the viewport to remain within the page rect. */ 1.316 + private ImmutableViewportMetrics clamp(float marginLeft, float marginTop, 1.317 + float marginRight, float marginBottom) { 1.318 + RectF newViewport = getViewport(); 1.319 + PointF offset = getMarginOffset(); 1.320 + 1.321 + // The viewport bounds ought to never exceed the page bounds. 1.322 + if (newViewport.right > pageRectRight + marginLeft + marginRight) 1.323 + newViewport.offset((pageRectRight + marginLeft + marginRight) - newViewport.right, 0); 1.324 + if (newViewport.left < pageRectLeft) 1.325 + newViewport.offset(pageRectLeft - newViewport.left, 0); 1.326 + 1.327 + if (newViewport.bottom > pageRectBottom + marginTop + marginBottom) 1.328 + newViewport.offset(0, (pageRectBottom + marginTop + marginBottom) - newViewport.bottom); 1.329 + if (newViewport.top < pageRectTop) 1.330 + newViewport.offset(0, pageRectTop - newViewport.top); 1.331 + 1.332 + return new ImmutableViewportMetrics( 1.333 + pageRectLeft, pageRectTop, pageRectRight, pageRectBottom, 1.334 + cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom, 1.335 + newViewport.left, newViewport.top, newViewport.right, newViewport.bottom, 1.336 + marginLeft, marginTop, marginRight, marginBottom, 1.337 + zoomFactor, isRTL); 1.338 + } 1.339 + 1.340 + public ImmutableViewportMetrics clamp() { 1.341 + return clamp(0, 0, 0, 0); 1.342 + } 1.343 + 1.344 + public ImmutableViewportMetrics clampWithMargins() { 1.345 + return clamp(marginLeft, marginTop, 1.346 + marginRight, marginBottom); 1.347 + } 1.348 + 1.349 + public boolean fuzzyEquals(ImmutableViewportMetrics other) { 1.350 + // Don't bother checking the pageRectXXX values because they are a product 1.351 + // of the cssPageRectXXX values and the zoomFactor, except with more rounding 1.352 + // error. Checking those is both inefficient and can lead to false negatives. 1.353 + // 1.354 + // This doesn't return false if the margins differ as none of the users 1.355 + // of this function are interested in the margins in that way. 1.356 + return FloatUtils.fuzzyEquals(cssPageRectLeft, other.cssPageRectLeft) 1.357 + && FloatUtils.fuzzyEquals(cssPageRectTop, other.cssPageRectTop) 1.358 + && FloatUtils.fuzzyEquals(cssPageRectRight, other.cssPageRectRight) 1.359 + && FloatUtils.fuzzyEquals(cssPageRectBottom, other.cssPageRectBottom) 1.360 + && FloatUtils.fuzzyEquals(viewportRectLeft, other.viewportRectLeft) 1.361 + && FloatUtils.fuzzyEquals(viewportRectTop, other.viewportRectTop) 1.362 + && FloatUtils.fuzzyEquals(viewportRectRight, other.viewportRectRight) 1.363 + && FloatUtils.fuzzyEquals(viewportRectBottom, other.viewportRectBottom) 1.364 + && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor); 1.365 + } 1.366 + 1.367 + @Override 1.368 + public String toString() { 1.369 + return "ImmutableViewportMetrics v=(" + viewportRectLeft + "," + viewportRectTop + "," 1.370 + + viewportRectRight + "," + viewportRectBottom + ") p=(" + pageRectLeft + "," 1.371 + + pageRectTop + "," + pageRectRight + "," + pageRectBottom + ") c=(" 1.372 + + cssPageRectLeft + "," + cssPageRectTop + "," + cssPageRectRight + "," 1.373 + + cssPageRectBottom + ") m=(" + marginLeft + "," 1.374 + + marginTop + "," + marginRight + "," 1.375 + + marginBottom + ") z=" + zoomFactor + ", rtl=" + isRTL; 1.376 + } 1.377 +}