michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.gfx; michael@0: michael@0: import org.mozilla.gecko.util.FloatUtils; michael@0: michael@0: import android.graphics.Bitmap; michael@0: import android.graphics.Rect; michael@0: import android.graphics.RectF; michael@0: import android.opengl.GLES20; michael@0: michael@0: import java.nio.FloatBuffer; michael@0: michael@0: public class ScrollbarLayer extends TileLayer { michael@0: public static final long FADE_DELAY = 500; // milliseconds before fade-out starts michael@0: private static final float FADE_AMOUNT = 0.03f; // how much (as a percent) the scrollbar should fade per frame michael@0: michael@0: private final boolean mVertical; michael@0: private float mOpacity; michael@0: michael@0: // To avoid excessive GC, declare some objects here that would otherwise michael@0: // be created and destroyed frequently during draw(). michael@0: private final RectF mBarRectF; michael@0: private final Rect mBarRect; michael@0: private final float[] mCoords; michael@0: private final RectF mCapRectF; michael@0: michael@0: private LayerRenderer mRenderer; michael@0: private int mProgram; michael@0: private int mPositionHandle; michael@0: private int mTextureHandle; michael@0: private int mSampleHandle; michael@0: private int mTMatrixHandle; michael@0: private int mOpacityHandle; michael@0: michael@0: // Fragment shader used to draw the scroll-bar with opacity michael@0: private static final String FRAGMENT_SHADER = michael@0: "precision mediump float;\n" + michael@0: "varying vec2 vTexCoord;\n" + michael@0: "uniform sampler2D sTexture;\n" + michael@0: "uniform float uOpacity;\n" + michael@0: "void main() {\n" + michael@0: " gl_FragColor = texture2D(sTexture, vTexCoord);\n" + michael@0: " gl_FragColor.a *= uOpacity;\n" + michael@0: "}\n"; michael@0: michael@0: // Dimensions of the texture bitmap (will always be power-of-two) michael@0: private final int mTexWidth; michael@0: private final int mTexHeight; michael@0: // Some useful dimensions of the actual content in the bitmap michael@0: private final int mBarWidth; michael@0: private final int mCapLength; michael@0: michael@0: private final Rect mStartCapTexCoords; // top/left endcap coordinates michael@0: private final Rect mBodyTexCoords; // 1-pixel slice of the texture to be stretched michael@0: private final Rect mEndCapTexCoords; // bottom/right endcap coordinates michael@0: michael@0: ScrollbarLayer(LayerRenderer renderer, Bitmap scrollbarImage, IntSize imageSize, boolean vertical) { michael@0: super(new BufferedCairoImage(scrollbarImage), TileLayer.PaintMode.NORMAL); michael@0: mRenderer = renderer; michael@0: mVertical = vertical; michael@0: michael@0: mBarRectF = new RectF(); michael@0: mBarRect = new Rect(); michael@0: mCoords = new float[20]; michael@0: mCapRectF = new RectF(); michael@0: michael@0: mTexHeight = scrollbarImage.getHeight(); michael@0: mTexWidth = scrollbarImage.getWidth(); michael@0: michael@0: if (mVertical) { michael@0: mBarWidth = imageSize.width; michael@0: mCapLength = imageSize.height / 2; michael@0: mStartCapTexCoords = new Rect(0, mTexHeight - mCapLength, imageSize.width, mTexHeight); michael@0: mBodyTexCoords = new Rect(0, mTexHeight - (mCapLength + 1), imageSize.width, mTexHeight - mCapLength); michael@0: mEndCapTexCoords = new Rect(0, mTexHeight - imageSize.height, imageSize.width, mTexHeight - (mCapLength + 1)); michael@0: } else { michael@0: mBarWidth = imageSize.height; michael@0: mCapLength = imageSize.width / 2; michael@0: mStartCapTexCoords = new Rect(0, mTexHeight - imageSize.height, mCapLength, mTexHeight); michael@0: mBodyTexCoords = new Rect(mCapLength, mTexHeight - imageSize.height, mCapLength + 1, mTexHeight); michael@0: mEndCapTexCoords = new Rect(mCapLength + 1, mTexHeight - imageSize.height, imageSize.width, mTexHeight); michael@0: } michael@0: } michael@0: michael@0: private void createProgram() { michael@0: int vertexShader = LayerRenderer.loadShader(GLES20.GL_VERTEX_SHADER, michael@0: LayerRenderer.DEFAULT_VERTEX_SHADER); michael@0: int fragmentShader = LayerRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, michael@0: FRAGMENT_SHADER); michael@0: michael@0: mProgram = GLES20.glCreateProgram(); michael@0: GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program michael@0: GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program michael@0: GLES20.glLinkProgram(mProgram); // creates OpenGL program executables michael@0: michael@0: // Get handles to the shaders' vPosition, aTexCoord, sTexture, and uTMatrix members. michael@0: mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); michael@0: mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord"); michael@0: mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture"); michael@0: mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix"); michael@0: mOpacityHandle = GLES20.glGetUniformLocation(mProgram, "uOpacity"); michael@0: } michael@0: michael@0: private void activateProgram() { michael@0: // Add the program to the OpenGL environment michael@0: GLES20.glUseProgram(mProgram); michael@0: michael@0: // Set the transformation matrix michael@0: GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, michael@0: LayerRenderer.DEFAULT_TEXTURE_MATRIX, 0); michael@0: michael@0: // Enable the arrays from which we get the vertex and texture coordinates michael@0: GLES20.glEnableVertexAttribArray(mPositionHandle); michael@0: GLES20.glEnableVertexAttribArray(mTextureHandle); michael@0: michael@0: GLES20.glUniform1i(mSampleHandle, 0); michael@0: GLES20.glUniform1f(mOpacityHandle, mOpacity); michael@0: } michael@0: michael@0: private void deactivateProgram() { michael@0: GLES20.glDisableVertexAttribArray(mTextureHandle); michael@0: GLES20.glDisableVertexAttribArray(mPositionHandle); michael@0: GLES20.glUseProgram(0); michael@0: } michael@0: michael@0: /** michael@0: * Decrease the opacity of the scrollbar by one frame's worth. michael@0: * Return true if the opacity was decreased, or false if the scrollbars michael@0: * are already fully faded out. michael@0: */ michael@0: public boolean fade() { michael@0: if (FloatUtils.fuzzyEquals(mOpacity, 0.0f)) { michael@0: return false; michael@0: } michael@0: beginTransaction(); // called on compositor thread michael@0: mOpacity = Math.max(mOpacity - FADE_AMOUNT, 0.0f); michael@0: endTransaction(); michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Restore the opacity of the scrollbar to fully opaque. michael@0: * Return true if the opacity was changed, or false if the scrollbars michael@0: * are already fully opaque. michael@0: */ michael@0: public boolean unfade() { michael@0: if (FloatUtils.fuzzyEquals(mOpacity, 1.0f)) { michael@0: return false; michael@0: } michael@0: beginTransaction(); // called on compositor thread michael@0: mOpacity = 1.0f; michael@0: endTransaction(); michael@0: return true; michael@0: } michael@0: michael@0: @Override michael@0: public void draw(RenderContext context) { michael@0: if (!initialized()) michael@0: return; michael@0: michael@0: // Create the shader program, if necessary michael@0: if (mProgram == 0) { michael@0: createProgram(); michael@0: } michael@0: michael@0: // Enable the shader program michael@0: mRenderer.deactivateDefaultProgram(); michael@0: activateProgram(); michael@0: michael@0: GLES20.glEnable(GLES20.GL_BLEND); michael@0: GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); michael@0: michael@0: if (mVertical) { michael@0: getVerticalRect(context, mBarRectF); michael@0: } else { michael@0: getHorizontalRect(context, mBarRectF); michael@0: } michael@0: RectUtils.round(mBarRectF, mBarRect); michael@0: michael@0: GLES20.glActiveTexture(GLES20.GL_TEXTURE0); michael@0: GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID()); michael@0: michael@0: float viewWidth = context.viewport.width(); michael@0: float viewHeight = context.viewport.height(); michael@0: michael@0: mBarRectF.set(mBarRect.left, viewHeight - mBarRect.top, mBarRect.right, viewHeight - mBarRect.bottom); michael@0: mBarRectF.offset(context.offset.x, -context.offset.y); michael@0: michael@0: // We take a 1-pixel slice from the center of the image and scale it to become the bar michael@0: fillRectCoordBuffer(mCoords, mBarRectF, viewWidth, viewHeight, mBodyTexCoords, mTexWidth, mTexHeight); michael@0: michael@0: // Get the buffer and handles from the context michael@0: FloatBuffer coordBuffer = context.coordBuffer; michael@0: int positionHandle = mPositionHandle; michael@0: int textureHandle = mTextureHandle; michael@0: michael@0: // Make sure we are at position zero in the buffer in case other draw methods did not michael@0: // clean up after themselves michael@0: coordBuffer.position(0); michael@0: coordBuffer.put(mCoords); michael@0: michael@0: // Unbind any the current array buffer so we can use client side buffers michael@0: GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); michael@0: michael@0: // Vertex coordinates are x,y,z starting at position 0 into the buffer. michael@0: coordBuffer.position(0); michael@0: GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer); michael@0: michael@0: // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer. michael@0: coordBuffer.position(3); michael@0: GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer); michael@0: michael@0: GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); michael@0: michael@0: // Reset the position in the buffer for the next set of vertex and texture coordinates. michael@0: coordBuffer.position(0); michael@0: if (mVertical) { michael@0: // top endcap michael@0: mCapRectF.set(mBarRectF.left, mBarRectF.top + mCapLength, mBarRectF.right, mBarRectF.top); michael@0: } else { michael@0: // left endcap michael@0: mCapRectF.set(mBarRectF.left - mCapLength, mBarRectF.bottom + mBarWidth, mBarRectF.left, mBarRectF.bottom); michael@0: } michael@0: michael@0: fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, mStartCapTexCoords, mTexWidth, mTexHeight); michael@0: coordBuffer.put(mCoords); michael@0: michael@0: // Vertex coordinates are x,y,z starting at position 0 into the buffer. michael@0: coordBuffer.position(0); michael@0: GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer); michael@0: michael@0: // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer. michael@0: coordBuffer.position(3); michael@0: GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer); michael@0: michael@0: GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); michael@0: michael@0: // Reset the position in the buffer for the next set of vertex and texture coordinates. michael@0: coordBuffer.position(0); michael@0: if (mVertical) { michael@0: // bottom endcap michael@0: mCapRectF.set(mBarRectF.left, mBarRectF.bottom, mBarRectF.right, mBarRectF.bottom - mCapLength); michael@0: } else { michael@0: // right endcap michael@0: mCapRectF.set(mBarRectF.right, mBarRectF.bottom + mBarWidth, mBarRectF.right + mCapLength, mBarRectF.bottom); michael@0: } michael@0: fillRectCoordBuffer(mCoords, mCapRectF, viewWidth, viewHeight, mEndCapTexCoords, mTexWidth, mTexHeight); michael@0: coordBuffer.put(mCoords); michael@0: michael@0: // Vertex coordinates are x,y,z starting at position 0 into the buffer. michael@0: coordBuffer.position(0); michael@0: GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer); michael@0: michael@0: // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer. michael@0: coordBuffer.position(3); michael@0: GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer); michael@0: michael@0: GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); michael@0: michael@0: // Reset the position in the buffer for the next set of vertex and texture coordinates. michael@0: coordBuffer.position(0); michael@0: michael@0: // Enable the default shader program again michael@0: deactivateProgram(); michael@0: mRenderer.activateDefaultProgram(); michael@0: } michael@0: michael@0: private void getVerticalRect(RenderContext context, RectF dest) { michael@0: RectF viewport = context.viewport; michael@0: RectF pageRect = context.pageRect; michael@0: float viewportHeight = viewport.height() - context.offset.y; michael@0: float barStart = ((viewport.top - context.offset.y - pageRect.top) * (viewportHeight / pageRect.height())) + mCapLength; michael@0: float barEnd = ((viewport.bottom - context.offset.y - pageRect.top) * (viewportHeight / pageRect.height())) - mCapLength; michael@0: if (barStart > barEnd) { michael@0: float middle = (barStart + barEnd) / 2.0f; michael@0: barStart = barEnd = middle; michael@0: } michael@0: dest.set(viewport.width() - mBarWidth, barStart, viewport.width(), barEnd); michael@0: } michael@0: michael@0: private void getHorizontalRect(RenderContext context, RectF dest) { michael@0: RectF viewport = context.viewport; michael@0: RectF pageRect = context.pageRect; michael@0: float viewportWidth = viewport.width() - context.offset.x; michael@0: float barStart = ((viewport.left - context.offset.x - pageRect.left) * (viewport.width() / pageRect.width())) + mCapLength; michael@0: float barEnd = ((viewport.right - context.offset.x - pageRect.left) * (viewport.width() / pageRect.width())) - mCapLength; michael@0: if (barStart > barEnd) { michael@0: float middle = (barStart + barEnd) / 2.0f; michael@0: barStart = barEnd = middle; michael@0: } michael@0: dest.set(barStart, viewport.height() - mBarWidth, barEnd, viewport.height()); michael@0: } michael@0: }