Bug 771746 - Draw scrollbars using a white outline so it shows up on dark backgrounds. r=Cwiiis
authorKartikaya Gupta <kgupta@mozilla.com>
Sat, 29 Dec 2012 15:24:01 -0500
changeset 126330 d462bf431ae4725d865b9504249fbff87918716a
parent 126329 97542ba6b0aa408d7918bea31c401c6db457ab47
child 126331 0acf066babea238a36137ca51bfb4b55ffc10d85
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCwiiis
bugs771746
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 771746 - Draw scrollbars using a white outline so it shows up on dark backgrounds. r=Cwiiis
mobile/android/base/Makefile.in
mobile/android/base/gfx/LayerRenderer.java
mobile/android/base/gfx/LayerView.java
mobile/android/base/gfx/ScrollbarLayer.java
mobile/android/base/resources/drawable/scrollbar.png
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -567,16 +567,17 @@ RES_DRAWABLE_BASE = \
   res/drawable/reader.png \
   res/drawable/reading_list.png \
   res/drawable/validation_arrow.png \
   res/drawable/validation_arrow_inverted.png \
   res/drawable/validation_bg.9.png \
   res/drawable/handle_end.png \
   res/drawable/handle_middle.png \
   res/drawable/handle_start.png \
+  res/drawable/scrollbar.png \
   $(addprefix res/drawable-mdpi/,$(notdir $(SYNC_RES_DRAWABLE_MDPI))) \
   $(NULL)
 
 RES_DRAWABLE_LDPI = \
   $(addprefix res/drawable-ldpi/,$(notdir $(SYNC_RES_DRAWABLE_LDPI))) \
   $(NULL)
 
 RES_DRAWABLE_HDPI = \
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -8,17 +8,20 @@ package org.mozilla.gecko.gfx;
 import org.mozilla.gecko.GeckoAppShell;
 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.content.Context;
 import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.graphics.RegionIterator;
 import android.opengl.GLES20;
 import android.os.SystemClock;
 import android.util.Log;
@@ -129,32 +132,53 @@ public class LayerRenderer implements Ta
         mView = view;
 
         CairoImage backgroundImage = new BufferedCairoImage(view.getBackgroundPattern());
         mBackgroundLayer = new SingleTileLayer(true, backgroundImage);
 
         CairoImage shadowImage = new BufferedCairoImage(view.getShadowPattern());
         mShadowLayer = new NinePatchTileLayer(shadowImage);
 
-        mHorizScrollLayer = ScrollbarLayer.create(this, false);
-        mVertScrollLayer = ScrollbarLayer.create(this, true);
+        Bitmap scrollbarImage = view.getScrollbarImage();
+        IntSize size = new IntSize(scrollbarImage.getWidth(), scrollbarImage.getHeight());
+        scrollbarImage = expandCanvasToPowerOfTwo(scrollbarImage, size);
+        mVertScrollLayer = new ScrollbarLayer(this, scrollbarImage, size, true);
+        mHorizScrollLayer = new ScrollbarLayer(this, diagonalFlip(scrollbarImage), new IntSize(size.height, size.width), false);
         mFadeRunnable = new FadeRunnable();
 
         mFrameTimings = new int[60];
         mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0;
 
         // Initialize the FloatBuffer that will be used to store all vertices and texture
         // coordinates in draw() commands.
         mCoordByteBuffer = DirectBufferAllocator.allocate(COORD_BUFFER_SIZE * 4);
         mCoordByteBuffer.order(ByteOrder.nativeOrder());
         mCoordBuffer = mCoordByteBuffer.asFloatBuffer();
 
         Tabs.registerOnTabsChangedListener(this);
     }
 
