deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/Axis.java
+++ /dev/null
@@ -1,532 +0,0 @@
-/* -*- 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 java.util.HashMap;
-import java.util.Map;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.util.FloatUtils;
-
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.View;
-
-/**
- * This class represents the physics for one axis of movement (i.e. either
- * horizontal or vertical). It tracks the different properties of movement
- * like displacement, velocity, viewport dimensions, etc. pertaining to
- * a particular axis.
- */
-abstract class Axis {
- private static final String LOGTAG = "GeckoAxis";
-
- private static final String PREF_SCROLLING_FRICTION_SLOW = "ui.scrolling.friction_slow";
- private static final String PREF_SCROLLING_FRICTION_FAST = "ui.scrolling.friction_fast";
- private static final String PREF_SCROLLING_MAX_EVENT_ACCELERATION = "ui.scrolling.max_event_acceleration";
- private static final String PREF_SCROLLING_OVERSCROLL_DECEL_RATE = "ui.scrolling.overscroll_decel_rate";
- private static final String PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT = "ui.scrolling.overscroll_snap_limit";
- private static final String PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE = "ui.scrolling.min_scrollable_distance";
- private static final String PREF_FLING_ACCEL_INTERVAL = "ui.scrolling.fling_accel_interval";
- private static final String PREF_FLING_ACCEL_BASE_MULTIPLIER = "ui.scrolling.fling_accel_base_multiplier";
- private static final String PREF_FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER = "ui.scrolling.fling_accel_supplemental_multiplier";
- private static final String PREF_FLING_CURVE_FUNCTION_X1 = "ui.scrolling.fling_curve_function_x1";
- private static final String PREF_FLING_CURVE_FUNCTION_Y1 = "ui.scrolling.fling_curve_function_y1";
- private static final String PREF_FLING_CURVE_FUNCTION_X2 = "ui.scrolling.fling_curve_function_x2";
- private static final String PREF_FLING_CURVE_FUNCTION_Y2 = "ui.scrolling.fling_curve_function_y2";
- private static final String PREF_FLING_CURVE_THRESHOLD_VELOCITY = "ui.scrolling.fling_curve_threshold_velocity";
- private static final String PREF_FLING_CURVE_MAXIMUM_VELOCITY = "ui.scrolling.fling_curve_max_velocity";
- private static final String PREF_FLING_CURVE_NEWTON_ITERATIONS = "ui.scrolling.fling_curve_newton_iterations";
-
- // This fraction of velocity remains after every animation frame when the velocity is low.
- private static float FRICTION_SLOW;
- // This fraction of velocity remains after every animation frame when the velocity is high.
- private static float FRICTION_FAST;
- // Below this velocity (in pixels per frame), the friction starts increasing from FRICTION_FAST
- // to FRICTION_SLOW.
- private static float VELOCITY_THRESHOLD;
- // The maximum velocity change factor between events, per ms, in %.
- // Direction changes are excluded.
- private static float MAX_EVENT_ACCELERATION;
-
- // The rate of deceleration when the surface has overscrolled.
- private static float OVERSCROLL_DECEL_RATE;
- // The percentage of the surface which can be overscrolled before it must snap back.
- private static float SNAP_LIMIT;
-
- // The minimum amount of space that must be present for an axis to be considered scrollable,
- // in pixels.
- private static float MIN_SCROLLABLE_DISTANCE;
-
- // The interval within which if two flings are done then scrolling effect is accelerated.
- private static long FLING_ACCEL_INTERVAL;
-
- // The multiplication constant of the base velocity in case of accelerated scrolling.
- private static float FLING_ACCEL_BASE_MULTIPLIER;
-
- // The multiplication constant of the supplemental velocity in case of accelerated scrolling.
- private static float FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER;
-
- // x co-ordinate of the second bezier control point
- private static float FLING_CURVE_FUNCTION_X1;
-
- // y co-ordinate of the second bezier control point
- private static float FLING_CURVE_FUNCTION_Y1;
-
- // x co-ordinate of the third bezier control point
- private static float FLING_CURVE_FUNCTION_X2;
-
- // y co-ordinate of the third bezier control point
- private static float FLING_CURVE_FUNCTION_Y2;
-
- // Minimum velocity for curve to be implemented i.e fling curving
- private static float FLING_CURVE_THRESHOLD_VELOCITY;
-
- // Maximum permitted velocity
- private static float FLING_CURVE_MAXIMUM_VELOCITY;
-
- // Number of iterations in the Newton-Raphson method
- private static int FLING_CURVE_NEWTON_ITERATIONS;
-
- private static float getFloatPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
- Integer value = (prefs == null ? null : prefs.get(prefName));
- return (value == null || value < 0 ? defaultValue : value) / 1000f;
- }
-
- private static int getIntPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
- Integer value = (prefs == null ? null : prefs.get(prefName));
- return (value == null || value < 0 ? defaultValue : value);
- }
-
- static void initPrefs() {
- final String[] prefs = { PREF_SCROLLING_FRICTION_FAST,
- PREF_SCROLLING_FRICTION_SLOW,
- PREF_SCROLLING_MAX_EVENT_ACCELERATION,
- PREF_SCROLLING_OVERSCROLL_DECEL_RATE,
- PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT,
- PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE,
- PREF_FLING_ACCEL_INTERVAL,
- PREF_FLING_ACCEL_BASE_MULTIPLIER,
- PREF_FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER,
- PREF_FLING_CURVE_FUNCTION_X1,
- PREF_FLING_CURVE_FUNCTION_Y1,
- PREF_FLING_CURVE_FUNCTION_X2,
- PREF_FLING_CURVE_FUNCTION_Y2,
- PREF_FLING_CURVE_THRESHOLD_VELOCITY,
- PREF_FLING_CURVE_MAXIMUM_VELOCITY,
- PREF_FLING_CURVE_NEWTON_ITERATIONS };
-
- PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
- Map<String, Integer> mPrefs = new HashMap<String, Integer>();
-
- @Override public void prefValue(String name, int value) {
- mPrefs.put(name, value);
- }
-
- @Override public void finish() {
- setPrefs(mPrefs);
- }
- });
- }
-
- static final float MS_PER_FRAME = 1000.0f / 60.0f;
- static final long NS_PER_FRAME = Math.round(1000000000f / 60f);
- private static final float FRAMERATE_MULTIPLIER = (1000f / 60f) / MS_PER_FRAME;
- private static final int FLING_VELOCITY_POINTS = 8;
-
- // The values we use for friction are based on a 16.6ms frame, adjust them to currentNsPerFrame:
- static float getFrameAdjustedFriction(float baseFriction, long currentNsPerFrame) {
- float framerateMultiplier = (float)currentNsPerFrame / NS_PER_FRAME;
- return (float)Math.pow(Math.E, (Math.log(baseFriction) / framerateMultiplier));
- }
-
- static void setPrefs(Map<String, Integer> prefs) {
- FRICTION_SLOW = getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850);
- FRICTION_FAST = getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970);
- VELOCITY_THRESHOLD = 10 / FRAMERATE_MULTIPLIER;
- MAX_EVENT_ACCELERATION = getFloatPref(prefs, PREF_SCROLLING_MAX_EVENT_ACCELERATION, GeckoAppShell.getDpi() > 300 ? 100 : 40);
- OVERSCROLL_DECEL_RATE = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40);
- SNAP_LIMIT = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT, 300);
- MIN_SCROLLABLE_DISTANCE = getFloatPref(prefs, PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE, 500);
- FLING_ACCEL_INTERVAL = getIntPref(prefs, PREF_FLING_ACCEL_INTERVAL, 500);
- FLING_ACCEL_BASE_MULTIPLIER = getFloatPref(prefs, PREF_FLING_ACCEL_BASE_MULTIPLIER, 1000);
- FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER = getFloatPref(prefs, PREF_FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER, 1000);
- FLING_CURVE_FUNCTION_X1 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_X1, 410);
- FLING_CURVE_FUNCTION_Y1 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_Y1, 0);
- FLING_CURVE_FUNCTION_X2 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_X2, 800);
- FLING_CURVE_FUNCTION_Y2 = getFloatPref(prefs, PREF_FLING_CURVE_FUNCTION_Y2, 1000);
- FLING_CURVE_THRESHOLD_VELOCITY = getFloatPref(prefs, PREF_FLING_CURVE_THRESHOLD_VELOCITY, 30);
- FLING_CURVE_MAXIMUM_VELOCITY = getFloatPref(prefs, PREF_FLING_CURVE_MAXIMUM_VELOCITY, 70);
- FLING_CURVE_NEWTON_ITERATIONS = getIntPref(prefs, PREF_FLING_CURVE_NEWTON_ITERATIONS, 5);
-
- Log.i(LOGTAG, "Prefs: " + FRICTION_SLOW + "," + FRICTION_FAST + "," + VELOCITY_THRESHOLD + ","
- + MAX_EVENT_ACCELERATION + "," + OVERSCROLL_DECEL_RATE + "," + SNAP_LIMIT + "," + MIN_SCROLLABLE_DISTANCE);
- }
-
- static {
- // set the scrolling parameters to default values on startup
- setPrefs(null);
- }
-
- private enum FlingStates {
- STOPPED,
- PANNING,
- FLINGING,
- }
-
- private enum Overscroll {
- NONE,
- MINUS, // Overscrolled in the negative direction
- PLUS, // Overscrolled in the positive direction
- BOTH, // Overscrolled in both directions (page is zoomed to smaller than screen)
- }
-
- private final SubdocumentScrollHelper mSubscroller;
-
- private int mOverscrollMode; /* Default to only overscrolling if we're allowed to scroll in a direction */
- private float mFirstTouchPos; /* Position of the first touch event on the current drag. */
- private float mTouchPos; /* Position of the most recent touch event on the current drag. */
- private float mLastTouchPos; /* Position of the touch event before touchPos. */
- private float mVelocity; /* Velocity in this direction; pixels per animation frame. */
- private final float[] mRecentVelocities; /* Circular buffer of recent velocities since last touch start. */
- private int mRecentVelocityCount; /* Number of values put into mRecentVelocities (unbounded). */
- private boolean mScrollingDisabled; /* Whether movement on this axis is locked. */
- private boolean mDisableSnap; /* Whether overscroll snapping is disabled. */
- private float mDisplacement;
- private long mLastFlingTime;
- private float mLastFlingVelocity;
-
- private FlingStates mFlingState = FlingStates.STOPPED; /* The fling state we're in on this axis. */
-
- protected abstract float getOrigin();
- protected abstract float getViewportLength();
- protected abstract float getPageStart();
- protected abstract float getPageLength();
- protected abstract float getVisibleEndOfLayerView();
-
- Axis(SubdocumentScrollHelper subscroller) {
- mSubscroller = subscroller;
- mOverscrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
- mRecentVelocities = new float[FLING_VELOCITY_POINTS];
- }
-
- // Implementors can override these to show effects when the axis overscrolls
- protected void overscrollFling(float velocity) { }
- protected void overscrollPan(float displacement) { }
-
- public void setOverScrollMode(int overscrollMode) {
- mOverscrollMode = overscrollMode;
- }
-
- public int getOverScrollMode() {
- return mOverscrollMode;
- }
-
- private float getViewportEnd() {
- return getOrigin() + getViewportLength();
- }
-
- private float getPageEnd() {
- return getPageStart() + getPageLength();
- }
-
- void startTouch(float pos) {
- mVelocity = 0.0f;
- mScrollingDisabled = false;
- mFirstTouchPos = mTouchPos = mLastTouchPos = pos;
- mRecentVelocityCount = 0;
- }
-
- float panDistance(float currentPos) {
- return currentPos - mFirstTouchPos;
- }
-
- void setScrollingDisabled(boolean disabled) {
- mScrollingDisabled = disabled;
- }
-
- void saveTouchPos() {
- mLastTouchPos = mTouchPos;
- }
-
- // Calculates and return the slope of the curve at given parameter t
- float getSlope(float t) {
- float y1 = FLING_CURVE_FUNCTION_Y1;
- float y2 = FLING_CURVE_FUNCTION_Y2;
-
- return (3 * y1)
- + t * (6 * y2 - 12 * y1)
- + t * t * (9 * y1 - 9 * y2 + 3);
- }
-
- // Calculates and returns the value of the bezier curve with the given parameter t and control points p1 and p2
- float cubicBezier(float p1, float p2, float t) {
- return (3 * t * (1 - t) * (1 - t) * p1)
- + (3 * t * t * (1 - t) * p2)
- + (t * t * t);
- }
-
- // Responsible for mapping the physical velocity to a the velocity obtained after applying bezier curve (with control points (X1,Y1) and (X2,Y2))
- float flingCurve(float By) {
- int ni = FLING_CURVE_NEWTON_ITERATIONS;
- float[] guess = new float[ni];
- float y1 = FLING_CURVE_FUNCTION_Y1;
- float y2 = FLING_CURVE_FUNCTION_Y2;
- guess[0] = By;
-
- for (int i = 1; i < ni; i++) {
- guess[i] = guess[i - 1] - (cubicBezier(y1, y2, guess[i - 1]) - By) / getSlope(guess[i - 1]);
- }
- // guess[4] is the final approximate root the cubic equation.
- float t = guess[4];
-
- float x1 = FLING_CURVE_FUNCTION_X1;
- float x2 = FLING_CURVE_FUNCTION_X2;
- return cubicBezier(x1, x2, t);
- }
-
- void updateWithTouchAt(float pos, float timeDelta) {
- float curveVelocityThreshold = FLING_CURVE_THRESHOLD_VELOCITY * GeckoAppShell.getDpi() * MS_PER_FRAME;
- float maxVelocity = FLING_CURVE_MAXIMUM_VELOCITY * GeckoAppShell.getDpi() * MS_PER_FRAME;
-
- float newVelocity = (mTouchPos - pos) / timeDelta * MS_PER_FRAME;
-
- if (Math.abs(newVelocity) > curveVelocityThreshold && Math.abs(newVelocity) < maxVelocity) {
- float sign = Math.signum(newVelocity);
- newVelocity = newVelocity * sign;
- float scale = maxVelocity - curveVelocityThreshold;
- float functInp = (newVelocity - curveVelocityThreshold) / scale;
- float functOut = flingCurve(functInp);
- newVelocity = functOut * scale + curveVelocityThreshold;
- newVelocity = newVelocity * sign;
- }
-
- mRecentVelocities[mRecentVelocityCount % FLING_VELOCITY_POINTS] = newVelocity;
- mRecentVelocityCount++;
-
- // If there's a direction change, or current velocity is very low,
- // allow setting of the velocity outright. Otherwise, use the current
- // velocity and a maximum change factor to set the new velocity.
- boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f / FRAMERATE_MULTIPLIER;
- boolean directionChange = (mVelocity > 0) != (newVelocity > 0);
- if (curVelocityIsLow || (directionChange && !FloatUtils.fuzzyEquals(newVelocity, 0.0f))) {
- mVelocity = newVelocity;
- } else {
- float maxChange = Math.abs(mVelocity * timeDelta * MAX_EVENT_ACCELERATION);
- mVelocity = Math.min(mVelocity + maxChange, Math.max(mVelocity - maxChange, newVelocity));
- }
-
- mTouchPos = pos;
- }
-
- boolean overscrolled() {
- return getOverscroll() != Overscroll.NONE;
- }
-
- private Overscroll getOverscroll() {
- boolean minus = (getOrigin() < getPageStart());
- boolean plus = (getViewportEnd() > getPageEnd());
- if (minus && plus) {
- return Overscroll.BOTH;
- } else if (minus) {
- return Overscroll.MINUS;
- } else if (plus) {
- return Overscroll.PLUS;
- } else {
- return Overscroll.NONE;
- }
- }
-
- // Returns the amount that the page has been overscrolled. If the page hasn't been
- // overscrolled on this axis, returns 0.
- private float getExcess() {
- switch (getOverscroll()) {
- case MINUS: return getPageStart() - getOrigin();
- case PLUS: return getViewportEnd() - getPageEnd();
- case BOTH: return (getViewportEnd() - getPageEnd()) + (getPageStart() - getOrigin());
- default: return 0.0f;
- }
- }
-
- /*
- * Returns true if the page is zoomed in to some degree along this axis such that scrolling is
- * possible and this axis has not been scroll locked while panning. Otherwise, returns false.
- */
- boolean scrollable() {
- // If we're scrolling a subdocument, ignore the viewport length restrictions (since those
- // apply to the top-level document) and only take into account axis locking.
- if (mSubscroller.scrolling()) {
- return !mScrollingDisabled;
- }
-
- // if we are axis locked, return false
- if (mScrollingDisabled) {
- return false;
- }
-
- // there is scrollable space, and we're not disabled, or the document fits the viewport
- // but we always allow overscroll anyway
- return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE ||
- getOverScrollMode() == View.OVER_SCROLL_ALWAYS;
- }
-
- /*
- * Returns the resistance, as a multiplier, that should be taken into account when
- * tracking or pinching.
- */
- float getEdgeResistance(boolean forPinching) {
- float excess = getExcess();
- if (excess > 0.0f && (getOverscroll() == Overscroll.BOTH || !forPinching)) {
- // excess can be greater than viewport length, but the resistance
- // must never drop below 0.0
- return Math.max(0.0f, SNAP_LIMIT - excess / getViewportLength());
- }
- return 1.0f;
- }
-
- /* Returns the velocity. If the axis is locked, returns 0. */
- float getRealVelocity() {
- return scrollable() ? mVelocity : 0f;
- }
-
- void startPan() {
- mFlingState = FlingStates.PANNING;
- }
-
- private float calculateFlingVelocity() {
- int usablePoints = Math.min(mRecentVelocityCount, FLING_VELOCITY_POINTS);
- if (usablePoints <= 1) {
- return mVelocity;
- }
- float average = 0;
- for (int i = 0; i < usablePoints; i++) {
- average += mRecentVelocities[i];
- }
- return average / usablePoints;
- }
-
- float accelerate(float velocity, float lastFlingVelocity) {
- return (FLING_ACCEL_BASE_MULTIPLIER * velocity + FLING_ACCEL_SUPPLEMENTAL_MULTIPLIER * lastFlingVelocity);
- }
-
- void startFling(boolean stopped) {
- mDisableSnap = mSubscroller.scrolling();
-
- if (stopped) {
- mFlingState = FlingStates.STOPPED;
- } else {
- long now = SystemClock.uptimeMillis();
- mVelocity = calculateFlingVelocity();
-
- if ((now - mLastFlingTime < FLING_ACCEL_INTERVAL) && Math.signum(mVelocity) == Math.signum(mLastFlingVelocity)) {
- mVelocity = accelerate(mVelocity, mLastFlingVelocity);
- }
- mFlingState = FlingStates.FLINGING;
- mLastFlingVelocity = mVelocity;
- mLastFlingTime = now;
- }
- }
-
- /* Advances a fling animation by one step. */
- boolean advanceFling(long realNsPerFrame) {
- if (mFlingState != FlingStates.FLINGING) {
- return false;
- }
- if (mSubscroller.scrolling() && !mSubscroller.lastScrollSucceeded()) {
- // if the subdocument stopped scrolling, it's because it reached the end
- // of the subdocument. we don't do overscroll on subdocuments, so there's
- // no point in continuing this fling.
- return false;
- }
-
- float excess = getExcess();
- Overscroll overscroll = getOverscroll();
- boolean decreasingOverscroll = false;
- if ((overscroll == Overscroll.MINUS && mVelocity > 0) ||
- (overscroll == Overscroll.PLUS && mVelocity < 0))
- {
- decreasingOverscroll = true;
- }
-
- if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f) || decreasingOverscroll) {
- // If we aren't overscrolled, just apply friction.
- if (Math.abs(mVelocity) >= VELOCITY_THRESHOLD) {
- mVelocity *= getFrameAdjustedFriction(FRICTION_FAST, realNsPerFrame);
- } else {
- float t = mVelocity / VELOCITY_THRESHOLD;
- mVelocity *= FloatUtils.interpolate(getFrameAdjustedFriction(FRICTION_SLOW, realNsPerFrame),
- getFrameAdjustedFriction(FRICTION_FAST, realNsPerFrame), t);
- }
- } else {
- // Otherwise, decrease the velocity linearly.
- float elasticity = 1.0f - excess / (getViewportLength() * SNAP_LIMIT);
- float overscrollDecelRate = getFrameAdjustedFriction(OVERSCROLL_DECEL_RATE, realNsPerFrame);
- if (overscroll == Overscroll.MINUS) {
- mVelocity = Math.min((mVelocity + overscrollDecelRate) * elasticity, 0.0f);
- } else { // must be Overscroll.PLUS
- mVelocity = Math.max((mVelocity - overscrollDecelRate) * elasticity, 0.0f);
- }
- }
-
- return true;
- }
-
- void stopFling() {
- mVelocity = 0.0f;
- mFlingState = FlingStates.STOPPED;
- }
-
- // Performs displacement of the viewport position according to the current velocity.
- void displace() {
- // if this isn't scrollable just return
- if (!scrollable())
- return;
-
- if (mFlingState == FlingStates.PANNING)
- mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance(false);
- else
- mDisplacement += mVelocity * getEdgeResistance(false);
-
- // if overscroll is disabled and we're trying to overscroll, reset the displacement
- // to remove any excess. Using getExcess alone isn't enough here since it relies on
- // getOverscroll which doesn't take into account any new displacment being applied.
- // If we using a subscroller, we don't want to alter the scrolling being done
- if (getOverScrollMode() == View.OVER_SCROLL_NEVER && !mSubscroller.scrolling()) {
- float originalDisplacement = mDisplacement;
-
- if (mDisplacement + getOrigin() < getPageStart()) {
- mDisplacement = getPageStart() - getOrigin();
- } else if (mDisplacement + getOrigin() + getVisibleEndOfLayerView() > getPageEnd()) {
- mDisplacement = getPageEnd() - getOrigin() - getVisibleEndOfLayerView();
- }
-
- // Return the amount of overscroll so that the overscroll controller can draw it for us
- if (originalDisplacement != mDisplacement) {
- if (mFlingState == FlingStates.FLINGING) {
- overscrollFling(mVelocity / MS_PER_FRAME * 1000);
- stopFling();
- } else if (mFlingState == FlingStates.PANNING) {
- overscrollPan(originalDisplacement - mDisplacement);
- }
- }
- }
- }
-
- float resetDisplacement() {
- float d = mDisplacement;
- mDisplacement = 0.0f;
- return d;
- }
-
- void setAutoscrollVelocity(float velocity) {
- if (mFlingState != FlingStates.STOPPED) {
- Log.e(LOGTAG, "Setting autoscroll velocity while in a fling is not allowed!");
- return;
- }
- mVelocity = velocity;
- }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/BufferedImage.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/* -*- 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.mozilla.gecko.mozglue.DirectBufferAllocator;
-
-import android.graphics.Bitmap;
-import android.util.Log;
-
-import java.nio.ByteBuffer;
-
-/** A buffered image that simply saves a buffer of pixel data. */
-public class BufferedImage {
- private ByteBuffer mBuffer;
- private Bitmap mBitmap;
- private IntSize mSize;
- private int mFormat;
-
- private static final String LOGTAG = "GeckoBufferedImage";
-
- /** Creates an empty buffered image */
- public BufferedImage() {
- mSize = new IntSize(0, 0);
- }
-
- /** Creates a buffered image from an Android bitmap. */
- public BufferedImage(Bitmap bitmap) {
- mFormat = bitmapConfigToFormat(bitmap.getConfig());
- mSize = new IntSize(bitmap.getWidth(), bitmap.getHeight());
- mBitmap = bitmap;
- }
-
- private synchronized void freeBuffer() {
- if (mBuffer != null) {
- mBuffer = DirectBufferAllocator.free(mBuffer);
- }
- }
-
- public void destroy() {
- try {
- freeBuffer();
- } catch (Exception ex) {
- Log.e(LOGTAG, "error clearing buffer: ", ex);
- }
- }
-
- public ByteBuffer getBuffer() {
- if (mBuffer == null) {
- int bpp = bitsPerPixelForFormat(mFormat);
- mBuffer = DirectBufferAllocator.allocate(mSize.getArea() * bpp);
- mBitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
- mBitmap = null;
- }
- return mBuffer;
- }
-
- public IntSize getSize() { return mSize; }
- public int getFormat() { return mFormat; }
-
- public static final int FORMAT_INVALID = -1;
- public static final int FORMAT_ARGB32 = 0;
- public static final int FORMAT_RGB24 = 1;
- public static final int FORMAT_A8 = 2;
- public static final int FORMAT_A1 = 3;
- public static final int FORMAT_RGB16_565 = 4;
-
- private static int bitsPerPixelForFormat(int format) {
- switch (format) {
- case FORMAT_A1: return 1;
- case FORMAT_A8: return 8;
- case FORMAT_RGB16_565: return 16;
- case FORMAT_RGB24: return 24;
- case FORMAT_ARGB32: return 32;
- default:
- throw new RuntimeException("Unknown Cairo format");
- }
- }
-
- private static int bitmapConfigToFormat(Bitmap.Config config) {
- if (config == null)
- return FORMAT_ARGB32; /* Droid Pro fix. */
-
- switch (config) {
- case ALPHA_8: return FORMAT_A8;
- case ARGB_4444: throw new RuntimeException("ARGB_444 unsupported");
- case ARGB_8888: return FORMAT_ARGB32;
- case RGB_565: return FORMAT_RGB16_565;
- default: throw new RuntimeException("Unknown Skia bitmap config");
- }
- }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/BufferedImageGLInfo.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/* -*- 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 javax.microedition.khronos.opengles.GL10;
-
-/** Information needed to render buffered bitmaps using OpenGL ES. */
-public class BufferedImageGLInfo {
- public final int internalFormat;
- public final int format;
- public final int type;
-
- public BufferedImageGLInfo(int bufferedImageFormat) {
- switch (bufferedImageFormat) {
- case BufferedImage.FORMAT_ARGB32:
- internalFormat = format = GL10.GL_RGBA; type = GL10.GL_UNSIGNED_BYTE;
- break;
- case BufferedImage.FORMAT_RGB24:
- internalFormat = format = GL10.GL_RGB; type = GL10.GL_UNSIGNED_BYTE;
- break;
- case BufferedImage.FORMAT_RGB16_565:
- internalFormat = format = GL10.GL_RGB; type = GL10.GL_UNSIGNED_SHORT_5_6_5;
- break;
- case BufferedImage.FORMAT_A8:
- case BufferedImage.FORMAT_A1:
- throw new RuntimeException("BufferedImage FORMAT_A1 and FORMAT_A8 unsupported");
- default:
- throw new RuntimeException("Unknown BufferedImage format");
- }
- }
-}
-
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/JavaPanZoomController.java
+++ /dev/null
@@ -1,1473 +0,0 @@
-/* -*- 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.json.JSONObject;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoEvent;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.ZoomConstraints;
-import org.mozilla.gecko.util.FloatUtils;
-import org.mozilla.gecko.util.GamepadUtils;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.GestureDetector;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-
-/*
- * Handles the kinetic scrolling and zooming physics for a layer controller.
- *
- * Many ideas are from Joe Hewitt's Scrollability:
- * https://github.com/joehewitt/scrollability/
- */
-class JavaPanZoomController
- extends GestureDetector.SimpleOnGestureListener
- implements PanZoomController, SimpleScaleGestureDetector.SimpleScaleGestureListener, GeckoEventListener
-{
- private static final String LOGTAG = "GeckoPanZoomController";
-
- private static final String MESSAGE_ZOOM_RECT = "Browser:ZoomToRect";
- private static final String MESSAGE_ZOOM_PAGE = "Browser:ZoomToPageWidth";
- private static final String MESSAGE_TOUCH_LISTENER = "Tab:HasTouchListener";
-
- // 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;
-
- // Angle from axis within which we stay axis-locked
- private static final double AXIS_LOCK_ANGLE = Math.PI / 6.0; // 30 degrees
-
- // Axis-lock breakout angle
- private static final double AXIS_BREAKOUT_ANGLE = Math.PI / 8.0;
-
- // The distance the user has to pan before we consider breaking out of a locked axis
- public static final float AXIS_BREAKOUT_THRESHOLD = 1 / 32f * GeckoAppShell.getDpi();
-
- // The maximum amount we allow you to zoom into a page
- private static final float MAX_ZOOM = 4.0f;
-
- // The maximum amount we would like to scroll with the mouse
- private static final float MAX_SCROLL = 0.075f * GeckoAppShell.getDpi();
-
- // The maximum zoom factor adjustment per frame of the AUTONAV animation
- private static final float MAX_ZOOM_DELTA = 0.125f;
-
- // The duration of the bounce animation in ns
- private static final int BOUNCE_ANIMATION_DURATION = 250000000;
-
- 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_X, /* touch-start followed by move (i.e. panning with axis lock) X axis */
- PANNING_LOCKED_Y, /* as above for Y axis */
- PANNING, /* panning without axis lock */
- PANNING_HOLD, /* in panning, but not moving.
- * similar to TOUCHING but after starting a pan */
- PANNING_HOLD_LOCKED_X, /* like PANNING_HOLD, but axis lock still in effect for X axis */
- PANNING_HOLD_LOCKED_Y, /* as above but for Y axis */
- PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */
- ANIMATED_ZOOM, /* animated zoom to a new rect */
- BOUNCE, /* in a bounce animation */
- WAITING_LISTENERS, /* a state halfway between NOTHING and TOUCHING - the user has
- put a finger down, but we don't yet know if a touch listener has
- prevented the default actions yet. we still need to abort animations. */
- AUTONAV, /* We are scrolling using an AutonavRunnable animation. This is similar
- to the FLING state except that it must be stopped manually by the code that
- started it, and it's velocity can be updated while it's running. */
- }
-
- private enum AxisLockMode {
- STANDARD, /* Default axis locking mode that doesn't break out until finger release */
- FREE, /* No locking at all */
- STICKY /* Break out with hysteresis so that it feels as free as possible whilst locking */
- }
-
- private final PanZoomTarget mTarget;
- private final SubdocumentScrollHelper mSubscroller;
- private final Axis mX;
- private final Axis mY;
- private final TouchEventHandler mTouchEventHandler;
- private final EventDispatcher mEventDispatcher;
-
- /* The task that handles flings, autonav or bounces. */
- private PanZoomRenderTask mAnimationRenderTask;
- /* The zoom focus at the first zoom event (in page coordinates). */
- private PointF mLastZoomFocus;
- /* The time the last motion event took place. */
- private long mLastEventTime;
- /* Current state the pan/zoom UI is in. */
- private PanZoomState mState;
- /* The per-frame zoom delta for the currently-running AUTONAV animation. */
- private float mAutonavZoomDelta;
- /* The user selected panning mode */
- private AxisLockMode mMode;
- /* Whether or not to wait for a double-tap before dispatching a single-tap */
- private boolean mWaitForDoubleTap;
- /* Used to change the scroll direction */
- private boolean mNegateWheelScroll;
- /* Whether the current event has been default-prevented. */
- private boolean mDefaultPrevented;
- /* Whether longpress events are enabled, or suppressed by robocop tests. */
- private boolean isLongpressEnabled;
- /* Whether longpress detection should be ignored */
- private boolean mIgnoreLongPress;
- /* Pointer scrolling delta, scaled by the preferred list item height which matches Android platform behavior */
- private float mPointerScrollFactor;
-
- // Handler to be notified when overscroll occurs
- private Overscroll mOverscroll;
-
- private final PrefsHelper.PrefHandler mPrefsObserver;
-
- public JavaPanZoomController(PanZoomTarget target, View view, EventDispatcher eventDispatcher) {
- mTarget = target;
- mSubscroller = new SubdocumentScrollHelper(eventDispatcher);
- mX = new AxisX(mSubscroller);
- mY = new AxisY(mSubscroller);
- mTouchEventHandler = new TouchEventHandler(view.getContext(), view, this);
- isLongpressEnabled = true;
-
- checkMainThread();
-
- setState(PanZoomState.NOTHING);
-
- mEventDispatcher = eventDispatcher;
- mEventDispatcher.registerGeckoThreadListener(this,
- MESSAGE_ZOOM_RECT,
- MESSAGE_ZOOM_PAGE,
- MESSAGE_TOUCH_LISTENER);
-
- mMode = AxisLockMode.STANDARD;
-
- String[] prefs = { "ui.scrolling.axis_lock_mode",
- "ui.scrolling.negate_wheel_scroll",
- "ui.scrolling.gamepad_dead_zone" };
- mPrefsObserver = new PrefsHelper.PrefHandlerBase() {
- @Override public void prefValue(String pref, String value) {
- if (pref.equals("ui.scrolling.axis_lock_mode")) {
- if (value.equals("standard")) {
- mMode = AxisLockMode.STANDARD;
- } else if (value.equals("free")) {
- mMode = AxisLockMode.FREE;
- } else {
- mMode = AxisLockMode.STICKY;
- }
- }
- }
-
- @Override public void prefValue(String pref, int value) {
- if (pref.equals("ui.scrolling.gamepad_dead_zone")) {
- GamepadUtils.overrideDeadZoneThreshold(value / 1000f);
- }
- }
-
- @Override public void prefValue(String pref, boolean value) {
- if (pref.equals("ui.scrolling.negate_wheel_scroll")) {
- mNegateWheelScroll = value;
- }
- }
- };
- PrefsHelper.addObserver(prefs, mPrefsObserver);
-
- Axis.initPrefs();
-
- TypedValue outValue = new TypedValue();
- if (view.getContext().getTheme().resolveAttribute(android.R.attr.listPreferredItemHeight, outValue, true)) {
- mPointerScrollFactor = outValue.getDimension(view.getContext().getResources().getDisplayMetrics());
- } else {
- mPointerScrollFactor = MAX_SCROLL;
- }
- }
-
- @Override
- public void destroy() {
- PrefsHelper.removeObserver(mPrefsObserver);
- mEventDispatcher.unregisterGeckoThreadListener(this,
- MESSAGE_ZOOM_RECT,
- MESSAGE_ZOOM_PAGE,
- MESSAGE_TOUCH_LISTENER);
- mSubscroller.destroy();
- mTouchEventHandler.destroy();
- }
-
- private final static float easeOut(float t) {
- // ease-out approx.
- // -(t-1)^2+1
- t = t - 1;
- return -t * t + 1;
- }
-
- private void setState(PanZoomState state) {
- if (state != mState) {
- GeckoAppShell.notifyObservers("PanZoom:StateChange", state.toString());
- mState = state;
-
- // Let the target know we've finished with it (for now)
- if (state == PanZoomState.NOTHING) {
- mTarget.panZoomStopped();
- }
- }
- }
-
- private ImmutableViewportMetrics getMetrics() {
- return mTarget.getViewportMetrics();
- }
-
- private void checkMainThread() {
- if (!ThreadUtils.isOnUiThread()) {
- // log with full stack trace
- Log.e(LOGTAG, "Uh-oh, we're running on the wrong thread!", new Exception());
- }
- }
-
- @Override
- 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"));
- if (message.optBoolean("animate", true)) {
- mTarget.post(new Runnable() {
- @Override
- public void run() {
- animatedZoomTo(zoomRect);
- }
- });
- } else {
- mTarget.setViewportMetrics(getMetricsToZoomTo(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);
- if (message.optBoolean("animate", true)) {
- mTarget.post(new Runnable() {
- @Override
- public void run() {
- animatedZoomTo(r);
- }
- });
- } else {
- mTarget.setViewportMetrics(getMetricsToZoomTo(r));
- }
- } else if (MESSAGE_TOUCH_LISTENER.equals(event)) {
- int tabId = message.getInt("tabID");
- final Tab tab = Tabs.getInstance().getTab(tabId);
- // Make sure we still have a Tab
- if (tab == null) {
- return;
- }
-
- tab.setHasTouchListeners(true);
- mTarget.post(new Runnable() {
- @Override
- public void run() {
- if (Tabs.getInstance().isSelectedTab(tab))
- mTouchEventHandler.setWaitForTouchListeners(true);
- }
- });
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
- }
- }
-
- /** This function MUST be called on the UI thread */
- @Override
- public boolean onKeyEvent(KeyEvent event) {
- if ((event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
- && event.getAction() == KeyEvent.ACTION_DOWN) {
-
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_ZOOM_IN:
- return animatedScale(0.2f);
- case KeyEvent.KEYCODE_ZOOM_OUT:
- return animatedScale(-0.2f);
- }
- }
- return false;
- }
-
- // Ignore MontionEvent velocity. Needed for C++APZ
- public void onMotionEventVelocity(final long aEventTime, final float aSpeedY) {}
-
- /** This function MUST be called on the UI thread */
- @Override
- public boolean onMotionEvent(MotionEvent event) {
- switch (event.getSource() & InputDevice.SOURCE_CLASS_MASK) {
- case InputDevice.SOURCE_CLASS_POINTER:
- switch (event.getAction() & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_SCROLL: return handlePointerScroll(event);
- }
- break;
- case InputDevice.SOURCE_CLASS_JOYSTICK:
- switch (event.getAction() & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_MOVE: return handleJoystickNav(event);
- }
- break;
- }
- return false;
- }
-
- /** This function MUST be called on the UI thread */
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- return mTouchEventHandler.handleEvent(event);
- }
-
- boolean handleEvent(MotionEvent event, boolean defaultPrevented) {
- mDefaultPrevented = defaultPrevented;
-
- switch (event.getAction() & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: return handleTouchStart(event);
- case MotionEvent.ACTION_MOVE: return handleTouchMove(event);
- case MotionEvent.ACTION_UP: return handleTouchEnd(event);
- case MotionEvent.ACTION_CANCEL: return handleTouchCancel(event);
- }
- return false;
- }
-
- /** This function MUST be called on the UI thread */
- @Override
- public void notifyDefaultActionPrevented(boolean prevented) {
- mTouchEventHandler.handleEventListenerAction(!prevented);
- }
-
- /** This function must be called from the UI thread. */
- @Override
- 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();
- // fall through
- case BOUNCE:
- 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
- setState(PanZoomState.NOTHING);
- // fall through
- case NOTHING:
- // Don't do animations here; they're distracting and can cause flashes on page
- // transitions.
- synchronized (mTarget.getLock()) {
- mTarget.setViewportMetrics(getValidViewportMetrics());
- mTarget.forceRedraw(null);
- }
- break;
- }
- }
-
- /** This function must be called on the UI thread. */
- public void startingNewEventBlock(MotionEvent event, boolean waitingForTouchListeners) {
- checkMainThread();
- mSubscroller.cancel();
- mIgnoreLongPress = false;
- if (waitingForTouchListeners) {
- if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
- // this is the first touch point going down, so we enter the pending state
- // setting the state will kill any animations in progress, possibly leaving
- // the page in overscroll
- setState(PanZoomState.WAITING_LISTENERS);
- } else if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
- // this is a second (or more) touch point going down, and we're waiting for
- // the content listeners to respond. while we're waiting though we might end
- // up triggering a long-press from the first touch point, which would be bad
- // because from the user's point of view they are already in a multi-touch
- // gesture. to prevent this from happening we set a flag that discards long-press
- // gesture detections.
- mIgnoreLongPress = true;
- }
- }
- }
-
- /** This must be called on the UI thread. */
- @Override
- public void pageRectUpdated() {
- if (mState == PanZoomState.NOTHING) {
- synchronized (mTarget.getLock()) {
- ImmutableViewportMetrics validated = getValidViewportMetrics();
- if (!getMetrics().fuzzyEquals(validated)) {
- // page size changed such that we are now in overscroll. snap to the
- // the nearest valid viewport
- mTarget.setViewportMetrics(validated);
- }
- }
- }
- }
-
- /*
- * Panning/scrolling
- */
-
- private boolean handleTouchStart(MotionEvent event) {
- // user is taking control of movement, so stop
- // any auto-movement we have going
- stopAnimationTask();
-
- switch (mState) {
- case ANIMATED_ZOOM:
- // We just interrupted a double-tap animation, so force a redraw in
- // case this touchstart is just a tap that doesn't end up triggering
- // a redraw
- mTarget.forceRedraw(null);
- // fall through
- case FLING:
- case AUTONAV:
- case BOUNCE:
- case NOTHING:
- case WAITING_LISTENERS:
- startTouch(event.getX(0), event.getY(0), event.getEventTime());
- return false;
- case TOUCHING:
- case PANNING:
- case PANNING_LOCKED_X:
- case PANNING_LOCKED_Y:
- case PANNING_HOLD:
- case PANNING_HOLD_LOCKED_X:
- case PANNING_HOLD_LOCKED_Y:
- case PINCHING:
- Log.e(LOGTAG, "Received impossible touch down while in " + mState);
- return false;
- }
- Log.e(LOGTAG, "Unhandled case " + mState + " in handleTouchStart");
- return false;
- }
-
- private boolean handleTouchMove(MotionEvent event) {
-
- switch (mState) {
- case FLING:
- case AUTONAV:
- case BOUNCE:
- case WAITING_LISTENERS:
- // should never happen
- Log.e(LOGTAG, "Received impossible touch move while in " + mState);
- // fall through
- case ANIMATED_ZOOM:
- case NOTHING:
- // may happen if user double-taps and drags without lifting after the
- // second tap. ignore the move if this happens.
- return false;
-
- case TOUCHING:
- // Don't allow panning if there is a non-root element in full-screen mode. See bug 775511 and bug 859683.
- if (mTarget.getFullScreenState() == FullScreenState.NON_ROOT_ELEMENT && !mSubscroller.scrolling()) {
- return false;
- }
- if (panDistance(event) < PanZoomController.PAN_THRESHOLD) {
- return false;
- }
- cancelTouch();
- startPanning(event.getX(0), event.getY(0), event.getEventTime());
- track(event);
- return true;
-
- case PANNING_HOLD_LOCKED_X:
- setState(PanZoomState.PANNING_LOCKED_X);
- track(event);
- return true;
- case PANNING_HOLD_LOCKED_Y:
- setState(PanZoomState.PANNING_LOCKED_Y);
- // fall through
- case PANNING_LOCKED_X:
- case PANNING_LOCKED_Y:
- track(event);
- return true;
-
- case PANNING_HOLD:
- setState(PanZoomState.PANNING);
- // fall through
- case PANNING:
- track(event);
- return true;
-
- case PINCHING:
- // scale gesture listener will handle this
- return false;
- }
- Log.e(LOGTAG, "Unhandled case " + mState + " in handleTouchMove");
- return false;
- }
-
- private boolean handleTouchEnd(MotionEvent event) {
-
- switch (mState) {
- case FLING:
- case AUTONAV:
- case BOUNCE:
- case ANIMATED_ZOOM:
- case NOTHING:
- // may happen if user double-taps and drags without lifting after the
- // second tap. ignore if this happens.
- return false;
-
- case WAITING_LISTENERS:
- if (!mDefaultPrevented) {
- // should never happen
- Log.e(LOGTAG, "Received impossible touch end while in " + mState);
- }
- // fall through
- case TOUCHING:
- // 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_X:
- case PANNING_LOCKED_Y:
- case PANNING_HOLD:
- case PANNING_HOLD_LOCKED_X:
- case PANNING_HOLD_LOCKED_Y:
- setState(PanZoomState.FLING);
- fling();
- return true;
-
- case PINCHING:
- setState(PanZoomState.NOTHING);
- return true;
- }
- Log.e(LOGTAG, "Unhandled case " + mState + " in handleTouchEnd");
- return false;
- }
-
- private boolean handleTouchCancel(MotionEvent event) {
- cancelTouch();
-
- // ensure we snap back if we're overscrolled
- bounce();
- return false;
- }
-
- private boolean handlePointerScroll(MotionEvent event) {
- if (mState == PanZoomState.NOTHING || mState == PanZoomState.FLING) {
- float scrollX = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
- float scrollY = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
- if (mNegateWheelScroll) {
- scrollX *= -1.0;
- scrollY *= -1.0;
- }
- scrollBy(scrollX * mPointerScrollFactor, scrollY * mPointerScrollFactor);
- bounce();
- return true;
- }
- return false;
- }
-
- private float filterDeadZone(MotionEvent event, int axis) {
- return (GamepadUtils.isValueInDeadZone(event, axis) ? 0 : event.getAxisValue(axis));
- }
-
- private float normalizeJoystickScroll(MotionEvent event, int axis) {
- return filterDeadZone(event, axis) * MAX_SCROLL;
- }
-
- private float normalizeJoystickZoom(MotionEvent event, int axis) {
- // negate MAX_ZOOM_DELTA so that pushing up on the stick zooms in
- return filterDeadZone(event, axis) * -MAX_ZOOM_DELTA;
- }
-
- // Since this event is a position-based event rather than a motion-based event, we need to
- // set up an AUTONAV animation to keep scrolling even while we don't get events.
- private boolean handleJoystickNav(MotionEvent event) {
- float velocityX = normalizeJoystickScroll(event, MotionEvent.AXIS_X);
- float velocityY = normalizeJoystickScroll(event, MotionEvent.AXIS_Y);
- float zoomDelta = normalizeJoystickZoom(event, MotionEvent.AXIS_RZ);
-
- if (velocityX == 0 && velocityY == 0 && zoomDelta == 0) {
- if (mState == PanZoomState.AUTONAV) {
- bounce(); // if not needed, this will automatically go to state NOTHING
- return true;
- }
- return false;
- }
-
- if (mState == PanZoomState.NOTHING) {
- setState(PanZoomState.AUTONAV);
- startAnimationRenderTask(new AutonavRenderTask());
- }
- if (mState == PanZoomState.AUTONAV) {
- mX.setAutoscrollVelocity(velocityX);
- mY.setAutoscrollVelocity(velocityY);
- mAutonavZoomDelta = zoomDelta;
- return true;
- }
- return false;
- }
-
- private void startTouch(float x, float y, long time) {
- mX.startTouch(x);
- mY.startTouch(y);
- setState(PanZoomState.TOUCHING);
- mLastEventTime = time;
- }
-
- private void startPanning(float x, float y, long time) {
- float dx = mX.panDistance(x);
- float dy = mY.panDistance(y);
- double angle = Math.atan2(dy, dx); // range [-pi, pi]
- angle = Math.abs(angle); // range [0, pi]
-
- // When the touch move breaks through the pan threshold, reposition the touch down origin
- // so the page won't jump when we start panning.
- mX.startTouch(x);
- mY.startTouch(y);
- mLastEventTime = time;
-
- if (mMode == AxisLockMode.STANDARD || mMode == AxisLockMode.STICKY) {
- if (!mX.scrollable() || !mY.scrollable()) {
- setState(PanZoomState.PANNING);
- } else if (angle < AXIS_LOCK_ANGLE || angle > (Math.PI - AXIS_LOCK_ANGLE)) {
- mY.setScrollingDisabled(true);
- setState(PanZoomState.PANNING_LOCKED_X);
- } else if (Math.abs(angle - (Math.PI / 2)) < AXIS_LOCK_ANGLE) {
- mX.setScrollingDisabled(true);
- setState(PanZoomState.PANNING_LOCKED_Y);
- } else {
- setState(PanZoomState.PANNING);
- }
- } else if (mMode == AxisLockMode.FREE) {
- setState(PanZoomState.PANNING);
- }
- }
-
- private float panDistance(MotionEvent move) {
- float dx = mX.panDistance(move.getX(0));
- float dy = mY.panDistance(move.getY(0));
- return (float) Math.sqrt(dx * dx + dy * dy);
- }
-
- private void track(float x, float y, long time) {
- float timeDelta = (time - mLastEventTime);
- if (FloatUtils.fuzzyEquals(timeDelta, 0)) {
- // probably a duplicate event, ignore it. using a zero timeDelta will mess
- // up our velocity
- return;
- }
- mLastEventTime = time;
-
-
- // if we're axis-locked check if the user is trying to scroll away from the lock
- if (mMode == AxisLockMode.STICKY) {
- float dx = mX.panDistance(x);
- float dy = mY.panDistance(y);
- double angle = Math.atan2(dy, dx); // range [-pi, pi]
- angle = Math.abs(angle); // range [0, pi]
-
- if (Math.abs(dx) > AXIS_BREAKOUT_THRESHOLD || Math.abs(dy) > AXIS_BREAKOUT_THRESHOLD) {
- if (mState == PanZoomState.PANNING_LOCKED_X) {
- if (angle > AXIS_BREAKOUT_ANGLE && angle < (Math.PI - AXIS_BREAKOUT_ANGLE)) {
- mY.setScrollingDisabled(false);
- setState(PanZoomState.PANNING);
- }
- } else if (mState == PanZoomState.PANNING_LOCKED_Y) {
- if (Math.abs(angle - (Math.PI / 2)) > AXIS_BREAKOUT_ANGLE) {
- mX.setScrollingDisabled(false);
- setState(PanZoomState.PANNING);
- }
- }
- }
- }
-
- mX.updateWithTouchAt(x, timeDelta);
- mY.updateWithTouchAt(y, timeDelta);
- }
-
- private void track(MotionEvent event) {
- mX.saveTouchPos();
- mY.saveTouchPos();
-
- for (int i = 0; i < event.getHistorySize(); i++) {
- track(event.getHistoricalX(0, i),
- event.getHistoricalY(0, i),
- event.getHistoricalEventTime(i));
- }
- track(event.getX(0), event.getY(0), event.getEventTime());
-
- if (stopped()) {
- if (mState == PanZoomState.PANNING) {
- setState(PanZoomState.PANNING_HOLD);
- } else if (mState == PanZoomState.PANNING_LOCKED_X) {
- setState(PanZoomState.PANNING_HOLD_LOCKED_X);
- } else if (mState == PanZoomState.PANNING_LOCKED_Y) {
- setState(PanZoomState.PANNING_HOLD_LOCKED_Y);
- } else {
- // should never happen, but handle anyway for robustness
- Log.e(LOGTAG, "Impossible case " + mState + " when stopped in track");
- setState(PanZoomState.PANNING_HOLD);
- }
- }
-
- mX.startPan();
- mY.startPan();
- updatePosition();
- }
-
- private void scrollBy(float dx, float dy) {
- mTarget.scrollBy(dx, dy);
- }
-
- private void fling() {
- updatePosition();
-
- stopAnimationTask();
-
- boolean stopped = stopped();
- mX.startFling(stopped);
- mY.startFling(stopped);
-
- startAnimationRenderTask(new FlingRenderTask());
- }
-
- /* Performs a bounce-back animation to the given viewport metrics. */
- private void bounce(ImmutableViewportMetrics metrics, PanZoomState state) {
- stopAnimationTask();
-
- ImmutableViewportMetrics bounceStartMetrics = getMetrics();
- if (bounceStartMetrics.fuzzyEquals(metrics)) {
- setState(PanZoomState.NOTHING);
- return;
- }
-
- setState(state);
-
- // 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);
- startAnimationRenderTask(new BounceRenderTask(bounceStartMetrics, metrics));
- }
-
- /* Performs a bounce-back animation to the nearest valid viewport metrics. */
- private void bounce() {
- bounce(getValidViewportMetrics(), PanZoomState.BOUNCE);
- }
-
- /* Starts the fling or bounce animation. */
- private void startAnimationRenderTask(final PanZoomRenderTask task) {
- if (mAnimationRenderTask != null) {
- Log.e(LOGTAG, "Attempted to start a new task without canceling the old one!");
- stopAnimationTask();
- }
-
- mAnimationRenderTask = task;
- mTarget.postRenderTask(mAnimationRenderTask);
- }
-
- /* Stops the fling or bounce animation. */
- private void stopAnimationTask() {
- if (mAnimationRenderTask != null) {
- mAnimationRenderTask.terminate();
- mTarget.removeRenderTask(mAnimationRenderTask);
- mAnimationRenderTask = null;
- }
- }
-
- private float getVelocity() {
- float xvel = mX.getRealVelocity();
- float yvel = mY.getRealVelocity();
- return (float) Math.sqrt(xvel * xvel + yvel * yvel);
- }
-
- @Override
- public PointF getVelocityVector() {
- return new PointF(mX.getRealVelocity(), mY.getRealVelocity());
- }
-
- private boolean stopped() {
- return getVelocity() < STOPPED_THRESHOLD;
- }
-
- PointF resetDisplacement() {
- return new PointF(mX.resetDisplacement(), mY.resetDisplacement());
- }
-
- private void updatePosition() {
- mX.displace();
- mY.displace();
- PointF displacement = resetDisplacement();
- if (FloatUtils.fuzzyEquals(displacement.x, 0.0f) && FloatUtils.fuzzyEquals(displacement.y, 0.0f)) {
- return;
- }
- if (!mDefaultPrevented && !mSubscroller.scrollBy(displacement)) {
- synchronized (mTarget.getLock()) {
- scrollBy(displacement.x, displacement.y);
- }
- }
- }
-
- /**
- * This class is an implementation of RenderTask which enforces its implementor to run in the UI thread.
- *
- */
- private abstract class PanZoomRenderTask extends RenderTask {
-
- /**
- * the time when the current frame was started in ns.
- */
- protected long mCurrentFrameStartTime;
- /**
- * The current frame duration in ns.
- */
- protected long mLastFrameTimeDelta;
-
- private final Runnable mRunnable = new Runnable() {
- @Override
- public final void run() {
- if (mContinueAnimation) {
- animateFrame();
- }
- }
- };
-
- private boolean mContinueAnimation = true;
-
- public PanZoomRenderTask() {
- super(false);
- }
-
- @Override
- protected final boolean internalRun(long timeDelta, long currentFrameStartTime) {
-
- mCurrentFrameStartTime = currentFrameStartTime;
- mLastFrameTimeDelta = timeDelta;
-
- mTarget.post(mRunnable);
- return mContinueAnimation;
- }
-
- /**
- * The method subclasses must override. This method is run on the UI thread thanks to internalRun
- */
- protected abstract void animateFrame();
-
- /**
- * Terminate the animation.
- */
- public void terminate() {
- mContinueAnimation = false;
- }
- }
-
- private class AutonavRenderTask extends PanZoomRenderTask {
- public AutonavRenderTask() {
- super();
- }
-
- @Override
- protected void animateFrame() {
- if (mState != PanZoomState.AUTONAV) {
- finishAnimation();
- return;
- }
-
- updatePosition();
- synchronized (mTarget.getLock()) {
- mTarget.setViewportMetrics(applyZoomDelta(getMetrics(), mAutonavZoomDelta));
- }
- }
- }
-
- /* The task that performs the bounce animation. */
- private class BounceRenderTask extends PanZoomRenderTask {
-
- /*
- * The viewport metrics that represent the start and end of the bounce-back animation,
- * respectively.
- */
- private final ImmutableViewportMetrics mBounceStartMetrics;
- private final ImmutableViewportMetrics mBounceEndMetrics;
- // How long ago this bounce was started in ns.
- private long mBounceDuration;
-
- BounceRenderTask(ImmutableViewportMetrics startMetrics, ImmutableViewportMetrics endMetrics) {
- super();
- mBounceStartMetrics = startMetrics;
- mBounceEndMetrics = endMetrics;
- }
-
- @Override
- 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.BOUNCE || mState == PanZoomState.ANIMATED_ZOOM)) {
- finishAnimation();
- return;
- }
-
- /* Perform the next frame of the bounce-back animation. */
- mBounceDuration = mCurrentFrameStartTime - getStartTime();
- if (mBounceDuration < BOUNCE_ANIMATION_DURATION) {
- advanceBounce();
- return;
- }
-
- /* Finally, if there's nothing else to do, complete the animation and go to sleep. */
- finishBounce();
- finishAnimation();
- setState(PanZoomState.NOTHING);
- }
-
- /* Performs one frame of a bounce animation. */
- private void advanceBounce() {
- synchronized (mTarget.getLock()) {
- float t = easeOut((float)mBounceDuration / BOUNCE_ANIMATION_DURATION);
- ImmutableViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
- mTarget.setViewportMetrics(newMetrics.setPageRectFrom(getMetrics()));
- }
- }
-
- /* Concludes a bounce animation and snaps the viewport into place. */
- private void finishBounce() {
- synchronized (mTarget.getLock()) {
- mTarget.setViewportMetrics(mBounceEndMetrics.setPageRectFrom(getMetrics()));
- }
- }
- }
-
- // The callback that performs the fling animation.
- private class FlingRenderTask extends PanZoomRenderTask {
-
- public FlingRenderTask() {
- super();
- }
-
- @Override
- 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(mLastFrameTimeDelta);
- boolean flingingY = mY.advanceFling(mLastFrameTimeDelta);
-
- 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();
- setState(PanZoomState.NOTHING);
- }
- }
- }
-
- private void finishAnimation() {
- checkMainThread();
-
- stopAnimationTask();
-
- // Force a viewport synchronisation
- mTarget.forceRedraw(null);
- }
-
- /* Returns the nearest viewport metrics with no overscroll visible. */
- private ImmutableViewportMetrics getValidViewportMetrics() {
- return getValidViewportMetrics(getMetrics());
- }
-
- 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 || !constraints.getAllowZoom()) {
- minZoomFactor = constraints.getMinZoom();
- }
- if (constraints.getMaxZoom() > 0 || !constraints.getAllowZoom()) {
- maxZoomFactor = constraints.getMaxZoom();
- }
-
- // Ensure minZoomFactor keeps the page at least as big as the viewport.
- if (pageRect.width() > 0) {
- float pageWidth = pageRect.width();
- float scaleFactor = viewport.width() / pageWidth;
- minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
- if (viewport.width() > pageWidth)
- focusX = 0.0f;
- }
- if (pageRect.height() > 0) {
- float pageHeight = pageRect.height();
- float scaleFactor = viewport.height() / pageHeight;
- minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
- if (viewport.height() > pageHeight)
- 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 class AxisX extends Axis {
- AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); }
- @Override
- public float getOrigin() { return getMetrics().viewportRectLeft; }
- @Override
- protected float getViewportLength() { return getMetrics().getWidth(); }
- @Override
- protected float getPageStart() { return getMetrics().pageRectLeft; }
- @Override
- protected float getPageLength() { return getMetrics().getPageWidth(); }
- @Override
- protected float getVisibleEndOfLayerView() {
- return mTarget.getVisibleEndOfLayerView().x;
- }
- @Override
- protected void overscrollFling(final float velocity) {
- if (mOverscroll != null) {
- mOverscroll.setVelocity(velocity, Overscroll.Axis.X);
- }
- }
- @Override
- protected void overscrollPan(final float distance) {
- if (mOverscroll != null) {
- mOverscroll.setDistance(distance, Overscroll.Axis.X);
- }
- }
- }
-
- private class AxisY extends Axis {
- AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); }
- @Override
- public float getOrigin() { return getMetrics().viewportRectTop; }
- @Override
- protected float getViewportLength() { return getMetrics().getHeight(); }
- @Override
- protected float getPageStart() { return getMetrics().pageRectTop; }
- @Override
- protected float getPageLength() { return getMetrics().getPageHeight(); }
- @Override
- protected float getVisibleEndOfLayerView() {
- return mTarget.getVisibleEndOfLayerView().y;
- }
- @Override
- protected void overscrollFling(final float velocity) {
- if (mOverscroll != null) {
- mOverscroll.setVelocity(velocity, Overscroll.Axis.Y);
- }
- }
- @Override
- protected void overscrollPan(final float distance) {
- if (mOverscroll != null) {
- mOverscroll.setDistance(distance, Overscroll.Axis.Y);
- }
- }
- }
-
- /*
- * Zooming
- */
- @Override
- public boolean onScaleBegin(SimpleScaleGestureDetector detector) {
- if (mState == PanZoomState.ANIMATED_ZOOM)
- return false;
-
- if (!mTarget.getZoomConstraints().getAllowZoom())
- return false;
-
- setState(PanZoomState.PINCHING);
- mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY());
- cancelTouch();
-
- final GeckoEvent event = GeckoEvent.createNativeGestureEvent(
- GeckoEvent.ACTION_MAGNIFY_START, mLastZoomFocus, getMetrics().zoomFactor);
- if (event != null) {
- GeckoAppShell.sendEventToGecko(event);
- }
-
- return true;
- }
-
- @Override
- public boolean onScale(SimpleScaleGestureDetector detector) {
- if (mTarget.getFullScreenState() != FullScreenState.NONE)
- return false;
-
- if (mState != PanZoomState.PINCHING)
- return false;
-
- float prevSpan = detector.getPreviousSpan();
- if (FloatUtils.fuzzyEquals(prevSpan, 0.0f)) {
- // let's eat this one to avoid setting the new zoom to infinity (bug 711453)
- return true;
- }
-
- synchronized (mTarget.getLock()) {
- float zoomFactor = getAdjustedZoomFactor(detector.getCurrentSpan() / prevSpan);
- scrollBy(mLastZoomFocus.x - detector.getFocusX(),
- mLastZoomFocus.y - detector.getFocusY());
- mLastZoomFocus.set(detector.getFocusX(), detector.getFocusY());
- ImmutableViewportMetrics target = getMetrics().scaleTo(zoomFactor, mLastZoomFocus);
-
- // If overscroll is disabled, prevent zooming outside the normal document pans.
- if (mX.getOverScrollMode() == View.OVER_SCROLL_NEVER || mY.getOverScrollMode() == View.OVER_SCROLL_NEVER) {
- target = getValidViewportMetrics(target);
- }
- mTarget.setViewportMetrics(target);
- }
-
- final GeckoEvent event = GeckoEvent.createNativeGestureEvent(
- GeckoEvent.ACTION_MAGNIFY, mLastZoomFocus, getMetrics().zoomFactor);
- if (event != null) {
- GeckoAppShell.sendEventToGecko(event);
- }
-
- return true;
- }
-
- private ImmutableViewportMetrics applyZoomDelta(ImmutableViewportMetrics metrics, float zoomDelta) {
- float oldZoom = metrics.zoomFactor;
- float newZoom = oldZoom + zoomDelta;
- float adjustedZoom = getAdjustedZoomFactor(newZoom / oldZoom);
- // since we don't have a particular focus to zoom to, just use the center
- PointF center = new PointF(metrics.getWidth() / 2.0f, metrics.getHeight() / 2.0f);
- metrics = metrics.scaleTo(adjustedZoom, center);
- return metrics;
- }
-
- private boolean animatedScale(float zoomDelta) {
- if (mState != PanZoomState.NOTHING && mState != PanZoomState.BOUNCE) {
- return false;
- }
- synchronized (mTarget.getLock()) {
- ImmutableViewportMetrics metrics = applyZoomDelta(getMetrics(), zoomDelta);
- bounce(getValidViewportMetrics(metrics), PanZoomState.BOUNCE);
- }
- return true;
- }
-
- private float getAdjustedZoomFactor(float zoomRatio) {
- /*
- * Apply edge resistance if we're zoomed out smaller than the page size by scaling the zoom
- * factor toward 1.0.
- */
- float resistance = Math.min(mX.getEdgeResistance(true), mY.getEdgeResistance(true));
- if (zoomRatio > 1.0f)
- zoomRatio = 1.0f + (zoomRatio - 1.0f) * resistance;
- else
- zoomRatio = 1.0f - (1.0f - zoomRatio) * resistance;
-
- float newZoomFactor = getMetrics().zoomFactor * zoomRatio;
- 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 (newZoomFactor < minZoomFactor) {
- // apply resistance when zooming past minZoomFactor,
- // such that it asymptotically reaches minZoomFactor / 2.0
- // but never exceeds that
- final float rate = 0.5f; // controls how quickly we approach the limit
- float excessZoom = minZoomFactor - newZoomFactor;
- excessZoom = 1.0f - (float)Math.exp(-excessZoom * rate);
- newZoomFactor = minZoomFactor * (1.0f - excessZoom / 2.0f);
- }
-
- if (newZoomFactor > maxZoomFactor) {
- // apply resistance when zooming past maxZoomFactor,
- // such that it asymptotically reaches maxZoomFactor + 1.0
- // but never exceeds that
- float excessZoom = newZoomFactor - maxZoomFactor;
- excessZoom = 1.0f - (float)Math.exp(-excessZoom);
- newZoomFactor = maxZoomFactor + excessZoom;
- }
-
- return newZoomFactor;
- }
-
- @Override
- public void onScaleEnd(SimpleScaleGestureDetector detector) {
- if (mState == PanZoomState.ANIMATED_ZOOM)
- return;
-
- // switch back to the touching state
- startTouch(detector.getFocusX(), detector.getFocusY(), detector.getEventTime());
-
- // Force a viewport synchronisation
- mTarget.forceRedraw(null);
-
- PointF point = new PointF(detector.getFocusX(), detector.getFocusY());
- GeckoEvent event = GeckoEvent.createNativeGestureEvent(GeckoEvent.ACTION_MAGNIFY_END, point, getMetrics().zoomFactor);
-
- if (event == null) {
- return;
- }
-
- GeckoAppShell.sendEventToGecko(event);
- }
-
- @Override
- public boolean getRedrawHint() {
- switch (mState) {
- case PINCHING:
- case ANIMATED_ZOOM:
- case BOUNCE:
- // don't redraw during these because the zoom is (or might be, in the case
- // of BOUNCE) be changing rapidly and gecko will have to redraw the entire
- // display port area. we trigger a force-redraw upon exiting these states.
- return false;
- default:
- // allow redrawing in other states
- return true;
- }
- }
-
- private void sendPointToGecko(String event, MotionEvent motionEvent) {
- String json;
- try {
- PointF point = new PointF(motionEvent.getX(), motionEvent.getY());
- point = mTarget.convertViewPointToLayerPoint(point);
- if (point == null) {
- return;
- }
- json = PointUtils.toJSON(point).toString();
- } catch (Exception e) {
- Log.e(LOGTAG, "Unable to convert point to JSON for " + event, e);
- return;
- }
-
- GeckoAppShell.notifyObservers(event, json);
- }
-
- @Override
- public boolean onDown(MotionEvent motionEvent) {
- mWaitForDoubleTap = mTarget.getZoomConstraints().getAllowDoubleTapZoom();
- return false;
- }
-
- @Override
- public void onShowPress(MotionEvent motionEvent) {
- // If we get this, it will be followed either by a call to
- // onSingleTapUp (if the user lifts their finger before the
- // long-press timeout) or a call to onLongPress (if the user
- // does not). In the former case, we want to make sure it is
- // treated as a click. (Note that if this is called, we will
- // not get a call to onDoubleTap).
- mWaitForDoubleTap = false;
- }
-
- /**
- * MotionEventHelper dragAsync() robocop tests can have us suppress
- * longpress events that are spuriously created on slower test devices.
- */
- @Override
- public void setIsLongpressEnabled(boolean isLongpressEnabled) {
- this.isLongpressEnabled = isLongpressEnabled;
- }
-
- @Override
- public void onLongPress(MotionEvent motionEvent) {
- if (!isLongpressEnabled || mIgnoreLongPress) {
- return;
- }
-
- GeckoEvent e = GeckoEvent.createLongPressEvent(motionEvent);
- GeckoAppShell.sendEventToGecko(e);
- }
-
- @Override
- public boolean onSingleTapUp(MotionEvent motionEvent) {
- // When double-tapping is allowed, we have to wait to see if this is
- // going to be a double-tap.
- if (!mWaitForDoubleTap) {
- sendPointToGecko("Gesture:SingleTap", motionEvent);
- }
- // return false because we still want to get the ACTION_UP event that triggers this
- return false;
- }
-
- @Override
- public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
- // In cases where we don't wait for double-tap, we handle this in onSingleTapUp.
- if (mWaitForDoubleTap) {
- sendPointToGecko("Gesture:SingleTap", motionEvent);
- }
- return true;
- }
-
- @Override
- public boolean onDoubleTap(MotionEvent motionEvent) {
- sendPointToGecko("Gesture:DoubleTap", motionEvent);
- return true;
- }
-
- private void cancelTouch() {
- GeckoAppShell.notifyObservers("Gesture:CancelTouch", "");
- }
-
- /**
- * Zoom to a specified rect IN CSS PIXELS.
- *
- * While we usually use device pixels, @zoomToRect must be specified in CSS
- * pixels.
- */
- private ImmutableViewportMetrics getMetricsToZoomTo(RectF zoomToRect) {
- 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);
- return finalMetrics;
- }
-
- private boolean animatedZoomTo(RectF zoomToRect) {
- bounce(getMetricsToZoomTo(zoomToRect), PanZoomState.ANIMATED_ZOOM);
- return true;
- }
-
- /** This function must be called from the UI thread. */
- @Override
- public void abortPanning() {
- checkMainThread();
- bounce();
- }
-
- @Override
- public void setOverScrollMode(int overscrollMode) {
- mX.setOverScrollMode(overscrollMode);
- mY.setOverScrollMode(overscrollMode);
- }
-
- @Override
- public int getOverScrollMode() {
- return mX.getOverScrollMode();
- }
-
- @Override
- public void setOverscrollHandler(final Overscroll handler) {
- mOverscroll = handler;
- }
-
- @Override
- public ImmutableViewportMetrics adjustScrollForSurfaceShift(ImmutableViewportMetrics aMetrics, PointF aShift) {
- return aMetrics.offsetViewportByAndClamp(aShift.x, aShift.y);
- }
-}
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/LayerRenderer.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/LayerRenderer.java
@@ -1,33 +1,24 @@
/* -*- 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.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.gfx.Layer.RenderContext;
import org.mozilla.gecko.mozglue.DirectBufferAllocator;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.PointF;
-import android.graphics.Rect;
import android.graphics.RectF;
import android.opengl.GLES20;
-import android.os.SystemClock;
import android.util.Log;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.util.ThreadUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@@ -40,49 +31,31 @@ import javax.microedition.khronos.egl.EG
/**
* The layer renderer implements the rendering logic for a layer view.
*/
public class LayerRenderer implements Tabs.OnTabsChangedListener {
private static final String LOGTAG = "GeckoLayerRenderer";
private static final String PROFTAG = "GeckoLayerRendererProf";
- /*
- * The amount of time a frame is allowed to take to render before we declare it a dropped
- * frame.
- */
- private static final int MAX_FRAME_TIME = 16; /* 1000 ms / 60 FPS */
-
- private static final int FRAME_RATE_METER_WIDTH = 128;
- private static final int FRAME_RATE_METER_HEIGHT = 32;
-
- private static final long NANOS_PER_MS = 1000000;
private static final int NANOS_PER_SECOND = 1000000000;
private static final int MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER = 5;
private final LayerView mView;
- private final ScrollbarLayer mHorizScrollLayer;
- private final ScrollbarLayer mVertScrollLayer;
- private final FadeRunnable mFadeRunnable;
private ByteBuffer mCoordByteBuffer;
private FloatBuffer mCoordBuffer;
- private RenderContext mLastPageContext;
private int mMaxTextureSize;
private int mBackgroundColor;
private long mLastFrameTime;
private final CopyOnWriteArrayList<RenderTask> mTasks;
private final CopyOnWriteArrayList<Layer> mExtraLayers = new CopyOnWriteArrayList<Layer>();
- // Dropped frames display
- private final int[] mFrameTimings;
- private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames;
-
// Render profiling output
private int mFramesRendered;
private float mCompleteFramesRendered;
private boolean mProfileRender;
private long mProfileOutputTime;
private IntBuffer mPixelBuffer;
@@ -138,72 +111,29 @@ public class LayerRenderer implements Ta
"uniform sampler2D sTexture;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(sTexture, vTexCoord);\n" +
"}\n";
public LayerRenderer(LayerView view) {
mView = view;
- final BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
- bitmapOptions.inScaled = false;
- Bitmap scrollbarImage =
- BitmapUtils.decodeResource(view.getContext(), R.drawable.scrollbar, bitmapOptions);
- IntSize size = new IntSize(scrollbarImage.getWidth(), scrollbarImage.getHeight());
- scrollbarImage = expandCanvasToPowerOfTwo(scrollbarImage, size);
-
mTasks = new CopyOnWriteArrayList<RenderTask>();
mLastFrameTime = System.nanoTime();
- if (!AppConstants.MOZ_ANDROID_APZ) {
- mVertScrollLayer = new ScrollbarLayer(this, scrollbarImage, size, true);
- mHorizScrollLayer = new ScrollbarLayer(this, diagonalFlip(scrollbarImage), new IntSize(size.height, size.width), false);
- mFadeRunnable = new FadeRunnable();
- } else {
- // final variables need to be initialized in the constructor
- mVertScrollLayer = null;
- mHorizScrollLayer = null;
- mFadeRunnable = null;
- }
-
- mFrameTimings = new int[60];
- mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0;
-
Tabs.registerOnTabsChangedListener(this);
mZoomedViewListeners = new ArrayList<LayerView.ZoomedViewListener>();
}
- private Bitmap expandCanvasToPowerOfTwo(Bitmap image, IntSize size) {
- IntSize potSize = size.nextPowerOfTwo();
- if (size.equals(potSize)) {
- return image;
- }
- // make the bitmap size a power-of-two in both dimensions if it's not already.
- Bitmap potImage = Bitmap.createBitmap(potSize.width, potSize.height, image.getConfig());
- new Canvas(potImage).drawBitmap(image, new Matrix(), null);
- return potImage;
- }
-
- private Bitmap diagonalFlip(Bitmap image) {
- Matrix rotation = new Matrix();
- rotation.setValues(new float[] { 0, 1, 0, 1, 0, 0, 0, 0, 1 }); // transform (x,y) into (y,x)
- Bitmap rotated = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), rotation, true);
- return rotated;
- }
-
public void destroy() {
if (mCoordByteBuffer != null) {
DirectBufferAllocator.free(mCoordByteBuffer);
mCoordByteBuffer = null;
mCoordBuffer = null;
}
- if (!AppConstants.MOZ_ANDROID_APZ) {
- mHorizScrollLayer.destroy();
- mVertScrollLayer.destroy();
- }
Tabs.unregisterOnTabsChangedListener(this);
mZoomedViewListeners.clear();
}
void onSurfaceCreated(EGLConfig config) {
checkMonitoringEnabled();
createDefaultProgram();
activateDefaultProgram();
@@ -354,31 +284,16 @@ public class LayerRenderer implements Ta
if (mCoordBuffer == null) {
throw new IllegalStateException();
}
}
return new RenderContext(viewport, pageRect, zoomFactor,
mPositionHandle, mTextureHandle, mCoordBuffer);
}
- private void updateDroppedFrames(long frameStartTime) {
- int frameElapsedTime = (int)((System.nanoTime() - frameStartTime) / NANOS_PER_MS);
-
- /* Update the running statistics. */
- mFrameTimingsSum -= mFrameTimings[mCurrentFrame];
- mFrameTimingsSum += frameElapsedTime;
- mDroppedFrames -= (mFrameTimings[mCurrentFrame] + 1) / MAX_FRAME_TIME;
- mDroppedFrames += (frameElapsedTime + 1) / MAX_FRAME_TIME;
-
- mFrameTimings[mCurrentFrame] = frameElapsedTime;
- mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length;
-
- int averageTime = mFrameTimingsSum / mFrameTimings.length;
- }
-
void checkMonitoringEnabled() {
mProfileRender = Log.isLoggable(PROFTAG, Log.DEBUG);
}
/*
* create a vertex shader type (GLES20.GL_VERTEX_SHADER)
* or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
*/
@@ -388,79 +303,32 @@ public class LayerRenderer implements Ta
GLES20.glCompileShader(shader);
return shader;
}
public Frame createFrame(ImmutableViewportMetrics metrics) {
return new Frame(metrics);
}
- class FadeRunnable implements Runnable {
- private boolean mStarted;
- long mRunAt; // Would be private but we need both file access and high performance.
-
- void scheduleStartFade(long delay) {
- mRunAt = SystemClock.elapsedRealtime() + delay;
- if (!mStarted) {
- mView.postDelayed(this, delay);
- mStarted = true;
- }
- }
-
- void scheduleNextFadeFrame() {
- if (mStarted) {
- Log.e(LOGTAG, "scheduleNextFadeFrame() called while scheduled for starting fade");
- }
- mView.postDelayed(this, 1000L / 60L); // request another frame at 60fps
- }
-
- boolean timeToFade() {
- return !mStarted;
- }
-
- @Override
- public void run() {
- long timeDelta = mRunAt - SystemClock.elapsedRealtime();
- if (timeDelta > 0) {
- // the run-at time was pushed back, so reschedule
- mView.postDelayed(this, timeDelta);
- } else {
- // reached the run-at time, execute
- mStarted = false;
- mView.requestRender();
- }
- }
- }
-
public class Frame {
// The timestamp recording the start of this frame.
private long mFrameStartTime;
// A fixed snapshot of the viewport metrics that this frame is using to render content.
private final ImmutableViewportMetrics mFrameMetrics;
// A rendering context for page-positioned layers, and one for screen-positioned layers.
private final RenderContext mPageContext, mScreenContext;
// Whether a layer was updated.
private boolean mUpdated;
- private final Rect mPageRect;
- private final Rect mAbsolutePageRect;
public Frame(ImmutableViewportMetrics metrics) {
mFrameMetrics = metrics;
// Work out the offset due to margins
- Layer rootLayer = mView.getLayerClient().getRoot();
mPageContext = createPageContext(metrics);
mScreenContext = createScreenContext(metrics);
-
- RectF pageRect = mFrameMetrics.getPageRect();
- mAbsolutePageRect = RectUtils.round(pageRect);
-
- PointF origin = mFrameMetrics.getOrigin();
- pageRect.offset(-origin.x, -origin.y);
- mPageRect = RectUtils.round(pageRect);
}
/** This function is invoked via JNI; be careful when modifying signature. */
@WrapForJNI(allowMultithread = true)
public void beginDrawing() {
mFrameStartTime = System.nanoTime();
TextureReaper.get().reap();
@@ -468,38 +336,16 @@ public class LayerRenderer implements Ta
mUpdated = true;
Layer rootLayer = mView.getLayerClient().getRoot();
// Run through pre-render tasks
runRenderTasks(mTasks, false, mFrameStartTime);
- if (!AppConstants.MOZ_ANDROID_APZ) {
- boolean hideScrollbars = (mView.getFullScreenState() == FullScreenState.NON_ROOT_ELEMENT);
- if (!mPageContext.fuzzyEquals(mLastPageContext) && !hideScrollbars) {
- // The viewport or page changed, so show the scrollbars again
- // as per UX decision. Don't do this if we're disabling scrolling due to
- // full-screen mode though.
- mVertScrollLayer.unfade();
- mHorizScrollLayer.unfade();
- mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
- } else if (mFadeRunnable.timeToFade()) {
- final long currentMillis = SystemClock.elapsedRealtime();
- final boolean stillFading = mVertScrollLayer.fade(mFadeRunnable.mRunAt, currentMillis) |
- mHorizScrollLayer.fade(mFadeRunnable.mRunAt, currentMillis);
- if (stillFading) {
- mFadeRunnable.scheduleNextFadeFrame();
- }
- }
- mLastPageContext = mPageContext;
- mUpdated &= mVertScrollLayer.update(mPageContext); // called on compositor thread
- mUpdated &= mHorizScrollLayer.update(mPageContext); // called on compositor thread
- }
-
/* Update layers. */
if (rootLayer != null) {
// Called on compositor thread.
mUpdated &= rootLayer.update(mPageContext);
}
for (Layer layer : mExtraLayers) {
mUpdated &= layer.update(mPageContext); // called on compositor thread
@@ -539,26 +385,16 @@ public class LayerRenderer implements Ta
/* Draw any extra layers that were added (likely plugins) */
if (mExtraLayers.size() > 0) {
for (Layer layer : mExtraLayers) {
layer.draw(mPageContext);
}
}
- if (!AppConstants.MOZ_ANDROID_APZ) {
- /* Draw the vertical scrollbar. */
- if (mPageRect.height() > mFrameMetrics.getHeight())
- mVertScrollLayer.draw(mPageContext);
-
- /* Draw the horizontal scrollbar. */
- if (mPageRect.width() > mFrameMetrics.getWidth())
- mHorizScrollLayer.draw(mPageContext);
- }
-
/* Measure how much of the screen is checkerboarding */
Layer rootLayer = mView.getLayerClient().getRoot();
if ((rootLayer != null) &&
(mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) {
// Calculate the incompletely rendered area of the page
float checkerboard = 1.0f - GeckoAppShell.computeRenderIntegrity();
PanningPerfAPI.recordCheckerboard(checkerboard);
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/PanZoomController.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/PanZoomController.java
@@ -1,37 +1,36 @@
/* -*- 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.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.EventDispatcher;
-
import android.graphics.PointF;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
+import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoAppShell;
public interface PanZoomController {
// 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 * GeckoAppShell.getDpi();
// Threshold for sending touch move events to content
public static final float CLICK_THRESHOLD = 1 / 50f * GeckoAppShell.getDpi();
static class Factory {
static PanZoomController create(PanZoomTarget target, View view, EventDispatcher dispatcher) {
if (org.mozilla.gecko.AppConstants.MOZ_ANDROID_APZ) {
return new NativePanZoomController(target, view);
} else {
- return new JavaPanZoomController(target, view, dispatcher);
+ throw new IllegalStateException("Must set MOZ_ANDROID_APZ");
}
}
}
public void destroy();
public boolean onTouchEvent(MotionEvent event);
public boolean onMotionEvent(MotionEvent event);
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/ScrollbarLayer.java
+++ /dev/null
@@ -1,430 +0,0 @@
-/* -*- 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.mozilla.gecko.util.FloatUtils;
-
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.opengl.GLES20;
-import android.util.Log;
-
-import java.nio.FloatBuffer;
-import java.nio.ByteBuffer;
-
-public class ScrollbarLayer extends Layer {
- private static final String LOGTAG = "GeckoScrollbarLayer";
-
- public static final long FADE_DELAY = 500; // milliseconds before fade-out starts
- private static final float FADE_MILLIS = 250; // how long the scrollbar should take to fade
-
- private final boolean mVertical;
- private float mOpacity;
- private final Rect mDirtyRect;
- private IntSize mSize;
-
- private int[] mTextureIDs;
-
- private final BufferedImage mImage;
-
- // To avoid excessive GC, declare some objects here that would otherwise
- // be created and destroyed frequently during draw().
- private final RectF mBarRectF;
- private final Rect mBarRect;
- private final float[] mCoords;
- private final RectF mCapRectF;
-
- private final LayerRenderer mRenderer;
- private int mProgram;
- private int mPositionHandle;
- private int mTextureHandle;
- private int mSampleHandle;
- private int mTMatrixHandle;
- private int mOpacityHandle;
-
- // Fragment shader used to draw the scroll-bar with opacity
- private static final String FRAGMENT_SHADER =
- "precision mediump float;\n" +
- "varying vec2 vTexCoord;\n" +
- "uniform sampler2D sTexture;\n" +
- "uniform float uOpacity;\n" +
- "void main() {\n" +
- " gl_FragColor = texture2D(sTexture, vTexCoord);\n" +
- " gl_FragColor.a *= uOpacity;\n" +
- "}\n";
-
- // Dimensions of the texture bitmap (will always be power-of-two)
- private final int mTexWidth;
- private final int mTexHeight;
- // Some useful dimensions of the actual content in the bitmap
- private final int mBarWidth;
- private final int mCapLength;
-
- private final Rect mStartCapTexCoords; // top/left endcap coordinates
- private final Rect mBodyTexCoords; // 1-pixel slice of the texture to be stretched
- private final Rect mEndCapTexCoords; // bottom/right endcap coordinates
-
- ScrollbarLayer(LayerRenderer renderer, Bitmap scrollbarImage, IntSize imageSize, boolean vertical) {
- super(new IntSize(scrollbarImage.getHeight(), scrollbarImage.getWidth()));
- mImage = new BufferedImage(scrollbarImage);
- mRenderer = renderer;
- mVertical = vertical;
-
- mBarRectF = new RectF();
- mBarRect = new Rect();
- mCoords = new float[20];
- mCapRectF = new RectF();
- mDirtyRect = new Rect();
- mSize = new IntSize(0, 0);
-
- mTexHeight = scrollbarImage.getHeight();
- mTexWidth = scrollbarImage.getWidth();
-
- if (mVertical) {
- mBarWidth = imageSize.width;
- mCapLength = imageSize.height / 2;
- mStartCapTexCoords = new Rect(0, mTexHeight - mCapLength, imageSize.width, mTexHeight);
- mBodyTexCoords = new Rect(0, mTexHeight - (mCapLength + 1), imageSize.width, mTexHeight - mCapLength);
- mEndCapTexCoords = new Rect(0, mTexHeight - imageSize.height, imageSize.width, mTexHeight - (mCapLength + 1));
- } else {
- mBarWidth = imageSize.height;
- mCapLength = imageSize.width / 2;
- mStartCapTexCoords = new Rect(0, mTexHeight - imageSize.height, mCapLength, mTexHeight);
- mBodyTexCoords = new Rect(mCapLength, mTexHeight - imageSize.height, mCapLength + 1, mTexHeight);
- mEndCapTexCoords = new Rect(mCapLength + 1, mTexHeight - imageSize.height, imageSize.width, mTexHeight);
- }
- }
-
- private void createProgram() {
- int vertexShader = LayerRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
- LayerRenderer.DEFAULT_VERTEX_SHADER);
- int fragmentShader = LayerRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
- FRAGMENT_SHADER);
-
- mProgram = GLES20.glCreateProgram();
- GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
- GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
- GLES20.glLinkProgram(mProgram); // creates OpenGL program executables
-
- // Get handles to the shaders' vPosition, aTexCoord, sTexture, and uTMatrix members.
- mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
- mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
- mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture");
- mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix");
- mOpacityHandle = GLES20.glGetUniformLocation(mProgram, "uOpacity");
- }
-
- private void activateProgram() {
- // Add the program to the OpenGL environment
- GLES20.glUseProgram(mProgram);
-
- // Set the transformation matrix
- GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false,
- LayerRenderer.DEFAULT_TEXTURE_MATRIX, 0);
-
- // Enable the arrays from which we get the vertex and texture coordinates
- GLES20.glEnableVertexAttribArray(mPositionHandle);
- GLES20.glEnableVertexAttribArray(mTextureHandle);
-
- GLES20.glUniform1i(mSampleHandle, 0);
- GLES20.glUniform1f(mOpacityHandle, mOpacity);
- }
-
- private void deactivateProgram() {
- GLES20.glDisableVertexAttribArray(mTextureHandle);
- GLES20.glDisableVertexAttribArray(mPositionHandle);
- GLES20.glUseProgram(0);
- }
-
- /**
- * Set the opacity of the scrollbar depending on how much time has
- * passed from the given start time, current time, and the constant duration.
- * Return true if the opacity was decreased, or false if the scrollbars
- * are already fully faded out.
- */
- public boolean fade(final long startMillis, final long currentMillis) {
- if (FloatUtils.fuzzyEquals(mOpacity, 0.0f)) {
- return false;
- }
- beginTransaction(); // called on compositor thread
- mOpacity = Math.max(1 - (currentMillis - startMillis) / FADE_MILLIS, 0.0f);
- endTransaction();
- return true;
- }
-
- /**
- * Restore the opacity of the scrollbar to fully opaque.
- * Return true if the opacity was changed, or false if the scrollbars
- * are already fully opaque.
- */
- public boolean unfade() {
- if (FloatUtils.fuzzyEquals(mOpacity, 1.0f)) {
- return false;
- }
- beginTransaction(); // called on compositor thread
- mOpacity = 1.0f;
- endTransaction();
- return true;
- }
-
- @Override
- public void draw(RenderContext context) {
- if (!initialized())
- return;
-
- // Create the shader program, if necessary
- if (mProgram == 0) {
- createProgram();
- }
-
- // Enable the shader program
- mRenderer.deactivateDefaultProgram();
- activateProgram();
-
- GLES20.glEnable(GLES20.GL_BLEND);
- GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
-
- if (mVertical) {
- getVerticalRect(context, mBarRectF);
- } else {
- getHorizontalRect(context, mBarRectF);
- }
- RectUtils.round(mBarRectF, mBarRect);
-
- GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
- GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
-
- float viewWidth = context.viewport.width();
- float viewHeight = context.viewport.height();
-
- mBarRectF.set(mBarRect.left, viewHeight - mBarRect.top, mBarRect.right, viewHeight - mBarRect.bottom);
-
- // We take a 1-pixel slice from the center of the image and scale it to become the bar
- fillRectCoordBuffer(mCoords, mBarRectF, viewWidth, viewHeight, mBodyTexCoords, mTexWidth, mTexHeight);
-
- // Get the buffer and handles from the context
- FloatBuffer coordBuffer = context.coordBuffer;
- int positionHandle = mPositionHandle;
- int textureHandle = mTextureHandle;
-
- // Make sure we are at position zero in the buffer in case other draw methods did not
- // clean up after themselves
- coordBuffer.position(0);
- coordBuffer.put(mCoords);
-
- // Unbind any the current array buffer so we can use client side buffers
- GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
-
- // Vertex coordinates are x,y,z starting at position 0 into the buffer.
- coordBuffer.position(0);
- GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
-
- // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
- coordBuffer.position(3);
- GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
-
- GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
-
- // Reset the position in the buffer for the next set of vertex and texture coordinates.
- coordBuffer.position(0);
- if (mVertical) {
- // top endcap
- mCapRectF.set(mBarRectF.left, mBarRectF.top + mCapLength, mBarRectF.right, mBarRectF.top);
- } else {
- // left endcap
- mCapRectF.set(mBarRectF.left - mCapLength, mBarRectF.bottom + mBarWidth, mBarRectF.left, mBarRectF.bottom);
- }
-
- fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, mStartCapTexCoords, mTexWidth, mTexHeight);
- coordBuffer.put(mCoords);
-
- // Vertex coordinates are x,y,z starting at position 0 into the buffer.
- coordBuffer.position(0);
- GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
-
- // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
- coordBuffer.position(3);
- GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
-
- GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
-
- // Reset the position in the buffer for the next set of vertex and texture coordinates.
- coordBuffer.position(0);
- if (mVertical) {
- // bottom endcap
- mCapRectF.set(mBarRectF.left, mBarRectF.bottom, mBarRectF.right, mBarRectF.bottom - mCapLength);
- } else {
- // right endcap
- mCapRectF.set(mBarRectF.right, mBarRectF.bottom + mBarWidth, mBarRectF.right + mCapLength, mBarRectF.bottom);
- }
- fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, mEndCapTexCoords, mTexWidth, mTexHeight);
- coordBuffer.put(mCoords);
-
- // Vertex coordinates are x,y,z starting at position 0 into the buffer.
- coordBuffer.position(0);
- GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
-
- // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
- coordBuffer.position(3);
- GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
-
- GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
-
- // Reset the position in the buffer for the next set of vertex and texture coordinates.
- coordBuffer.position(0);
-
- // Enable the default shader program again
- deactivateProgram();
- mRenderer.activateDefaultProgram();
- }
-
- private void getVerticalRect(RenderContext context, RectF dest) {
- RectF viewport = context.viewport;
- RectF pageRect = context.pageRect;
- float viewportHeight = viewport.height();
- float barStart = ((viewport.top - pageRect.top) * (viewportHeight / pageRect.height())) + mCapLength;
- float barEnd = ((viewport.bottom - pageRect.top) * (viewportHeight / pageRect.height())) - mCapLength;
- if (barStart > barEnd) {
- float middle = (barStart + barEnd) / 2.0f;
- barStart = barEnd = middle;
- }
- dest.set(viewport.width() - mBarWidth, barStart, viewport.width(), barEnd);
- }
-
- private void getHorizontalRect(RenderContext context, RectF dest) {
- RectF viewport = context.viewport;
- RectF pageRect = context.pageRect;
- float viewportWidth = viewport.width();
- float barStart = ((viewport.left - pageRect.left) * (viewport.width() / pageRect.width())) + mCapLength;
- float barEnd = ((viewport.right - pageRect.left) * (viewport.width() / pageRect.width())) - mCapLength;
- if (barStart > barEnd) {
- float middle = (barStart + barEnd) / 2.0f;
- barStart = barEnd = middle;
- }
- dest.set(barStart, viewport.height() - mBarWidth, barEnd, viewport.height());
- }
-
- private void validateTexture() {
- /* Calculate the ideal texture size. This must be a power of two if
- * the texture is repeated or OpenGL ES 2.0 isn't supported, as
- * OpenGL ES 2.0 is required for NPOT texture support (without
- * extensions), but doesn't support repeating NPOT textures.
- *
- * XXX Currently, we don't pick a GLES 2.0 context, so always round.
- */
- IntSize textureSize = mImage.getSize().nextPowerOfTwo();
-
- if (!textureSize.equals(mSize)) {
- mSize = textureSize;
-
- // Delete the old texture
- if (mTextureIDs != null) {
- TextureReaper.get().add(mTextureIDs);
- mTextureIDs = null;
-
- // Free the texture immediately, so we don't incur a
- // temporarily increased memory usage.
- TextureReaper.get().reap();
- }
- }
- }
-
- @Override
- protected void performUpdates(RenderContext context) {
- super.performUpdates(context);
-
- // Reallocate the texture if the size has changed
- validateTexture();
-
- // Don't do any work if the image has an invalid size.
- if (!mImage.getSize().isPositive())
- return;
-
- // If we haven't allocated a texture, assume the whole region is dirty
- if (mTextureIDs == null) {
- uploadFullTexture();
- } else {
- uploadDirtyRect(mDirtyRect);
- }
-
- mDirtyRect.setEmpty();
- }
-
- private void uploadFullTexture() {
- IntSize bufferSize = mImage.getSize();
- uploadDirtyRect(new Rect(0, 0, bufferSize.width, bufferSize.height));
- }
-
- private void uploadDirtyRect(Rect dirtyRect) {
- // If we have nothing to upload, just return for now
- if (dirtyRect.isEmpty())
- return;
-
- // It's possible that the buffer will be null, check for that and return
- ByteBuffer imageBuffer = mImage.getBuffer();
- if (imageBuffer == null)
- return;
-
- if (mTextureIDs == null) {
- mTextureIDs = new int[1];
- GLES20.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
- }
-
- int imageFormat = mImage.getFormat();
- BufferedImageGLInfo glInfo = new BufferedImageGLInfo(imageFormat);
-
- bindAndSetGLParameters();
-
- // XXX TexSubImage2D is too broken to rely on on Adreno, and very slow
- // on other chipsets, so we always upload the entire buffer.
- IntSize bufferSize = mImage.getSize();
- if (mSize.equals(bufferSize)) {
- GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width,
- mSize.height, 0, glInfo.format, glInfo.type, imageBuffer);
- } else {
- // Our texture has been expanded to the next power of two.
- // XXX We probably never want to take this path, so throw an exception.
- throw new RuntimeException("Buffer/image size mismatch in ScrollbarLayer!");
- }
- }
-
- private void bindAndSetGLParameters() {
- GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
- GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
- GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
- GLES20.GL_LINEAR);
- GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
- GLES20.GL_LINEAR);
-
- int repeatMode = GLES20.GL_CLAMP_TO_EDGE;
- GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, repeatMode);
- GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, repeatMode);
- }
-
- public void destroy() {
- try {
- if (mImage != null) {
- mImage.destroy();
- }
- } catch (Exception ex) {
- Log.e(LOGTAG, "error clearing buffers: ", ex);
- }
- }
-
- protected int getTextureID() { return mTextureIDs[0]; }
- protected boolean initialized() { return mImage != null && mTextureIDs != null; }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mTextureIDs != null)
- TextureReaper.get().add(mTextureIDs);
- } finally {
- super.finalize();
- }
- }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/SimpleScaleGestureDetector.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/* -*- 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.json.JSONException;
-
-import android.graphics.PointF;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import java.util.LinkedList;
-import java.util.ListIterator;
-import java.util.Stack;
-
-/**
- * A less buggy, and smoother, replacement for the built-in Android ScaleGestureDetector.
- *
- * This gesture detector is more reliable than the built-in ScaleGestureDetector because:
- *
- * - It doesn't assume that pointer IDs are numbered 0 and 1.
- *
- * - It doesn't attempt to correct for "slop" when resting one's hand on the device. On some
- * devices (e.g. the Droid X) this can cause the ScaleGestureDetector to lose track of how many
- * pointers are down, with disastrous results (bug 706684).
- *
- * - Cancelling a zoom into a pan is handled correctly.
- *
- * - Starting with three or more fingers down, releasing fingers so that only two are down, and
- * then performing a scale gesture is handled correctly.
- *
- * - It doesn't take pressure into account, which results in smoother scaling.
- */
-class SimpleScaleGestureDetector {
- private static final String LOGTAG = "GeckoSimpleScaleGestureDetector";
-
- private final SimpleScaleGestureListener mListener;
- private long mLastEventTime;
- private boolean mScaleResult;
-
- /* Information about all pointers that are down. */
- private final LinkedList<PointerInfo> mPointerInfo;
-
- /** Creates a new gesture detector with the given listener. */
- SimpleScaleGestureDetector(SimpleScaleGestureListener listener) {
- mListener = listener;
- mPointerInfo = new LinkedList<PointerInfo>();
- }
-
- /** Forward touch events to this function. */
- public void onTouchEvent(MotionEvent event) {
- switch (event.getAction() & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN:
- // If we get ACTION_DOWN while still tracking any pointers,
- // something is wrong. Cancel the current gesture and start over.
- if (getPointersDown() > 0)
- onTouchEnd(event);
- onTouchStart(event);
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- onTouchStart(event);
- break;
- case MotionEvent.ACTION_MOVE:
- onTouchMove(event);
- break;
- case MotionEvent.ACTION_POINTER_UP:
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- onTouchEnd(event);
- break;
- }
- }
-
- private int getPointersDown() {
- return mPointerInfo.size();
- }
-
- private int getActionIndex(MotionEvent event) {
- return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
- >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- }
-
- private void onTouchStart(MotionEvent event) {
- mLastEventTime = event.getEventTime();
- mPointerInfo.addFirst(PointerInfo.create(event, getActionIndex(event)));
- if (getPointersDown() == 2) {
- sendScaleGesture(EventType.BEGIN);
- }
- }
-
- private void onTouchMove(MotionEvent event) {
- mLastEventTime = event.getEventTime();
- for (int i = 0; i < event.getPointerCount(); i++) {
- PointerInfo pointerInfo = pointerInfoForEventIndex(event, i);
- if (pointerInfo != null) {
- pointerInfo.populate(event, i);
- }
- }
-
- if (getPointersDown() == 2) {
- sendScaleGesture(EventType.CONTINUE);
- }
- }
-
- private void onTouchEnd(MotionEvent event) {
- mLastEventTime = event.getEventTime();
-
- int action = event.getAction() & MotionEvent.ACTION_MASK;
- boolean isCancel = (action == MotionEvent.ACTION_CANCEL ||
- action == MotionEvent.ACTION_DOWN);
-
- int id = event.getPointerId(getActionIndex(event));
- ListIterator<PointerInfo> iterator = mPointerInfo.listIterator();
- while (iterator.hasNext()) {
- PointerInfo pointerInfo = iterator.next();
- if (!(isCancel || pointerInfo.getId() == id)) {
- continue;
- }
-
- // One of the pointers we were tracking was lifted. Remove its info object from the
- // list, recycle it to avoid GC pauses, and send an onScaleEnd() notification if this
- // ended the gesture.
- iterator.remove();
- pointerInfo.recycle();
- if (getPointersDown() == 1) {
- sendScaleGesture(EventType.END);
- }
- }
- }
-
- /**
- * Returns the X coordinate of the focus location (the midpoint of the two fingers). If only
- * one finger is down, returns the location of that finger.
- */
- public float getFocusX() {
- switch (getPointersDown()) {
- case 1:
- return mPointerInfo.getFirst().getCurrent().x;
- case 2:
- PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
- return (pointerA.getCurrent().x + pointerB.getCurrent().x) / 2.0f;
- }
-
- Log.e(LOGTAG, "No gesture taking place in getFocusX()!");
- return 0.0f;
- }
-
- /**
- * Returns the Y coordinate of the focus location (the midpoint of the two fingers). If only
- * one finger is down, returns the location of that finger.
- */
- public float getFocusY() {
- switch (getPointersDown()) {
- case 1:
- return mPointerInfo.getFirst().getCurrent().y;
- case 2:
- PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
- return (pointerA.getCurrent().y + pointerB.getCurrent().y) / 2.0f;
- }
-
- Log.e(LOGTAG, "No gesture taking place in getFocusY()!");
- return 0.0f;
- }
-
- /** Returns the most recent distance between the two pointers. */
- public float getCurrentSpan() {
- if (getPointersDown() != 2) {
- Log.e(LOGTAG, "No gesture taking place in getCurrentSpan()!");
- return 0.0f;
- }
-
- PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
- return PointUtils.distance(pointerA.getCurrent(), pointerB.getCurrent());
- }
-
- /** Returns the second most recent distance between the two pointers. */
- public float getPreviousSpan() {
- if (getPointersDown() != 2) {
- Log.e(LOGTAG, "No gesture taking place in getPreviousSpan()!");
- return 0.0f;
- }
-
- PointerInfo pointerA = mPointerInfo.getFirst(), pointerB = mPointerInfo.getLast();
- PointF a = pointerA.getPrevious(), b = pointerB.getPrevious();
- if (a == null || b == null) {
- a = pointerA.getCurrent();
- b = pointerB.getCurrent();
- }
-
- return PointUtils.distance(a, b);
- }
-
- /** Returns the time of the last event related to the gesture. */
- public long getEventTime() {
- return mLastEventTime;
- }
-
- /** Returns true if the scale gesture is in progress and false otherwise. */
- public boolean isInProgress() {
- return getPointersDown() == 2;
- }
-
- /* Sends the requested scale gesture notification to the listener. */
- private void sendScaleGesture(EventType eventType) {
- switch (eventType) {
- case BEGIN:
- mScaleResult = mListener.onScaleBegin(this);
- break;
- case CONTINUE:
- if (mScaleResult) {
- mListener.onScale(this);
- }
- break;
- case END:
- if (mScaleResult) {
- mListener.onScaleEnd(this);
- }
- break;
- }
- }
-
- /*
- * Returns the pointer info corresponding to the given pointer index, or null if the pointer
- * isn't one that's being tracked.
- */
- private PointerInfo pointerInfoForEventIndex(MotionEvent event, int index) {
- int id = event.getPointerId(index);
- for (PointerInfo pointerInfo : mPointerInfo) {
- if (pointerInfo.getId() == id) {
- return pointerInfo;
- }
- }
- return null;
- }
-
- private enum EventType {
- BEGIN,
- CONTINUE,
- END,
- }
-
- /* Encapsulates information about one of the two fingers involved in the gesture. */
- private static class PointerInfo {
- /* A free list that recycles pointer info objects, to reduce GC pauses. */
- private static Stack<PointerInfo> sPointerInfoFreeList;
-
- private int mId;
- private PointF mCurrent, mPrevious;
-
- private PointerInfo() {
- // External users should use create() instead.
- }
-
- /* Creates or recycles a new PointerInfo instance from an event and a pointer index. */
- public static PointerInfo create(MotionEvent event, int index) {
- if (sPointerInfoFreeList == null) {
- sPointerInfoFreeList = new Stack<PointerInfo>();
- }
-
- PointerInfo pointerInfo;
- if (sPointerInfoFreeList.empty()) {
- pointerInfo = new PointerInfo();
- } else {
- pointerInfo = sPointerInfoFreeList.pop();
- }
-
- pointerInfo.populate(event, index);
- return pointerInfo;
- }
-
- /*
- * Fills in the fields of this instance from the given motion event and pointer index
- * within that event.
- */
- public void populate(MotionEvent event, int index) {
- mId = event.getPointerId(index);
- mPrevious = mCurrent;
- mCurrent = new PointF(event.getX(index), event.getY(index));
- }
-
- public void recycle() {
- mId = -1;
- mPrevious = mCurrent = null;
- sPointerInfoFreeList.push(this);
- }
-
- public int getId() { return mId; }
- public PointF getCurrent() { return mCurrent; }
- public PointF getPrevious() { return mPrevious; }
-
- @Override
- public String toString() {
- if (mId == -1) {
- return "(up)";
- }
-
- try {
- String prevString;
- if (mPrevious == null) {
- prevString = "n/a";
- } else {
- prevString = PointUtils.toJSON(mPrevious).toString();
- }
-
- // The current position should always be non-null.
- String currentString = PointUtils.toJSON(mCurrent).toString();
- return "id=" + mId + " cur=" + currentString + " prev=" + prevString;
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- public static interface SimpleScaleGestureListener {
- public boolean onScale(SimpleScaleGestureDetector detector);
- public boolean onScaleBegin(SimpleScaleGestureDetector detector);
- public void onScaleEnd(SimpleScaleGestureDetector detector);
- }
-}
-
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/SubdocumentScrollHelper.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/* -*- 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.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoEvent;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.util.GeckoEventListener;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.graphics.PointF;
-import android.os.Handler;
-import android.util.Log;
-
-class SubdocumentScrollHelper implements GeckoEventListener {
- private static final String LOGTAG = "GeckoSubdocScroll";
-
- private static final String MESSAGE_PANNING_OVERRIDE = "Panning:Override";
- private static final String MESSAGE_CANCEL_OVERRIDE = "Panning:CancelOverride";
- private static final String MESSAGE_SCROLL = "Gesture:Scroll";
- private static final String MESSAGE_SCROLL_ACK = "Gesture:ScrollAck";
-
- private final Handler mUiHandler;
- private final EventDispatcher mEventDispatcher;
-
- /* This is the amount of displacement we have accepted but not yet sent to JS; this is
- * only valid when mOverrideScrollPending is true. */
- private final PointF mPendingDisplacement;
-
- /* When this is true, we're sending scroll events to JS to scroll the active subdocument. */
- private boolean mOverridePanning;
-
- /* When this is true, we have received an ack for the last scroll event we sent to JS, and
- * are ready to send the next scroll event. Note we only ever have one scroll event inflight
- * at a time. */
- private boolean mOverrideScrollAck;
-
- /* When this is true, we have a pending scroll that we need to send to JS; we were unable
- * to send it when it was initially requested because mOverrideScrollAck was not true. */
- private boolean mOverrideScrollPending;
-
- /* When this is true, the last scroll event we sent actually did some amount of scrolling on
- * the subdocument; we use this to decide when we have reached the end of the subdocument. */
- private boolean mScrollSucceeded;
-
- SubdocumentScrollHelper(EventDispatcher eventDispatcher) {
- // mUiHandler will be bound to the UI thread since that's where this constructor runs
- mUiHandler = new Handler();
- mPendingDisplacement = new PointF();
-
- mEventDispatcher = eventDispatcher;
- mEventDispatcher.registerGeckoThreadListener(this,
- MESSAGE_PANNING_OVERRIDE,
- MESSAGE_CANCEL_OVERRIDE,
- MESSAGE_SCROLL_ACK);
- }
-
- void destroy() {
- mEventDispatcher.unregisterGeckoThreadListener(this,
- MESSAGE_PANNING_OVERRIDE,
- MESSAGE_CANCEL_OVERRIDE,
- MESSAGE_SCROLL_ACK);
- }
-
- boolean scrollBy(PointF displacement) {
- if (! mOverridePanning) {
- return false;
- }
-
- if (! mOverrideScrollAck) {
- mOverrideScrollPending = true;
- mPendingDisplacement.x += displacement.x;
- mPendingDisplacement.y += displacement.y;
- return true;
- }
-
- JSONObject json = new JSONObject();
- try {
- json.put("x", displacement.x);
- json.put("y", displacement.y);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error forming subwindow scroll message: ", e);
- }
- GeckoAppShell.notifyObservers(MESSAGE_SCROLL, json.toString());
-
- mOverrideScrollAck = false;
- mOverrideScrollPending = false;
- // clear the |mPendingDisplacement| after serializing |displacement| to
- // JSON because they might be the same object
- mPendingDisplacement.x = 0;
- mPendingDisplacement.y = 0;
-
- return true;
- }
-
- void cancel() {
- mOverridePanning = false;
- }
-
- boolean scrolling() {
- return mOverridePanning;
- }
-
- boolean lastScrollSucceeded() {
- return mScrollSucceeded;
- }
-
- // GeckoEventListener implementation
-
- @Override
- public void handleMessage(final String event, final JSONObject message) {
- // This comes in on the Gecko thread; hand off the handling to the UI thread.
- mUiHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- if (MESSAGE_PANNING_OVERRIDE.equals(event)) {
- mOverridePanning = true;
- mOverrideScrollAck = true;
- mOverrideScrollPending = false;
- mScrollSucceeded = true;
- } else if (MESSAGE_CANCEL_OVERRIDE.equals(event)) {
- mOverridePanning = false;
- } else if (MESSAGE_SCROLL_ACK.equals(event)) {
- mOverrideScrollAck = true;
- mScrollSucceeded = message.getBoolean("scrolled");
- if (mOverridePanning && mOverrideScrollPending) {
- scrollBy(mPendingDisplacement);
- }
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception handling message", e);
- }
- }
- });
- }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/TouchEventHandler.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/* -*- 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.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.View;
-
-import java.util.LinkedList;
-import java.util.Queue;
-
-/**
- * This class handles incoming touch events from the user and sends them to
- * listeners in Gecko and/or performs the "default action" (asynchronous pan/zoom
- * behaviour. EVERYTHING IN THIS CLASS MUST RUN ON THE UI THREAD.
- *
- * In the following code/comments, a "block" of events refers to a contiguous
- * sequence of events that starts with a DOWN or POINTER_DOWN and goes up to
- * but not including the next DOWN or POINTER_DOWN event.
- *
- * "Dispatching" an event refers to performing the default actions for the event,
- * which at our level of abstraction just means sending it off to the gesture
- * detectors and the pan/zoom controller.
- *
- * If an event is "default-prevented" that means one or more listeners in Gecko
- * has called preventDefault() on the event, which means that the default action
- * for that event should not occur. Usually we care about a "block" of events being
- * default-prevented, which means that the DOWN/POINTER_DOWN event that started
- * the block, or the first MOVE event following that, were prevent-defaulted.
- *
- * A "default-prevented notification" is when we here in Java-land receive a notification
- * from gecko as to whether or not a block of events was default-prevented. This happens
- * at some point after the first or second event in the block is processed in Gecko.
- * This code assumes we get EXACTLY ONE default-prevented notification for each block
- * of events.
- *
- * Note that even if all events are default-prevented, we still send specific types
- * of notifications to the pan/zoom controller. The notifications are needed
- * to respond to user actions a timely manner regardless of default-prevention,
- * and fix issues like bug 749384.
- */
-final class TouchEventHandler implements Tabs.OnTabsChangedListener {
- private static final String LOGTAG = "GeckoTouchEventHandler";
-
- // The time limit for listeners to respond with preventDefault on touchevents
- // before we begin panning the page
- private final int EVENT_LISTENER_TIMEOUT = 200;
-
- private final View mView;
- private final GestureDetector mGestureDetector;
- private final SimpleScaleGestureDetector mScaleGestureDetector;
- private final JavaPanZoomController mPanZoomController;
-
- // the queue of events that we are holding on to while waiting for a preventDefault
- // notification
- private final Queue<MotionEvent> mEventQueue;
- private final ListenerTimeoutProcessor mListenerTimeoutProcessor;
-
- // whether or not we should wait for touch listeners to respond (this state is
- // per-tab and is updated when we switch tabs).
- private boolean mWaitForTouchListeners;
-
- // true if we should hold incoming events in our queue. this is re-set for every
- // block of events, this is cleared once we find out if the block has been
- // default-prevented or not (or we time out waiting for that).
- private boolean mHoldInQueue;
-
- // false if the current event block has been default-prevented. In this case,
- // we still pass the event to both Gecko and the pan/zoom controller, but the
- // latter will not use it to scroll content. It may still use the events for
- // other things, such as making the dynamic toolbar visible.
- private boolean mAllowDefaultAction;
-
- // this next variable requires some explanation. strap yourself in.
- //
- // for each block of events, we do two things: (1) send the events to gecko and expect
- // exactly one default-prevented notification in return, and (2) kick off a delayed
- // ListenerTimeoutProcessor that triggers in case we don't hear from the listener in
- // a timely fashion.
- // since events are constantly coming in, we need to be able to handle more than one
- // block of events in the queue.
- //
- // this means that there are ordering restrictions on these that we can take advantage of,
- // and need to abide by. blocks of events in the queue will always be in the order that
- // the user generated them. default-prevented notifications we get from gecko will be in
- // the same order as the blocks of events in the queue. the ListenerTimeoutProcessors that
- // have been posted will also fire in the same order as the blocks of events in the queue.
- // HOWEVER, we may get multiple default-prevented notifications interleaved with multiple
- // ListenerTimeoutProcessor firings, and that interleaving is not predictable.
- //
- // therefore, we need to make sure that for each block of events, we process the queued
- // events exactly once, either when we get the default-prevented notification, or when the
- // timeout expires (whichever happens first). there is no way to associate the
- // default-prevented notification with a particular block of events other than via ordering,
- //
- // so what we do to accomplish this is to track a "processing balance", which is the number
- // of default-prevented notifications that we have received, minus the number of ListenerTimeoutProcessors
- // that have fired. (think "balance" as in teeter-totter balance). this value is:
- // - zero when we are in a state where the next default-prevented notification we expect
- // to receive and the next ListenerTimeoutProcessor we expect to fire both correspond to
- // the next block of events in the queue.
- // - positive when we are in a state where we have received more default-prevented notifications
- // than ListenerTimeoutProcessors. This means that the next default-prevented notification
- // does correspond to the block at the head of the queue, but the next n ListenerTimeoutProcessors
- // need to be ignored as they are for blocks we have already processed. (n is the absolute value
- // of the balance.)
- // - negative when we are in a state where we have received more ListenerTimeoutProcessors than
- // default-prevented notifications. This means that the next ListenerTimeoutProcessor that
- // we receive does correspond to the block at the head of the queue, but the next n
- // default-prevented notifications need to be ignored as they are for blocks we have already
- // processed. (n is the absolute value of the balance.)
- private int mProcessingBalance;
-
- TouchEventHandler(Context context, View view, JavaPanZoomController panZoomController) {
- mView = view;
-
- mEventQueue = new LinkedList<MotionEvent>();
- mPanZoomController = panZoomController;
- mGestureDetector = new GestureDetector(context, mPanZoomController);
- mScaleGestureDetector = new SimpleScaleGestureDetector(mPanZoomController);
- mListenerTimeoutProcessor = new ListenerTimeoutProcessor();
- mAllowDefaultAction = true;
-
- mGestureDetector.setOnDoubleTapListener(mPanZoomController);
-
- Tabs.registerOnTabsChangedListener(this);
- }
-
- public void destroy() {
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- /* This function MUST be called on the UI thread */
- public boolean handleEvent(MotionEvent event) {
- if (isDownEvent(event)) {
- // this is the start of a new block of events! whee!
- mHoldInQueue = mWaitForTouchListeners;
-
- // Set mAllowDefaultAction to true so that in the event we dispatch events, the
- // PanZoomController doesn't treat them as if they've been prevent-defaulted
- // when they haven't.
- mAllowDefaultAction = true;
- if (mHoldInQueue) {
- // if the new block we are starting is the current block (i.e. there are no
- // other blocks waiting in the queue, then we should let the pan/zoom controller
- // know we are waiting for the touch listeners to run
- if (mEventQueue.isEmpty()) {
- mPanZoomController.startingNewEventBlock(event, true);
- }
- } else {
- // we're not going to be holding this block of events in the queue, but we need
- // a marker of some sort so that the processEventBlock loop deals with the blocks
- // in the right order as notifications come in. we use a single null event in
- // the queue as a placeholder for a block of events that has already been dispatched.
- mEventQueue.add(null);
- mPanZoomController.startingNewEventBlock(event, false);
- }
-
- // set the timeout so that we dispatch these events and update mProcessingBalance
- // if we don't get a default-prevented notification
- mView.postDelayed(mListenerTimeoutProcessor, EVENT_LISTENER_TIMEOUT);
- }
-
- // if we need to hold the events, add it to the queue, otherwise dispatch
- // it directly.
- if (mHoldInQueue) {
- mEventQueue.add(MotionEvent.obtain(event));
- } else {
- dispatchEvent(event, mAllowDefaultAction);
- }
-
- return false;
- }
-
- /**
- * This function is how gecko sends us a default-prevented notification. It is called
- * once gecko knows definitively whether the block of events has had preventDefault
- * called on it (either on the initial down event that starts the block, or on
- * the first event following that down event).
- *
- * This function MUST be called on the UI thread.
- */
- public void handleEventListenerAction(boolean allowDefaultAction) {
- if (mProcessingBalance > 0) {
- // this event listener that triggered this took too long, and the corresponding
- // ListenerTimeoutProcessor runnable already ran for the event in question. the
- // block of events this is for has already been processed, so we don't need to
- // do anything here.
- } else {
- processEventBlock(allowDefaultAction);
- }
- mProcessingBalance--;
- }
-
- /* This function MUST be called on the UI thread. */
- public void setWaitForTouchListeners(boolean aValue) {
- mWaitForTouchListeners = aValue;
- }
-
- private boolean isDownEvent(MotionEvent event) {
- int action = (event.getAction() & MotionEvent.ACTION_MASK);
- return (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN);
- }
-
- private boolean touchFinished(MotionEvent event) {
- int action = (event.getAction() & MotionEvent.ACTION_MASK);
- return (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL);
- }
-
- /**
- * Dispatch the event to the gesture detectors and the pan/zoom controller.
- */
- private void dispatchEvent(MotionEvent event, boolean allowDefaultAction) {
- if (allowDefaultAction) {
- if (mGestureDetector.onTouchEvent(event)) {
- return;
- }
- mScaleGestureDetector.onTouchEvent(event);
- if (mScaleGestureDetector.isInProgress()) {
- return;
- }
- }
- mPanZoomController.handleEvent(event, !allowDefaultAction);
- }
-
- /**
- * Process the block of events at the head of the queue now that we know
- * whether it has been default-prevented or not.
- */
- private void processEventBlock(boolean allowDefaultAction) {
- if (mEventQueue.isEmpty()) {
- Log.e(LOGTAG, "Unexpected empty event queue in processEventBlock!", new Exception());
- return;
- }
-
- // the odd loop condition is because the first event in the queue will
- // always be a DOWN or POINTER_DOWN event, and we want to process all
- // the events in the queue starting at that one, up to but not including
- // the next DOWN or POINTER_DOWN event.
-
- MotionEvent event = mEventQueue.poll();
- while (true) {
- // event being null here is valid and represents a block of events
- // that has already been dispatched.
-
- if (event != null) {
- dispatchEvent(event, allowDefaultAction);
- event.recycle();
- event = null;
- }
- if (mEventQueue.isEmpty()) {
- // we have processed the backlog of events, and are all caught up.
- // now we can set clear the hold flag and set the dispatch flag so
- // that the handleEvent() function can do the right thing for all
- // remaining events in this block (which is still ongoing) without
- // having to put them in the queue.
- mHoldInQueue = false;
- mAllowDefaultAction = allowDefaultAction;
- break;
- }
- event = mEventQueue.peek();
- if (event == null || isDownEvent(event)) {
- // we have finished processing the block we were interested in.
- // now we wait for the next call to processEventBlock
- if (event != null) {
- mPanZoomController.startingNewEventBlock(event, true);
- }
- break;
- }
- // pop the event we peeked above, as it is still part of the block and
- // we want to keep processing
- mEventQueue.remove();
- }
- }
-
- private class ListenerTimeoutProcessor implements Runnable {
- /* This MUST be run on the UI thread */
- @Override
- public void run() {
- if (mProcessingBalance < 0) {
- // gecko already responded with default-prevented notification, and so
- // the block of events this ListenerTimeoutProcessor corresponds to have
- // already been removed from the queue.
- } else {
- processEventBlock(true);
- }
- mProcessingBalance++;
- }
- }
-
- // Tabs.OnTabsChangedListener implementation
-
- @Override
- public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
- if ((Tabs.getInstance().isSelectedTab(tab) && msg == Tabs.TabEvents.STOP) || msg == Tabs.TabEvents.SELECTED) {
- mWaitForTouchListeners = tab.getHasTouchListeners();
- }
- }
-}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -349,51 +349,43 @@ gbjar.sources += ['java/org/mozilla/geck
'GeckoService.java',
'GeckoSharedPrefs.java',
'GeckoSmsManager.java',
'GeckoThread.java',
'GeckoUpdateReceiver.java',
'GeckoView.java',
'GeckoViewChrome.java',
'GeckoViewContent.java',
- 'gfx/Axis.java',
'gfx/BitmapUtils.java',
- 'gfx/BufferedImage.java',
- 'gfx/BufferedImageGLInfo.java',
'gfx/DisplayPortCalculator.java',
'gfx/DisplayPortMetrics.java',
'gfx/DrawTimingQueue.java',
'gfx/DynamicToolbarAnimator.java',
'gfx/FloatSize.java',
'gfx/FullScreenState.java',
'gfx/GeckoLayerClient.java',
'gfx/GLController.java',
'gfx/ImmutableViewportMetrics.java',
'gfx/IntSize.java',
- 'gfx/JavaPanZoomController.java',
'gfx/Layer.java',
'gfx/LayerRenderer.java',
'gfx/LayerView.java',
'gfx/NativePanZoomController.java',
'gfx/Overscroll.java',
'gfx/OverscrollEdgeEffect.java',
'gfx/PanningPerfAPI.java',
'gfx/PanZoomController.java',
'gfx/PanZoomTarget.java',
'gfx/PluginLayer.java',
'gfx/PointUtils.java',
'gfx/ProgressiveUpdateData.java',
'gfx/RectUtils.java',
'gfx/RenderTask.java',
- 'gfx/ScrollbarLayer.java',
- 'gfx/SimpleScaleGestureDetector.java',
- 'gfx/SubdocumentScrollHelper.java',
'gfx/TextureGenerator.java',
'gfx/TextureReaper.java',
- 'gfx/TouchEventHandler.java',
'gfx/ViewTransform.java',
'gfx/VirtualLayer.java',
'GlobalHistory.java',
'GuestSession.java',
'health/HealthRecorder.java',
'health/SessionInformation.java',
'health/StubbedHealthRecorder.java',
'home/BookmarkFolderView.java',
deleted file mode 100644
index 55462df1dc0e7aeecf6cff11acb9ce48364358e8..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001