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