+    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() {
         DirectBufferAllocator.free(mCoordByteBuffer);
         mCoordByteBuffer = null;
         mCoordBuffer = null;
         mBackgroundLayer.destroy();
         mShadowLayer.destroy();
         mHorizScrollLayer.destroy();
         mVertScrollLayer.destroy();
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -314,16 +314,20 @@ public class LayerView extends FrameLayo
     Bitmap getBackgroundPattern() {
         return getDrawable(R.drawable.abouthome_bg);
     }
 
     Bitmap getShadowPattern() {
         return getDrawable(R.drawable.shadow);
     }
 
+    Bitmap getScrollbarImage() {
+        return getDrawable(R.drawable.scrollbar);
+    }
+
     private void onSizeChanged(int width, int height) {
         mGLController.surfaceChanged(width, height);
 
         if (mListener != null) {
             mListener.surfaceChanged(width, height);
         }
     }
 
--- a/mobile/android/base/gfx/ScrollbarLayer.java
+++ b/mobile/android/base/gfx/ScrollbarLayer.java
@@ -15,27 +15,20 @@ import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.opengl.GLES20;
 
 import java.nio.ByteBuffer;
 import java.nio.FloatBuffer;
 
-/**
- * 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 final boolean mVertical;
     private float mOpacity;
 
     // 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;
@@ -55,60 +48,53 @@ public class ScrollbarLayer extends Tile
         "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 image
-    private static final int TEX_HEIGHT = 8;
-    private static final int TEX_WIDTH = 8;
-
-    private static final Rect BODY_TEX_COORDS = new Rect(CAP_RADIUS, CAP_RADIUS, CAP_RADIUS + 1, CAP_RADIUS + 1);
-    private static final Rect LEFT_CAP_TEX_COORDS = new Rect(0, TEX_HEIGHT - BAR_SIZE, CAP_RADIUS, TEX_HEIGHT);
-    private static final Rect TOP_CAP_TEX_COORDS = new Rect(0, TEX_HEIGHT - CAP_RADIUS, BAR_SIZE, TEX_HEIGHT);
-    private static final Rect RIGHT_CAP_TEX_COORDS = new Rect(CAP_RADIUS, TEX_HEIGHT - BAR_SIZE, BAR_SIZE, TEX_HEIGHT);
-    private static final Rect BOT_CAP_TEX_COORDS = new Rect(0, TEX_HEIGHT - BAR_SIZE, BAR_SIZE, TEX_HEIGHT - CAP_RADIUS);
-
-    private ScrollbarLayer(LayerRenderer renderer, CairoImage image, boolean vertical, ByteBuffer buffer) {
-        super(image, TileLayer.PaintMode.NORMAL);
-        mVertical = vertical;
-        mRenderer = renderer;
+    // 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;
 
-        IntSize size = image.getSize();
-        Bitmap bitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(bitmap);
+    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
 
-        // Paint a spot to use as the scroll indicator
-        Paint foregroundPaint = new Paint();
-        foregroundPaint.setAntiAlias(true);
-        foregroundPaint.setStyle(Paint.Style.FILL);
-        foregroundPaint.setColor(Color.argb(127, 0, 0, 0));
-
-        canvas.drawColor(Color.argb(0, 0, 0, 0), PorterDuff.Mode.CLEAR);
-        canvas.drawCircle(CAP_RADIUS, CAP_RADIUS, CAP_RADIUS, foregroundPaint);
-
-        bitmap.copyPixelsToBuffer(buffer.asIntBuffer());
+    ScrollbarLayer(LayerRenderer renderer, Bitmap scrollbarImage, IntSize imageSize, boolean vertical) {
+        super(new BufferedCairoImage(scrollbarImage), TileLayer.PaintMode.NORMAL);
+        mRenderer = renderer;
+        mVertical = vertical;
 
         mBarRectF = new RectF();
         mBarRect = new Rect();
         mCoords = new float[20];
         mCapRectF = new RectF();
-    }
+
+        mTexHeight = scrollbarImage.getHeight();
+        mTexWidth = scrollbarImage.getWidth();
 
-    public static ScrollbarLayer create(LayerRenderer renderer, boolean vertical) {
-        // just create an empty image for now, it will get drawn
-        // on demand anyway
-        int imageSize = IntSize.nextPowerOfTwo(BAR_SIZE);
-        ByteBuffer buffer = DirectBufferAllocator.allocate(imageSize * imageSize * 4);
-        CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize,
-                                                  CairoImage.FORMAT_ARGB32);
-        return new ScrollbarLayer(renderer, image, vertical, buffer);
+        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);
 
@@ -204,18 +190,18 @@ public class ScrollbarLayer extends Tile
         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 1x1 pixel from the center of the image and scale it to become the bar
-        fillRectCoordBuffer(mCoords, mBarRectF, viewWidth, viewHeight, BODY_TEX_COORDS, TEX_WIDTH, TEX_HEIGHT);
+        // 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
@@ -234,45 +220,45 @@ public class ScrollbarLayer extends Tile
         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 + CAP_RADIUS, mBarRectF.left + BAR_SIZE, mBarRectF.top);
-            fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, TOP_CAP_TEX_COORDS, TEX_WIDTH, TEX_HEIGHT);
+            mCapRectF.set(mBarRectF.left, mBarRectF.top + mCapLength, mBarRectF.right, mBarRectF.top);
         } else {
-            mCapRectF.set(mBarRectF.left - CAP_RADIUS, mBarRectF.bottom + BAR_SIZE, mBarRectF.left, mBarRectF.bottom);
-            fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, LEFT_CAP_TEX_COORDS, TEX_WIDTH, TEX_HEIGHT);
+            // 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.left + BAR_SIZE, mBarRectF.bottom - CAP_RADIUS);
-            fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, BOT_CAP_TEX_COORDS, TEX_WIDTH, TEX_HEIGHT);
+            mCapRectF.set(mBarRectF.left, mBarRectF.bottom, mBarRectF.right, mBarRectF.bottom - mCapLength);
         } else {
             // right endcap
-            mCapRectF.set(mBarRectF.right, mBarRectF.bottom + BAR_SIZE, mBarRectF.right + CAP_RADIUS, mBarRectF.bottom);
-            fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, RIGHT_CAP_TEX_COORDS, TEX_WIDTH, TEX_HEIGHT);
+            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);
@@ -286,31 +272,29 @@ public class ScrollbarLayer extends Tile
         // 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 barStart = ((viewport.top - pageRect.top) * (viewport.height() / pageRect.height())) + CAP_RADIUS;
-        float barEnd = ((viewport.bottom - pageRect.top) * (viewport.height() / pageRect.height())) - CAP_RADIUS;
+        float barStart = ((viewport.top - pageRect.top) * (viewport.height() / pageRect.height())) + mCapLength;
+        float barEnd = ((viewport.bottom - pageRect.top) * (viewport.height() / pageRect.height())) - mCapLength;
         if (barStart > barEnd) {
             float middle = (barStart + barEnd) / 2.0f;
             barStart = barEnd = middle;
         }
-        float right = viewport.width() - PADDING;
-        dest.set(right - BAR_SIZE, barStart, right, barEnd);
+        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 barStart = ((viewport.left - pageRect.left) * (viewport.width() / pageRect.width())) + CAP_RADIUS;
-        float barEnd = ((viewport.right - pageRect.left) * (viewport.width() / pageRect.width())) - CAP_RADIUS;
+        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;
         }
-        float bottom = viewport.height() - PADDING;
-        dest.set(barStart, bottom - BAR_SIZE, barEnd, bottom);
+        dest.set(barStart, viewport.height() - mBarWidth, barEnd, viewport.height());
     }
 }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a34fcae627fcaa143397349c1e17f503a42f4087
GIT binary patch
literal 337
zc%17D@N?(olHy`uVBq!ia0vp^96-#?!2%>-Ok!CFq?nSt-CY>|xA&jf59DzcctjQh
zX%8@VJDF_<WaN3eIEHXs&pm6%caVX>?V-P_P^xCLwBc^A^-7Lw*u*!?j9KnHJ7D$w
z3oAR#_dVR}*|745Q{yGozuSw0l!|K7rsv9?R8(7dH=I}Ndw*)<Dn{Avg@%vTXP76P
z%@(+R>z3Eq+#BbLPFUBTo5_D$&|cuN*70S5w=97+tCqM%l%yn<q*^5xr2;7iBLhPt
zT_7?r2{AOZGBB|+GSxLOwK6c!RCZxT(U6;;l9^VC&|n;5Xl`X_U}a<s(Xj6N#OXi{
wk{}Ji`DrEPiAAXljw$&`sS2LCiRr09sfj6-g(p*OfQlGAUHx3vIVCg!0Qs+N%K!iX