blob: 83e2d094eac7de88974028bd905b45bc394349ad [file] [log] [blame]
package com.android.camera.ui;
import static com.android.camera.ui.GLRootView.dpToPixel;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.MotionEvent;
import com.android.camera.R;
import com.android.camera.Util;
import java.text.DecimalFormat;
import java.util.Arrays;
import javax.microedition.khronos.opengles.GL11;
public class ZoomController extends GLView {
private static final int LABEL_COLOR = Color.WHITE;
private static final DecimalFormat sZoomFormat = new DecimalFormat("#.#x");
private static final int INVALID_POSITION = Integer.MAX_VALUE;
private static final float LABEL_FONT_SIZE = 18;
private static final int HORIZONTAL_PADDING = 3;
private static final int VERTICAL_PADDING = 3;
private static final int MINIMAL_HEIGHT = 150;
private static final float TOLERANCE_RADIUS = 30;
private static float sLabelSize;
private static int sHorizontalPadding;
private static int sVerticalPadding;
private static int sMinimalHeight;
private static float sToleranceRadius;
private static NinePatchTexture sBackground;
private static Texture sSlider;
private static Texture sTickMark;
private static Texture sFineTickMark;
private StringTexture mTickLabels[];
private float mRatios[];
private int mIndex;
private int mFineTickStep;
private int mLabelStep;
private int mMaxLabelWidth;
private int mMaxLabelHeight;
private int mSliderTop;
private int mSliderBottom;
private int mSliderLeft;
private int mSliderPosition = INVALID_POSITION;
private float mValueGap;
private ZoomListener mZoomListener;
public interface ZoomListener {
public void onZoomChanged(int index, float ratio, boolean isMoving);
}
public ZoomController(Context context) {
initializeStaticVariable(context);
}
private void onSliderMoved(int position, boolean isMoving) {
position = Util.clamp(position,
mSliderTop, mSliderBottom - sSlider.getHeight());
mSliderPosition = position;
invalidate();
int index = mRatios.length - 1 - (int)
((position - mSliderTop) / mValueGap + .5f);
if (index != mIndex || !isMoving) {
mIndex = index;
if (mZoomListener != null) {
mZoomListener.onZoomChanged(mIndex, mRatios[mIndex], isMoving);
}
}
}
private static void initializeStaticVariable(Context context) {
if (sBackground != null) return;
sLabelSize = dpToPixel(context, LABEL_FONT_SIZE);
sHorizontalPadding = dpToPixel(context, HORIZONTAL_PADDING);
sVerticalPadding = dpToPixel(context, VERTICAL_PADDING);
sMinimalHeight = dpToPixel(context, MINIMAL_HEIGHT);
sToleranceRadius = dpToPixel(context, TOLERANCE_RADIUS);
sBackground = new NinePatchTexture(context, R.drawable.zoom_background);
sSlider = new ResourceTexture(context, R.drawable.zoom_slider);
sTickMark = new ResourceTexture(context, R.drawable.zoom_tickmark);
sFineTickMark = new ResourceTexture(
context, R.drawable.zoom_finetickmark);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!changed) return;
Rect p = mPaddings;
int height = b - t - p.top - p.bottom;
int margin = Math.max(sSlider.getHeight(), mMaxLabelHeight);
mValueGap = (float) (height - margin) / (mRatios.length - 1);
mSliderLeft = p.left + mMaxLabelWidth + sHorizontalPadding
+ sTickMark.getWidth() + sHorizontalPadding;
mSliderTop = p.top + margin / 2 - sSlider.getHeight() / 2;
mSliderBottom = mSliderTop + height - margin + sSlider.getHeight();
}
private boolean withInToleranceRange(float x, float y) {
float sx = mSliderLeft + sSlider.getWidth() / 2;
float sy = mSliderTop + (mRatios.length - 1 - mIndex) * mValueGap
+ sSlider.getHeight() / 2;
float dist = Util.distance(x, y, sx, sy);
return dist <= sToleranceRadius;
}
@Override
protected boolean onTouch(MotionEvent e) {
float x = e.getX();
float y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
if (withInToleranceRange(x, y)) {
onSliderMoved((int) (y - sSlider.getHeight()), true);
}
return true;
case MotionEvent.ACTION_MOVE:
if (mSliderPosition != INVALID_POSITION) {
onSliderMoved((int) (y - sSlider.getHeight()), true);
}
return true;
case MotionEvent.ACTION_UP:
if (mSliderPosition != INVALID_POSITION) {
onSliderMoved((int) (y - sSlider.getHeight()), false);
mSliderPosition = INVALID_POSITION;
}
return true;
}
return true;
}
public void setAvailableZoomRatios(float ratios[]) {
if (Arrays.equals(ratios, mRatios)) return;
mRatios = ratios;
mLabelStep = getLabelStep(ratios.length);
mTickLabels = new StringTexture[
(ratios.length + mLabelStep - 1) / mLabelStep];
for (int i = 0, n = mTickLabels.length; i < n; ++i) {
mTickLabels[i] = StringTexture.newInstance(
sZoomFormat.format(ratios[i * mLabelStep]),
sLabelSize, LABEL_COLOR);
}
mFineTickStep = mLabelStep % 3 == 0
? mLabelStep / 3
: mLabelStep %2 == 0 ? mLabelStep / 2 : 0;
int maxHeight = 0;
int maxWidth = 0;
int labelCount = mTickLabels.length;
for (int i = 0; i < labelCount; ++i) {
maxWidth = Math.max(maxWidth, mTickLabels[i].getWidth());
maxHeight = Math.max(maxHeight, mTickLabels[i].getHeight());
}
mMaxLabelHeight = maxHeight;
mMaxLabelWidth = maxWidth;
invalidate();
}
private int getLabelStep(final int valueCount) {
if (valueCount < 5) return 1;
for (int step = valueCount / 5;; ++step) {
if (valueCount / step <= 5) return step;
}
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
int labelCount = mTickLabels.length;
int ratioCount = mRatios.length;
int height = (mMaxLabelHeight + sVerticalPadding)
* (labelCount - 1) * ratioCount / (mLabelStep * labelCount)
+ Math.max(sSlider.getHeight(), mMaxLabelHeight);
int width = mMaxLabelWidth + sHorizontalPadding + sTickMark.getWidth()
+ sHorizontalPadding + sBackground.getIntrinsicWidth();
height = Math.max(sMinimalHeight, height);
new MeasureHelper(this)
.setPreferredContentSize(width, height)
.measure(widthSpec, heightSpec);
}
@Override
protected void render(GLRootView root, GL11 gl) {
renderTicks(root, gl);
renderSlider(root, gl);
}
private void renderTicks(GLRootView root, GL11 gl) {
float gap = mValueGap;
int labelStep = mLabelStep;
// render the tick labels
int xoffset = mPaddings.left + mMaxLabelWidth;
float yoffset = mSliderBottom - sSlider.getHeight() / 2;
for (int i = 0, n = mTickLabels.length; i < n; ++i) {
Texture t = mTickLabels[i];
t.draw(root, xoffset - t.getWidth(),
(int) (yoffset - t.getHeight() / 2));
yoffset -= labelStep * gap;
}
// render the main tick marks
Texture tickMark = sTickMark;
xoffset += sHorizontalPadding;
yoffset = mSliderBottom - sSlider.getHeight() / 2;
int halfHeight = tickMark.getHeight() / 2;
for (int i = 0, n = mTickLabels.length; i < n; ++i) {
tickMark.draw(root, xoffset, (int) (yoffset - halfHeight));
yoffset -= labelStep * gap;
}
if (mFineTickStep > 0) {
// render the fine tick marks
tickMark = sFineTickMark;
xoffset += sTickMark.getWidth() - tickMark.getWidth();
yoffset = mSliderBottom - sSlider.getHeight() / 2;
halfHeight = tickMark.getHeight() / 2;
for (int i = 0, n = mRatios.length; i < n; ++i) {
if (i % mLabelStep != 0) {
tickMark.draw(root, xoffset, (int) (yoffset - halfHeight));
}
yoffset -= gap;
}
}
}
private void renderSlider(GLRootView root, GL11 gl) {
int left = mSliderLeft;
int bottom = mSliderBottom;
int top = mSliderTop;
sBackground.setSize(sBackground.getIntrinsicWidth(), bottom - top);
sBackground.draw(root, left, top);
if (mSliderPosition == INVALID_POSITION) {
sSlider.draw(root, left, (int)
(top + mValueGap * (mRatios.length - 1 - mIndex)));
} else {
sSlider.draw(root, left, mSliderPosition);
}
}
public void setZoomListener(ZoomListener listener) {
mZoomListener = listener;
}
public void setZoomIndex(int index) {
index = Util.clamp(index, 0, mRatios.length - 1);
if (mIndex == index) return;
mIndex = index;
if (mZoomListener != null) {
mZoomListener.onZoomChanged(mIndex, mRatios[mIndex], false);
}
}
}