1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/gfx/ScrollbarLayer.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,297 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.gfx; 1.10 + 1.11 +import org.mozilla.gecko.util.FloatUtils; 1.12 + 1.13 +import android.graphics.Bitmap; 1.14 +import android.graphics.Rect; 1.15 +import android.graphics.RectF; 1.16 +import android.opengl.GLES20; 1.17 + 1.18 +import java.nio.FloatBuffer; 1.19 + 1.20 +public class ScrollbarLayer extends TileLayer { 1.21 + public static final long FADE_DELAY = 500; // milliseconds before fade-out starts 1.22 + private static final float FADE_AMOUNT = 0.03f; // how much (as a percent) the scrollbar should fade per frame 1.23 + 1.24 + private final boolean mVertical; 1.25 + private float mOpacity; 1.26 + 1.27 + // To avoid excessive GC, declare some objects here that would otherwise 1.28 + // be created and destroyed frequently during draw(). 1.29 + private final RectF mBarRectF; 1.30 + private final Rect mBarRect; 1.31 + private final float[] mCoords; 1.32 + private final RectF mCapRectF; 1.33 + 1.34 + private LayerRenderer mRenderer; 1.35 + private int mProgram; 1.36 + private int mPositionHandle; 1.37 + private int mTextureHandle; 1.38 + private int mSampleHandle; 1.39 + private int mTMatrixHandle; 1.40 + private int mOpacityHandle; 1.41 + 1.42 + // Fragment shader used to draw the scroll-bar with opacity 1.43 + private static final String FRAGMENT_SHADER = 1.44 + "precision mediump float;\n" + 1.45 + "varying vec2 vTexCoord;\n" + 1.46 + "uniform sampler2D sTexture;\n" + 1.47 + "uniform float uOpacity;\n" + 1.48 + "void main() {\n" + 1.49 + " gl_FragColor = texture2D(sTexture, vTexCoord);\n" + 1.50 + " gl_FragColor.a *= uOpacity;\n" + 1.51 + "}\n"; 1.52 + 1.53 + // Dimensions of the texture bitmap (will always be power-of-two) 1.54 + private final int mTexWidth; 1.55 + private final int mTexHeight; 1.56 + // Some useful dimensions of the actual content in the bitmap 1.57 + private final int mBarWidth; 1.58 + private final int mCapLength; 1.59 + 1.60 + private final Rect mStartCapTexCoords; // top/left endcap coordinates 1.61 + private final Rect mBodyTexCoords; // 1-pixel slice of the texture to be stretched 1.62 + private final Rect mEndCapTexCoords; // bottom/right endcap coordinates 1.63 + 1.64 + ScrollbarLayer(LayerRenderer renderer, Bitmap scrollbarImage, IntSize imageSize, boolean vertical) { 1.65 + super(new BufferedCairoImage(scrollbarImage), TileLayer.PaintMode.NORMAL); 1.66 + mRenderer = renderer; 1.67 + mVertical = vertical; 1.68 + 1.69 + mBarRectF = new RectF(); 1.70 + mBarRect = new Rect(); 1.71 + mCoords = new float[20]; 1.72 + mCapRectF = new RectF(); 1.73 + 1.74 + mTexHeight = scrollbarImage.getHeight(); 1.75 + mTexWidth = scrollbarImage.getWidth(); 1.76 + 1.77 + if (mVertical) { 1.78 + mBarWidth = imageSize.width; 1.79 + mCapLength = imageSize.height / 2; 1.80 + mStartCapTexCoords = new Rect(0, mTexHeight - mCapLength, imageSize.width, mTexHeight); 1.81 + mBodyTexCoords = new Rect(0, mTexHeight - (mCapLength + 1), imageSize.width, mTexHeight - mCapLength); 1.82 + mEndCapTexCoords = new Rect(0, mTexHeight - imageSize.height, imageSize.width, mTexHeight - (mCapLength + 1)); 1.83 + } else { 1.84 + mBarWidth = imageSize.height; 1.85 + mCapLength = imageSize.width / 2; 1.86 + mStartCapTexCoords = new Rect(0, mTexHeight - imageSize.height, mCapLength, mTexHeight); 1.87 + mBodyTexCoords = new Rect(mCapLength, mTexHeight - imageSize.height, mCapLength + 1, mTexHeight); 1.88 + mEndCapTexCoords = new Rect(mCapLength + 1, mTexHeight - imageSize.height, imageSize.width, mTexHeight); 1.89 + } 1.90 + } 1.91 + 1.92 + private void createProgram() { 1.93 + int vertexShader = LayerRenderer.loadShader(GLES20.GL_VERTEX_SHADER, 1.94 + LayerRenderer.DEFAULT_VERTEX_SHADER); 1.95 + int fragmentShader = LayerRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, 1.96 + FRAGMENT_SHADER); 1.97 + 1.98 + mProgram = GLES20.glCreateProgram(); 1.99 + GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program 1.100 + GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program 1.101 + GLES20.glLinkProgram(mProgram); // creates OpenGL program executables 1.102 + 1.103 + // Get handles to the shaders' vPosition, aTexCoord, sTexture, and uTMatrix members. 1.104 + mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); 1.105 + mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord"); 1.106 + mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture"); 1.107 + mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix"); 1.108 + mOpacityHandle = GLES20.glGetUniformLocation(mProgram, "uOpacity"); 1.109 + } 1.110 + 1.111 + private void activateProgram() { 1.112 + // Add the program to the OpenGL environment 1.113 + GLES20.glUseProgram(mProgram); 1.114 + 1.115 + // Set the transformation matrix 1.116 + GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, 1.117 + LayerRenderer.DEFAULT_TEXTURE_MATRIX, 0); 1.118 + 1.119 + // Enable the arrays from which we get the vertex and texture coordinates 1.120 + GLES20.glEnableVertexAttribArray(mPositionHandle); 1.121 + GLES20.glEnableVertexAttribArray(mTextureHandle); 1.122 + 1.123 + GLES20.glUniform1i(mSampleHandle, 0); 1.124 + GLES20.glUniform1f(mOpacityHandle, mOpacity); 1.125 + } 1.126 + 1.127 + private void deactivateProgram() { 1.128 + GLES20.glDisableVertexAttribArray(mTextureHandle); 1.129 + GLES20.glDisableVertexAttribArray(mPositionHandle); 1.130 + GLES20.glUseProgram(0); 1.131 + } 1.132 + 1.133 + /** 1.134 + * Decrease the opacity of the scrollbar by one frame's worth. 1.135 + * Return true if the opacity was decreased, or false if the scrollbars 1.136 + * are already fully faded out. 1.137 + */ 1.138 + public boolean fade() { 1.139 + if (FloatUtils.fuzzyEquals(mOpacity, 0.0f)) { 1.140 + return false; 1.141 + } 1.142 + beginTransaction(); // called on compositor thread 1.143 + mOpacity = Math.max(mOpacity - FADE_AMOUNT, 0.0f); 1.144 + endTransaction(); 1.145 + return true; 1.146 + } 1.147 + 1.148 + /** 1.149 + * Restore the opacity of the scrollbar to fully opaque. 1.150 + * Return true if the opacity was changed, or false if the scrollbars 1.151 + * are already fully opaque. 1.152 + */ 1.153 + public boolean unfade() { 1.154 + if (FloatUtils.fuzzyEquals(mOpacity, 1.0f)) { 1.155 + return false; 1.156 + } 1.157 + beginTransaction(); // called on compositor thread 1.158 + mOpacity = 1.0f; 1.159 + endTransaction(); 1.160 + return true; 1.161 + } 1.162 + 1.163 + @Override 1.164 + public void draw(RenderContext context) { 1.165 + if (!initialized()) 1.166 + return; 1.167 + 1.168 + // Create the shader program, if necessary 1.169 + if (mProgram == 0) { 1.170 + createProgram(); 1.171 + } 1.172 + 1.173 + // Enable the shader program 1.174 + mRenderer.deactivateDefaultProgram(); 1.175 + activateProgram(); 1.176 + 1.177 + GLES20.glEnable(GLES20.GL_BLEND); 1.178 + GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); 1.179 + 1.180 + if (mVertical) { 1.181 + getVerticalRect(context, mBarRectF); 1.182 + } else { 1.183 + getHorizontalRect(context, mBarRectF); 1.184 + } 1.185 + RectUtils.round(mBarRectF, mBarRect); 1.186 + 1.187 + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 1.188 + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID()); 1.189 + 1.190 + float viewWidth = context.viewport.width(); 1.191 + float viewHeight = context.viewport.height(); 1.192 + 1.193 + mBarRectF.set(mBarRect.left, viewHeight - mBarRect.top, mBarRect.right, viewHeight - mBarRect.bottom); 1.194 + mBarRectF.offset(context.offset.x, -context.offset.y); 1.195 + 1.196 + // We take a 1-pixel slice from the center of the image and scale it to become the bar 1.197 + fillRectCoordBuffer(mCoords, mBarRectF, viewWidth, viewHeight, mBodyTexCoords, mTexWidth, mTexHeight); 1.198 + 1.199 + // Get the buffer and handles from the context 1.200 + FloatBuffer coordBuffer = context.coordBuffer; 1.201 + int positionHandle = mPositionHandle; 1.202 + int textureHandle = mTextureHandle; 1.203 + 1.204 + // Make sure we are at position zero in the buffer in case other draw methods did not 1.205 + // clean up after themselves 1.206 + coordBuffer.position(0); 1.207 + coordBuffer.put(mCoords); 1.208 + 1.209 + // Unbind any the current array buffer so we can use client side buffers 1.210 + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); 1.211 + 1.212 + // Vertex coordinates are x,y,z starting at position 0 into the buffer. 1.213 + coordBuffer.position(0); 1.214 + GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer); 1.215 + 1.216 + // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer. 1.217 + coordBuffer.position(3); 1.218 + GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer); 1.219 + 1.220 + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 1.221 + 1.222 + // Reset the position in the buffer for the next set of vertex and texture coordinates. 1.223 + coordBuffer.position(0); 1.224 + if (mVertical) { 1.225 + // top endcap 1.226 + mCapRectF.set(mBarRectF.left, mBarRectF.top + mCapLength, mBarRectF.right, mBarRectF.top); 1.227 + } else { 1.228 + // left endcap 1.229 + mCapRectF.set(mBarRectF.left - mCapLength, mBarRectF.bottom + mBarWidth, mBarRectF.left, mBarRectF.bottom); 1.230 + } 1.231 + 1.232 + fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, mStartCapTexCoords, mTexWidth, mTexHeight); 1.233 + coordBuffer.put(mCoords); 1.234 + 1.235 + // Vertex coordinates are x,y,z starting at position 0 into the buffer. 1.236 + coordBuffer.position(0); 1.237 + GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer); 1.238 + 1.239 + // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer. 1.240 + coordBuffer.position(3); 1.241 + GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer); 1.242 + 1.243 + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 1.244 + 1.245 + // Reset the position in the buffer for the next set of vertex and texture coordinates. 1.246 + coordBuffer.position(0); 1.247 + if (mVertical) { 1.248 + // bottom endcap 1.249 + mCapRectF.set(mBarRectF.left, mBarRectF.bottom, mBarRectF.right, mBarRectF.bottom - mCapLength); 1.250 + } else { 1.251 + // right endcap 1.252 + mCapRectF.set(mBarRectF.right, mBarRectF.bottom + mBarWidth, mBarRectF.right + mCapLength, mBarRectF.bottom); 1.253 + } 1.254 + fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, mEndCapTexCoords, mTexWidth, mTexHeight); 1.255 + coordBuffer.put(mCoords); 1.256 + 1.257 + // Vertex coordinates are x,y,z starting at position 0 into the buffer. 1.258 + coordBuffer.position(0); 1.259 + GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer); 1.260 + 1.261 + // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer. 1.262 + coordBuffer.position(3); 1.263 + GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer); 1.264 + 1.265 + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 1.266 + 1.267 + // Reset the position in the buffer for the next set of vertex and texture coordinates. 1.268 + coordBuffer.position(0); 1.269 + 1.270 + // Enable the default shader program again 1.271 + deactivateProgram(); 1.272 + mRenderer.activateDefaultProgram(); 1.273 + } 1.274 + 1.275 + private void getVerticalRect(RenderContext context, RectF dest) { 1.276 + RectF viewport = context.viewport; 1.277 + RectF pageRect = context.pageRect; 1.278 + float viewportHeight = viewport.height() - context.offset.y; 1.279 + float barStart = ((viewport.top - context.offset.y - pageRect.top) * (viewportHeight / pageRect.height())) + mCapLength; 1.280 + float barEnd = ((viewport.bottom - context.offset.y - pageRect.top) * (viewportHeight / pageRect.height())) - mCapLength; 1.281 + if (barStart > barEnd) { 1.282 + float middle = (barStart + barEnd) / 2.0f; 1.283 + barStart = barEnd = middle; 1.284 + } 1.285 + dest.set(viewport.width() - mBarWidth, barStart, viewport.width(), barEnd); 1.286 + } 1.287 + 1.288 + private void getHorizontalRect(RenderContext context, RectF dest) { 1.289 + RectF viewport = context.viewport; 1.290 + RectF pageRect = context.pageRect; 1.291 + float viewportWidth = viewport.width() - context.offset.x; 1.292 + float barStart = ((viewport.left - context.offset.x - pageRect.left) * (viewport.width() / pageRect.width())) + mCapLength; 1.293 + float barEnd = ((viewport.right - context.offset.x - pageRect.left) * (viewport.width() / pageRect.width())) - mCapLength; 1.294 + if (barStart > barEnd) { 1.295 + float middle = (barStart + barEnd) / 2.0f; 1.296 + barStart = barEnd = middle; 1.297 + } 1.298 + dest.set(barStart, viewport.height() - mBarWidth, barEnd, viewport.height()); 1.299 + } 1.300 +}