Bug 704784 - Fade scrollbars when content stops moving [r=pcwalton]

This commit is contained in:
Kartikaya Gupta 2011-12-09 16:05:24 -05:00
parent 9297a9e422
commit e2c4df2f39
4 changed files with 136 additions and 11 deletions

View File

@ -44,6 +44,7 @@ import android.graphics.RectF;
import android.util.Log;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.opengles.GL10;
import org.mozilla.gecko.FloatUtils;
public abstract class Layer {
private final ReentrantLock mTransactionLock;
@ -191,6 +192,15 @@ public abstract class Layer {
pageSize = aPageSize;
zoomFactor = aZoomFactor;
}
public boolean fuzzyEquals(RenderContext other) {
if (other == null) {
return false;
}
return RectUtils.fuzzyEquals(viewport, other.viewport)
&& pageSize.fuzzyEquals(other.pageSize)
&& FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor);
}
}
}

View File

@ -67,6 +67,8 @@ import java.nio.ByteBuffer;
* The layer renderer implements the rendering logic for a layer view.
*/
public class LayerRenderer implements GLSurfaceView.Renderer {
private static final String LOGTAG = "GeckoLayerRenderer";
private static final float BACKGROUND_COLOR_R = 0.81f;
private static final float BACKGROUND_COLOR_G = 0.81f;
private static final float BACKGROUND_COLOR_B = 0.81f;
@ -86,6 +88,8 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
private final TextLayer mFrameRateLayer;
private final ScrollbarLayer mHorizScrollLayer;
private final ScrollbarLayer mVertScrollLayer;
private final FadeRunnable mFadeRunnable;
private RenderContext mLastPageContext;
// Dropped frames display
private int[] mFrameTimings;
@ -108,6 +112,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
mHorizScrollLayer = ScrollbarLayer.create(false);
mVertScrollLayer = ScrollbarLayer.create(true);
mFadeRunnable = new FadeRunnable();
mFrameTimings = new int[60];
mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0;
@ -140,6 +145,20 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
Layer rootLayer = controller.getRoot();
RenderContext screenContext = createScreenContext(), pageContext = createPageContext();
if (!pageContext.fuzzyEquals(mLastPageContext)) {
// the viewport or page changed, so show the scrollbars again
// as per UX decision
mVertScrollLayer.unfade();
mHorizScrollLayer.unfade();
mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
} else if (mFadeRunnable.timeToFade()) {
boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade();
if (stillFading) {
mFadeRunnable.scheduleNextFadeFrame();
}
}
mLastPageContext = pageContext;
/* Update layers. */
if (rootLayer != null) rootLayer.update(gl);
mShadowLayer.update(gl);
@ -299,4 +318,40 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
}
}).run();
}
class FadeRunnable implements Runnable {
private boolean mStarted;
private long mRunAt;
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;
}
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();
}
}
}
}

View File

@ -130,4 +130,11 @@ public final class RectUtils {
FloatUtils.interpolate(from.right, to.right, t),
FloatUtils.interpolate(from.bottom, to.bottom, t));
}
public static boolean fuzzyEquals(RectF a, RectF b) {
return FloatUtils.fuzzyEquals(a.top, b.top)
&& FloatUtils.fuzzyEquals(a.left, b.left)
&& FloatUtils.fuzzyEquals(a.right, b.right)
&& FloatUtils.fuzzyEquals(a.bottom, b.bottom);
}
}

View File

@ -50,15 +50,18 @@ import android.opengl.GLES11Ext;
import android.util.Log;
import java.nio.ByteBuffer;
import javax.microedition.khronos.opengles.GL10;
import org.mozilla.gecko.FloatUtils;
/**
* Draws a small rect. This is scaled to become a scrollbar.
*/
public class ScrollbarLayer extends TileLayer {
public static final long FADE_DELAY = 500; // milliseconds before fade-out starts
private static final float FADE_AMOUNT = 0.03f; // how much (as a percent) the scrollbar should fade per frame
private static final int PADDING = 1; // gap between scrollbar and edge of viewport
private static final int BAR_SIZE = 6;
private static final int CAP_RADIUS = (BAR_SIZE / 2);
private static final int SCROLLBAR_COLOR = Color.argb(127, 0, 0, 0);
private static final int[] CROPRECT_MIDPIXEL = new int[] { CAP_RADIUS, CAP_RADIUS, 1, 1 };
private static final int[] CROPRECT_TOPCAP = new int[] { 0, CAP_RADIUS, BAR_SIZE, -CAP_RADIUS };
@ -67,30 +70,80 @@ public class ScrollbarLayer extends TileLayer {
private static final int[] CROPRECT_RIGHTCAP = new int[] { CAP_RADIUS, BAR_SIZE, CAP_RADIUS, -BAR_SIZE };
private final boolean mVertical;
private final ByteBuffer mBuffer;
private final Bitmap mBitmap;
private final Canvas mCanvas;
private float mOpacity;
private ScrollbarLayer(CairoImage image, boolean vertical) {
private ScrollbarLayer(CairoImage image, boolean vertical, ByteBuffer buffer) {
super(false, image);
mVertical = vertical;
mBuffer = buffer;
mBitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
public static ScrollbarLayer create(boolean vertical) {
// just create an empty image for now, it will get drawn
// on demand anyway
int imageSize = nextPowerOfTwo(BAR_SIZE);
ByteBuffer buffer = ByteBuffer.allocateDirect(imageSize * imageSize * 4);
CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize, CairoImage.FORMAT_ARGB32);
return new ScrollbarLayer(image, vertical, buffer);
}
Bitmap bitmap = Bitmap.createBitmap(imageSize, imageSize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
/**
* Decrease the opacity of the scrollbar by one frame's worth.
* Return true if the opacity was decreased, or false if the scrollbars
* are already fully faded out.
*/
public boolean fade() {
if (FloatUtils.fuzzyEquals(mOpacity, 0.0f)) {
return false;
}
beginTransaction();
try {
setOpacity(Math.max(mOpacity - FADE_AMOUNT, 0.0f));
invalidate();
} finally {
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();
try {
setOpacity(1.0f);
invalidate();
} finally {
endTransaction();
}
return true;
}
private void setOpacity(float opacity) {
mOpacity = opacity;
Paint foregroundPaint = new Paint();
foregroundPaint.setAntiAlias(true);
foregroundPaint.setStyle(Paint.Style.FILL);
foregroundPaint.setColor(SCROLLBAR_COLOR);
canvas.drawColor(Color.argb(0, 0, 0, 0), PorterDuff.Mode.CLEAR);
canvas.drawCircle(CAP_RADIUS, CAP_RADIUS, CAP_RADIUS, foregroundPaint);
// use a (a,r,g,b) color of (127,0,0,0), and multiply the alpha by mOpacity for fading
foregroundPaint.setColor(Color.argb((int)Math.round(mOpacity * 127), 0, 0, 0));
ByteBuffer buffer = ByteBuffer.allocateDirect(imageSize * imageSize * 4);
bitmap.copyPixelsToBuffer(buffer.asIntBuffer());
CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize, CairoImage.FORMAT_ARGB32);
mCanvas.drawColor(Color.argb(0, 0, 0, 0), PorterDuff.Mode.CLEAR);
mCanvas.drawCircle(CAP_RADIUS, CAP_RADIUS, CAP_RADIUS, foregroundPaint);
return new ScrollbarLayer(image, vertical);
mBitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
}
@Override