• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.camera.ui;
18 
19 import static com.android.camera.ui.GLRootView.dpToPixel;
20 import android.content.Context;
21 import android.graphics.Color;
22 import android.graphics.Rect;
23 import android.view.MotionEvent;
24 
25 import com.android.camera.R;
26 import com.android.camera.Util;
27 
28 import java.text.DecimalFormat;
29 import java.util.Arrays;
30 
31 import javax.microedition.khronos.opengles.GL11;
32 
33 class ZoomController extends GLView {
34     private static final int LABEL_COLOR = Color.WHITE;
35 
36     private static final DecimalFormat sZoomFormat = new DecimalFormat("#.#x");
37     private static final int INVALID_POSITION = Integer.MAX_VALUE;
38 
39     private static final float LABEL_FONT_SIZE = 18;
40     private static final int HORIZONTAL_PADDING = 3;
41     private static final int VERTICAL_PADDING = 3;
42     private static final int MINIMAL_HEIGHT = 150;
43     private static final float TOLERANCE_RADIUS = 30;
44 
45     private static float sLabelSize;
46     private static int sHorizontalPadding;
47     private static int sVerticalPadding;
48     private static int sMinimalHeight;
49     private static float sToleranceRadius;
50 
51     private static NinePatchTexture sBackground;
52     private static BitmapTexture sSlider;
53     private static BitmapTexture sTickMark;
54     private static BitmapTexture sFineTickMark;
55 
56     private StringTexture mTickLabels[];
57     private float mRatios[];
58     private int mIndex;
59 
60     private int mFineTickStep;
61     private int mLabelStep;
62 
63     private int mMaxLabelWidth;
64     private int mMaxLabelHeight;
65 
66     private int mSliderTop;
67     private int mSliderBottom;
68     private int mSliderLeft;
69     private int mSliderPosition = INVALID_POSITION;
70     private float mValueGap;
71     private ZoomControllerListener mZoomListener;
72 
ZoomController(Context context)73     public ZoomController(Context context) {
74         initializeStaticVariable(context);
75     }
76 
onSliderMoved(int position, boolean isMoving)77     private void onSliderMoved(int position, boolean isMoving) {
78         position = Util.clamp(position,
79                 mSliderTop, mSliderBottom - sSlider.getHeight());
80         mSliderPosition = position;
81         invalidate();
82 
83         int index = mRatios.length - 1 - (int)
84                 ((position - mSliderTop) /  mValueGap + .5f);
85         if (index != mIndex || !isMoving) {
86             mIndex = index;
87             if (mZoomListener != null) {
88                 mZoomListener.onZoomChanged(mIndex, mRatios[mIndex], isMoving);
89             }
90         }
91     }
92 
initializeStaticVariable(Context context)93     private static void initializeStaticVariable(Context context) {
94         if (sBackground != null) return;
95 
96         sLabelSize = dpToPixel(context, LABEL_FONT_SIZE);
97         sHorizontalPadding = dpToPixel(context, HORIZONTAL_PADDING);
98         sVerticalPadding = dpToPixel(context, VERTICAL_PADDING);
99         sMinimalHeight = dpToPixel(context, MINIMAL_HEIGHT);
100         sToleranceRadius = dpToPixel(context, TOLERANCE_RADIUS);
101 
102         sBackground = new NinePatchTexture(context, R.drawable.zoom_background);
103         sSlider = new ResourceTexture(context, R.drawable.zoom_slider);
104         sTickMark = new ResourceTexture(context, R.drawable.zoom_tickmark);
105         sFineTickMark = new ResourceTexture(
106                 context, R.drawable.zoom_finetickmark);
107     }
108 
109     @Override
onLayout(boolean changed, int l, int t, int r, int b)110     protected void onLayout(boolean changed, int l, int t, int r, int b) {
111         if (!changed) return;
112         Rect p = mPaddings;
113         int height = b - t - p.top - p.bottom;
114         int margin = Math.max(sSlider.getHeight(), mMaxLabelHeight);
115         mValueGap = (float) (height - margin) / (mRatios.length - 1);
116 
117         mSliderLeft = p.left + mMaxLabelWidth + sHorizontalPadding
118                 + sTickMark.getWidth() + sHorizontalPadding;
119 
120         mSliderTop = p.top + margin / 2 - sSlider.getHeight() / 2;
121         mSliderBottom = mSliderTop + height - margin + sSlider.getHeight();
122     }
123 
withInToleranceRange(float x, float y)124     private boolean withInToleranceRange(float x, float y) {
125         float sx = mSliderLeft + sSlider.getWidth() / 2;
126         float sy = mSliderTop + (mRatios.length - 1 - mIndex) * mValueGap
127                 + sSlider.getHeight() / 2;
128         float dist = Util.distance(x, y, sx, sy);
129         return dist <= sToleranceRadius;
130     }
131 
132     @Override
onTouch(MotionEvent e)133     protected boolean onTouch(MotionEvent e) {
134         float x = e.getX();
135         float y = e.getY();
136         switch (e.getAction()) {
137             case MotionEvent.ACTION_DOWN:
138                 if (withInToleranceRange(x, y)) {
139                     onSliderMoved((int) (y - sSlider.getHeight()), true);
140                 }
141                 return true;
142             case MotionEvent.ACTION_MOVE:
143                 if (mSliderPosition != INVALID_POSITION) {
144                     onSliderMoved((int) (y - sSlider.getHeight()), true);
145                 }
146                 return true;
147             case MotionEvent.ACTION_UP:
148                 if (mSliderPosition != INVALID_POSITION) {
149                     onSliderMoved((int) (y - sSlider.getHeight()), false);
150                     mSliderPosition = INVALID_POSITION;
151                 }
152                 return true;
153         }
154         return true;
155     }
156 
setAvailableZoomRatios(float ratios[])157     public void setAvailableZoomRatios(float ratios[]) {
158         if (Arrays.equals(ratios, mRatios)) return;
159         mRatios = ratios;
160         mLabelStep = getLabelStep(ratios.length);
161         mTickLabels = new StringTexture[
162                 (ratios.length + mLabelStep - 1) / mLabelStep];
163         for (int i = 0, n = mTickLabels.length; i < n; ++i) {
164             mTickLabels[i] = StringTexture.newInstance(
165                     sZoomFormat.format(ratios[i * mLabelStep]),
166                     sLabelSize, LABEL_COLOR);
167         }
168         mFineTickStep = mLabelStep % 3 == 0
169                 ? mLabelStep / 3
170                 : mLabelStep %2 == 0 ? mLabelStep / 2 : 0;
171 
172         int maxHeight = 0;
173         int maxWidth = 0;
174         int labelCount = mTickLabels.length;
175         for (int i = 0; i < labelCount; ++i) {
176             maxWidth = Math.max(maxWidth, mTickLabels[i].getWidth());
177             maxHeight = Math.max(maxHeight, mTickLabels[i].getHeight());
178         }
179 
180         mMaxLabelHeight = maxHeight;
181         mMaxLabelWidth = maxWidth;
182         invalidate();
183     }
184 
getLabelStep(final int valueCount)185     private int getLabelStep(final int valueCount) {
186         if (valueCount < 5) return 1;
187         for (int step = valueCount / 5;; ++step) {
188             if (valueCount / step <= 5) return step;
189         }
190     }
191 
192     @Override
onMeasure(int widthSpec, int heightSpec)193     protected void onMeasure(int widthSpec, int heightSpec) {
194         int labelCount = mTickLabels.length;
195         int ratioCount = mRatios.length;
196 
197         int height = (mMaxLabelHeight + sVerticalPadding)
198                 * (labelCount - 1) * ratioCount / (mLabelStep * labelCount)
199                 + Math.max(sSlider.getHeight(), mMaxLabelHeight);
200 
201         int width = mMaxLabelWidth + sHorizontalPadding + sTickMark.getWidth()
202                 + sHorizontalPadding + sBackground.getWidth();
203         height = Math.max(sMinimalHeight, height);
204 
205         new MeasureHelper(this)
206                 .setPreferredContentSize(width, height)
207                 .measure(widthSpec, heightSpec);
208     }
209 
210     @Override
render(GLRootView root, GL11 gl)211     protected void render(GLRootView root, GL11 gl) {
212         renderTicks(root, gl);
213         renderSlider(root, gl);
214     }
215 
renderTicks(GLRootView root, GL11 gl)216     private void renderTicks(GLRootView root, GL11 gl) {
217         float gap = mValueGap;
218         int labelStep = mLabelStep;
219 
220         // render the tick labels
221         int xoffset = mPaddings.left + mMaxLabelWidth;
222         float yoffset = mSliderBottom - sSlider.getHeight() / 2;
223         for (int i = 0, n = mTickLabels.length; i < n; ++i) {
224             BitmapTexture t = mTickLabels[i];
225             t.draw(root, xoffset - t.getWidth(),
226                     (int) (yoffset - t.getHeight() / 2));
227             yoffset -= labelStep * gap;
228         }
229 
230         // render the main tick marks
231         BitmapTexture tickMark = sTickMark;
232         xoffset += sHorizontalPadding;
233         yoffset = mSliderBottom - sSlider.getHeight() / 2;
234         int halfHeight = tickMark.getHeight() / 2;
235         for (int i = 0, n = mTickLabels.length; i < n; ++i) {
236             tickMark.draw(root, xoffset, (int) (yoffset - halfHeight));
237             yoffset -= labelStep * gap;
238         }
239 
240         if (mFineTickStep > 0) {
241             // render the fine tick marks
242             tickMark = sFineTickMark;
243             xoffset += sTickMark.getWidth() - tickMark.getWidth();
244             yoffset = mSliderBottom - sSlider.getHeight() / 2;
245             halfHeight = tickMark.getHeight() / 2;
246             for (int i = 0, n = mRatios.length; i < n; ++i) {
247                 if (i % mLabelStep != 0) {
248                     tickMark.draw(root, xoffset, (int) (yoffset - halfHeight));
249                 }
250                 yoffset -= gap;
251             }
252         }
253     }
254 
renderSlider(GLRootView root, GL11 gl)255     private void renderSlider(GLRootView root, GL11 gl) {
256         int left = mSliderLeft;
257         int bottom = mSliderBottom;
258         int top = mSliderTop;
259         sBackground.draw(root, left, top, sBackground.getWidth(), bottom - top);
260 
261         if (mSliderPosition == INVALID_POSITION) {
262             sSlider.draw(root, left, (int)
263                     (top + mValueGap * (mRatios.length - 1 - mIndex)));
264         } else {
265             sSlider.draw(root, left, mSliderPosition);
266         }
267     }
268 
setZoomListener(ZoomControllerListener listener)269     public void setZoomListener(ZoomControllerListener listener) {
270         mZoomListener = listener;
271     }
272 
setZoomIndex(int index)273     public void setZoomIndex(int index) {
274         index = Util.clamp(index, 0, mRatios.length - 1);
275         if (mIndex == index) return;
276         mIndex = index;
277         if (mZoomListener != null) {
278             mZoomListener.onZoomChanged(mIndex, mRatios[mIndex], false);
279         }
280     }
281 }
282