Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | package org.mozilla.gecko.gfx; |
michael@0 | 7 | |
michael@0 | 8 | import org.mozilla.gecko.util.FloatUtils; |
michael@0 | 9 | |
michael@0 | 10 | import android.graphics.Bitmap; |
michael@0 | 11 | import android.graphics.Rect; |
michael@0 | 12 | import android.graphics.RectF; |
michael@0 | 13 | import android.opengl.GLES20; |
michael@0 | 14 | |
michael@0 | 15 | import java.nio.FloatBuffer; |
michael@0 | 16 | |
michael@0 | 17 | public class ScrollbarLayer extends TileLayer { |
michael@0 | 18 | public static final long FADE_DELAY = 500; // milliseconds before fade-out starts |
michael@0 | 19 | private static final float FADE_AMOUNT = 0.03f; // how much (as a percent) the scrollbar should fade per frame |
michael@0 | 20 | |
michael@0 | 21 | private final boolean mVertical; |
michael@0 | 22 | private float mOpacity; |
michael@0 | 23 | |
michael@0 | 24 | // To avoid excessive GC, declare some objects here that would otherwise |
michael@0 | 25 | // be created and destroyed frequently during draw(). |
michael@0 | 26 | private final RectF mBarRectF; |
michael@0 | 27 | private final Rect mBarRect; |
michael@0 | 28 | private final float[] mCoords; |
michael@0 | 29 | private final RectF mCapRectF; |
michael@0 | 30 | |
michael@0 | 31 | private LayerRenderer mRenderer; |
michael@0 | 32 | private int mProgram; |
michael@0 | 33 | private int mPositionHandle; |
michael@0 | 34 | private int mTextureHandle; |
michael@0 | 35 | private int mSampleHandle; |
michael@0 | 36 | private int mTMatrixHandle; |
michael@0 | 37 | private int mOpacityHandle; |
michael@0 | 38 | |
michael@0 | 39 | // Fragment shader used to draw the scroll-bar with opacity |
michael@0 | 40 | private static final String FRAGMENT_SHADER = |
michael@0 | 41 | "precision mediump float;\n" + |
michael@0 | 42 | "varying vec2 vTexCoord;\n" + |
michael@0 | 43 | "uniform sampler2D sTexture;\n" + |
michael@0 | 44 | "uniform float uOpacity;\n" + |
michael@0 | 45 | "void main() {\n" + |
michael@0 | 46 | " gl_FragColor = texture2D(sTexture, vTexCoord);\n" + |
michael@0 | 47 | " gl_FragColor.a *= uOpacity;\n" + |
michael@0 | 48 | "}\n"; |
michael@0 | 49 | |
michael@0 | 50 | // Dimensions of the texture bitmap (will always be power-of-two) |
michael@0 | 51 | private final int mTexWidth; |
michael@0 | 52 | private final int mTexHeight; |
michael@0 | 53 | // Some useful dimensions of the actual content in the bitmap |
michael@0 | 54 | private final int mBarWidth; |
michael@0 | 55 | private final int mCapLength; |
michael@0 | 56 | |
michael@0 | 57 | private final Rect mStartCapTexCoords; // top/left endcap coordinates |
michael@0 | 58 | private final Rect mBodyTexCoords; // 1-pixel slice of the texture to be stretched |
michael@0 | 59 | private final Rect mEndCapTexCoords; // bottom/right endcap coordinates |
michael@0 | 60 | |
michael@0 | 61 | ScrollbarLayer(LayerRenderer renderer, Bitmap scrollbarImage, IntSize imageSize, boolean vertical) { |
michael@0 | 62 | super(new BufferedCairoImage(scrollbarImage), TileLayer.PaintMode.NORMAL); |
michael@0 | 63 | mRenderer = renderer; |
michael@0 | 64 | mVertical = vertical; |
michael@0 | 65 | |
michael@0 | 66 | mBarRectF = new RectF(); |
michael@0 | 67 | mBarRect = new Rect(); |
michael@0 | 68 | mCoords = new float[20]; |
michael@0 | 69 | mCapRectF = new RectF(); |
michael@0 | 70 | |
michael@0 | 71 | mTexHeight = scrollbarImage.getHeight(); |
michael@0 | 72 | mTexWidth = scrollbarImage.getWidth(); |
michael@0 | 73 | |
michael@0 | 74 | if (mVertical) { |
michael@0 | 75 | mBarWidth = imageSize.width; |
michael@0 | 76 | mCapLength = imageSize.height / 2; |
michael@0 | 77 | mStartCapTexCoords = new Rect(0, mTexHeight - mCapLength, imageSize.width, mTexHeight); |
michael@0 | 78 | mBodyTexCoords = new Rect(0, mTexHeight - (mCapLength + 1), imageSize.width, mTexHeight - mCapLength); |
michael@0 | 79 | mEndCapTexCoords = new Rect(0, mTexHeight - imageSize.height, imageSize.width, mTexHeight - (mCapLength + 1)); |
michael@0 | 80 | } else { |
michael@0 | 81 | mBarWidth = imageSize.height; |
michael@0 | 82 | mCapLength = imageSize.width / 2; |
michael@0 | 83 | mStartCapTexCoords = new Rect(0, mTexHeight - imageSize.height, mCapLength, mTexHeight); |
michael@0 | 84 | mBodyTexCoords = new Rect(mCapLength, mTexHeight - imageSize.height, mCapLength + 1, mTexHeight); |
michael@0 | 85 | mEndCapTexCoords = new Rect(mCapLength + 1, mTexHeight - imageSize.height, imageSize.width, mTexHeight); |
michael@0 | 86 | } |
michael@0 | 87 | } |
michael@0 | 88 | |
michael@0 | 89 | private void createProgram() { |
michael@0 | 90 | int vertexShader = LayerRenderer.loadShader(GLES20.GL_VERTEX_SHADER, |
michael@0 | 91 | LayerRenderer.DEFAULT_VERTEX_SHADER); |
michael@0 | 92 | int fragmentShader = LayerRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, |
michael@0 | 93 | FRAGMENT_SHADER); |
michael@0 | 94 | |
michael@0 | 95 | mProgram = GLES20.glCreateProgram(); |
michael@0 | 96 | GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program |
michael@0 | 97 | GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program |
michael@0 | 98 | GLES20.glLinkProgram(mProgram); // creates OpenGL program executables |
michael@0 | 99 | |
michael@0 | 100 | // Get handles to the shaders' vPosition, aTexCoord, sTexture, and uTMatrix members. |
michael@0 | 101 | mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); |
michael@0 | 102 | mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord"); |
michael@0 | 103 | mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture"); |
michael@0 | 104 | mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix"); |
michael@0 | 105 | mOpacityHandle = GLES20.glGetUniformLocation(mProgram, "uOpacity"); |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | private void activateProgram() { |
michael@0 | 109 | // Add the program to the OpenGL environment |
michael@0 | 110 | GLES20.glUseProgram(mProgram); |
michael@0 | 111 | |
michael@0 | 112 | // Set the transformation matrix |
michael@0 | 113 | GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, |
michael@0 | 114 | LayerRenderer.DEFAULT_TEXTURE_MATRIX, 0); |
michael@0 | 115 | |
michael@0 | 116 | // Enable the arrays from which we get the vertex and texture coordinates |
michael@0 | 117 | GLES20.glEnableVertexAttribArray(mPositionHandle); |
michael@0 | 118 | GLES20.glEnableVertexAttribArray(mTextureHandle); |
michael@0 | 119 | |
michael@0 | 120 | GLES20.glUniform1i(mSampleHandle, 0); |
michael@0 | 121 | GLES20.glUniform1f(mOpacityHandle, mOpacity); |
michael@0 | 122 | } |
michael@0 | 123 | |
michael@0 | 124 | private void deactivateProgram() { |
michael@0 | 125 | GLES20.glDisableVertexAttribArray(mTextureHandle); |
michael@0 | 126 | GLES20.glDisableVertexAttribArray(mPositionHandle); |
michael@0 | 127 | GLES20.glUseProgram(0); |
michael@0 | 128 | } |
michael@0 | 129 | |
michael@0 | 130 | /** |
michael@0 | 131 | * Decrease the opacity of the scrollbar by one frame's worth. |
michael@0 | 132 | * Return true if the opacity was decreased, or false if the scrollbars |
michael@0 | 133 | * are already fully faded out. |
michael@0 | 134 | */ |
michael@0 | 135 | public boolean fade() { |
michael@0 | 136 | if (FloatUtils.fuzzyEquals(mOpacity, 0.0f)) { |
michael@0 | 137 | return false; |
michael@0 | 138 | } |
michael@0 | 139 | beginTransaction(); // called on compositor thread |
michael@0 | 140 | mOpacity = Math.max(mOpacity - FADE_AMOUNT, 0.0f); |
michael@0 | 141 | endTransaction(); |
michael@0 | 142 | return true; |
michael@0 | 143 | } |
michael@0 | 144 | |
michael@0 | 145 | /** |
michael@0 | 146 | * Restore the opacity of the scrollbar to fully opaque. |
michael@0 | 147 | * Return true if the opacity was changed, or false if the scrollbars |
michael@0 | 148 | * are already fully opaque. |
michael@0 | 149 | */ |
michael@0 | 150 | public boolean unfade() { |
michael@0 | 151 | if (FloatUtils.fuzzyEquals(mOpacity, 1.0f)) { |
michael@0 | 152 | return false; |
michael@0 | 153 | } |
michael@0 | 154 | beginTransaction(); // called on compositor thread |
michael@0 | 155 | mOpacity = 1.0f; |
michael@0 | 156 | endTransaction(); |
michael@0 | 157 | return true; |
michael@0 | 158 | } |
michael@0 | 159 | |
michael@0 | 160 | @Override |
michael@0 | 161 | public void draw(RenderContext context) { |
michael@0 | 162 | if (!initialized()) |
michael@0 | 163 | return; |
michael@0 | 164 | |
michael@0 | 165 | // Create the shader program, if necessary |
michael@0 | 166 | if (mProgram == 0) { |
michael@0 | 167 | createProgram(); |
michael@0 | 168 | } |
michael@0 | 169 | |
michael@0 | 170 | // Enable the shader program |
michael@0 | 171 | mRenderer.deactivateDefaultProgram(); |
michael@0 | 172 | activateProgram(); |
michael@0 | 173 | |
michael@0 | 174 | GLES20.glEnable(GLES20.GL_BLEND); |
michael@0 | 175 | GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); |
michael@0 | 176 | |
michael@0 | 177 | if (mVertical) { |
michael@0 | 178 | getVerticalRect(context, mBarRectF); |
michael@0 | 179 | } else { |
michael@0 | 180 | getHorizontalRect(context, mBarRectF); |
michael@0 | 181 | } |
michael@0 | 182 | RectUtils.round(mBarRectF, mBarRect); |
michael@0 | 183 | |
michael@0 | 184 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); |
michael@0 | 185 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID()); |
michael@0 | 186 | |
michael@0 | 187 | float viewWidth = context.viewport.width(); |
michael@0 | 188 | float viewHeight = context.viewport.height(); |
michael@0 | 189 | |
michael@0 | 190 | mBarRectF.set(mBarRect.left, viewHeight - mBarRect.top, mBarRect.right, viewHeight - mBarRect.bottom); |
michael@0 | 191 | mBarRectF.offset(context.offset.x, -context.offset.y); |
michael@0 | 192 | |
michael@0 | 193 | // We take a 1-pixel slice from the center of the image and scale it to become the bar |
michael@0 | 194 | fillRectCoordBuffer(mCoords, mBarRectF, viewWidth, viewHeight, mBodyTexCoords, mTexWidth, mTexHeight); |
michael@0 | 195 | |
michael@0 | 196 | // Get the buffer and handles from the context |
michael@0 | 197 | FloatBuffer coordBuffer = context.coordBuffer; |
michael@0 | 198 | int positionHandle = mPositionHandle; |
michael@0 | 199 | int textureHandle = mTextureHandle; |
michael@0 | 200 | |
michael@0 | 201 | // Make sure we are at position zero in the buffer in case other draw methods did not |
michael@0 | 202 | // clean up after themselves |
michael@0 | 203 | coordBuffer.position(0); |
michael@0 | 204 | coordBuffer.put(mCoords); |
michael@0 | 205 | |
michael@0 | 206 | // Unbind any the current array buffer so we can use client side buffers |
michael@0 | 207 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); |
michael@0 | 208 | |
michael@0 | 209 | // Vertex coordinates are x,y,z starting at position 0 into the buffer. |
michael@0 | 210 | coordBuffer.position(0); |
michael@0 | 211 | GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer); |
michael@0 | 212 | |
michael@0 | 213 | // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer. |
michael@0 | 214 | coordBuffer.position(3); |
michael@0 | 215 | GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer); |
michael@0 | 216 | |
michael@0 | 217 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); |
michael@0 | 218 | |
michael@0 | 219 | // Reset the position in the buffer for the next set of vertex and texture coordinates. |
michael@0 | 220 | coordBuffer.position(0); |
michael@0 | 221 | if (mVertical) { |
michael@0 | 222 | // top endcap |
michael@0 | 223 | mCapRectF.set(mBarRectF.left, mBarRectF.top + mCapLength, mBarRectF.right, mBarRectF.top); |
michael@0 | 224 | } else { |
michael@0 | 225 | // left endcap |
michael@0 | 226 | mCapRectF.set(mBarRectF.left - mCapLength, mBarRectF.bottom + mBarWidth, mBarRectF.left, mBarRectF.bottom); |
michael@0 | 227 | } |
michael@0 | 228 | |
michael@0 | 229 | fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, mStartCapTexCoords, mTexWidth, mTexHeight); |
michael@0 | 230 | coordBuffer.put(mCoords); |
michael@0 | 231 | |
michael@0 | 232 | // Vertex coordinates are x,y,z starting at position 0 into the buffer. |
michael@0 | 233 | coordBuffer.position(0); |
michael@0 | 234 | GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer); |
michael@0 | 235 | |
michael@0 | 236 | // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer. |
michael@0 | 237 | coordBuffer.position(3); |
michael@0 | 238 | GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer); |
michael@0 | 239 | |
michael@0 | 240 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); |
michael@0 | 241 | |
michael@0 | 242 | // Reset the position in the buffer for the next set of vertex and texture coordinates. |
michael@0 | 243 | coordBuffer.position(0); |
michael@0 | 244 | if (mVertical) { |
michael@0 | 245 | // bottom endcap |
michael@0 | 246 | mCapRectF.set(mBarRectF.left, mBarRectF.bottom, mBarRectF.right, mBarRectF.bottom - mCapLength); |
michael@0 | 247 | } else { |
michael@0 | 248 | // right endcap |
michael@0 | 249 | mCapRectF.set(mBarRectF.right, mBarRectF.bottom + mBarWidth, mBarRectF.right + mCapLength, mBarRectF.bottom); |
michael@0 | 250 | } |
michael@0 | 251 | fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, mEndCapTexCoords, mTexWidth, mTexHeight); |
michael@0 | 252 | coordBuffer.put(mCoords); |
michael@0 | 253 | |
michael@0 | 254 | // Vertex coordinates are x,y,z starting at position 0 into the buffer. |
michael@0 | 255 | coordBuffer.position(0); |
michael@0 | 256 | GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer); |
michael@0 | 257 | |
michael@0 | 258 | // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer. |
michael@0 | 259 | coordBuffer.position(3); |
michael@0 | 260 | GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer); |
michael@0 | 261 | |
michael@0 | 262 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); |
michael@0 | 263 | |
michael@0 | 264 | // Reset the position in the buffer for the next set of vertex and texture coordinates. |
michael@0 | 265 | coordBuffer.position(0); |
michael@0 | 266 | |
michael@0 | 267 | // Enable the default shader program again |
michael@0 | 268 | deactivateProgram(); |
michael@0 | 269 | mRenderer.activateDefaultProgram(); |
michael@0 | 270 | } |
michael@0 | 271 | |
michael@0 | 272 | private void getVerticalRect(RenderContext context, RectF dest) { |
michael@0 | 273 | RectF viewport = context.viewport; |
michael@0 | 274 | RectF pageRect = context.pageRect; |
michael@0 | 275 | float viewportHeight = viewport.height() - context.offset.y; |
michael@0 | 276 | float barStart = ((viewport.top - context.offset.y - pageRect.top) * (viewportHeight / pageRect.height())) + mCapLength; |
michael@0 | 277 | float barEnd = ((viewport.bottom - context.offset.y - pageRect.top) * (viewportHeight / pageRect.height())) - mCapLength; |
michael@0 | 278 | if (barStart > barEnd) { |
michael@0 | 279 | float middle = (barStart + barEnd) / 2.0f; |
michael@0 | 280 | barStart = barEnd = middle; |
michael@0 | 281 | } |
michael@0 | 282 | dest.set(viewport.width() - mBarWidth, barStart, viewport.width(), barEnd); |
michael@0 | 283 | } |
michael@0 | 284 | |
michael@0 | 285 | private void getHorizontalRect(RenderContext context, RectF dest) { |
michael@0 | 286 | RectF viewport = context.viewport; |
michael@0 | 287 | RectF pageRect = context.pageRect; |
michael@0 | 288 | float viewportWidth = viewport.width() - context.offset.x; |
michael@0 | 289 | float barStart = ((viewport.left - context.offset.x - pageRect.left) * (viewport.width() / pageRect.width())) + mCapLength; |
michael@0 | 290 | float barEnd = ((viewport.right - context.offset.x - pageRect.left) * (viewport.width() / pageRect.width())) - mCapLength; |
michael@0 | 291 | if (barStart > barEnd) { |
michael@0 | 292 | float middle = (barStart + barEnd) / 2.0f; |
michael@0 | 293 | barStart = barEnd = middle; |
michael@0 | 294 | } |
michael@0 | 295 | dest.set(barStart, viewport.height() - mBarWidth, barEnd, viewport.height()); |
michael@0 | 296 | } |
michael@0 | 297 | } |