Home | History | Annotate | Download | only in ui
      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 public 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 Texture sSlider;
     53     private static Texture sTickMark;
     54     private static Texture 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 ZoomListener mZoomListener;
     72 
     73     public interface ZoomListener {
     74         public void onZoomChanged(int index, float ratio, boolean isMoving);
     75     }
     76 
     77     public ZoomController(Context context) {
     78         initializeStaticVariable(context);
     79     }
     80 
     81     private void onSliderMoved(int position, boolean isMoving) {
     82         position = Util.clamp(position,
     83                 mSliderTop, mSliderBottom - sSlider.getHeight());
     84         mSliderPosition = position;
     85         invalidate();
     86 
     87         int index = mRatios.length - 1 - (int)
     88                 ((position - mSliderTop) /  mValueGap + .5f);
     89         if (index != mIndex || !isMoving) {
     90             mIndex = index;
     91             if (mZoomListener != null) {
     92                 mZoomListener.onZoomChanged(mIndex, mRatios[mIndex], isMoving);
     93             }
     94         }
     95     }
     96 
     97     private static void initializeStaticVariable(Context context) {
     98         if (sBackground != null) return;
     99 
    100         sLabelSize = dpToPixel(context, LABEL_FONT_SIZE);
    101         sHorizontalPadding = dpToPixel(context, HORIZONTAL_PADDING);
    102         sVerticalPadding = dpToPixel(context, VERTICAL_PADDING);
    103         sMinimalHeight = dpToPixel(context, MINIMAL_HEIGHT);
    104         sToleranceRadius = dpToPixel(context, TOLERANCE_RADIUS);
    105 
    106         sBackground = new NinePatchTexture(context, R.drawable.zoom_background);
    107         sSlider = new ResourceTexture(context, R.drawable.zoom_slider);
    108         sTickMark = new ResourceTexture(context, R.drawable.zoom_tickmark);
    109         sFineTickMark = new ResourceTexture(
    110                 context, R.drawable.zoom_finetickmark);
    111     }
    112 
    113     @Override
    114     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    115         if (!changed) return;
    116         Rect p = mPaddings;
    117         int height = b - t - p.top - p.bottom;
    118         int margin = Math.max(sSlider.getHeight(), mMaxLabelHeight);
    119         mValueGap = (float) (height - margin) / (mRatios.length - 1);
    120 
    121         mSliderLeft = p.left + mMaxLabelWidth + sHorizontalPadding
    122                 + sTickMark.getWidth() + sHorizontalPadding;
    123 
    124         mSliderTop = p.top + margin / 2 - sSlider.getHeight() / 2;
    125         mSliderBottom = mSliderTop + height - margin + sSlider.getHeight();
    126     }
    127 
    128     private boolean withInToleranceRange(float x, float y) {
    129         float sx = mSliderLeft + sSlider.getWidth() / 2;
    130         float sy = mSliderTop + (mRatios.length - 1 - mIndex) * mValueGap
    131                 + sSlider.getHeight() / 2;
    132         float dist = Util.distance(x, y, sx, sy);
    133         return dist <= sToleranceRadius;
    134     }
    135 
    136     @Override
    137     protected boolean onTouch(MotionEvent e) {
    138         float x = e.getX();
    139         float y = e.getY();
    140         switch (e.getAction()) {
    141             case MotionEvent.ACTION_DOWN:
    142                 if (withInToleranceRange(x, y)) {
    143                     onSliderMoved((int) (y - sSlider.getHeight()), true);
    144                 }
    145                 return true;
    146             case MotionEvent.ACTION_MOVE:
    147                 if (mSliderPosition != INVALID_POSITION) {
    148                     onSliderMoved((int) (y - sSlider.getHeight()), true);
    149                 }
    150                 return true;
    151             case MotionEvent.ACTION_UP:
    152                 if (mSliderPosition != INVALID_POSITION) {
    153                     onSliderMoved((int) (y - sSlider.getHeight()), false);
    154                     mSliderPosition = INVALID_POSITION;
    155                 }
    156                 return true;
    157         }
    158         return true;
    159     }
    160 
    161     public void setAvailableZoomRatios(float ratios[]) {
    162         if (Arrays.equals(ratios, mRatios)) return;
    163         mRatios = ratios;
    164         mLabelStep = getLabelStep(ratios.length);
    165         mTickLabels = new StringTexture[
    166                 (ratios.length + mLabelStep - 1) / mLabelStep];
    167         for (int i = 0, n = mTickLabels.length; i < n; ++i) {
    168             mTickLabels[i] = StringTexture.newInstance(
    169                     sZoomFormat.format(ratios[i * mLabelStep]),
    170                     sLabelSize, LABEL_COLOR);
    171         }
    172         mFineTickStep = mLabelStep % 3 == 0
    173                 ? mLabelStep / 3
    174                 : mLabelStep %2 == 0 ? mLabelStep / 2 : 0;
    175 
    176         int maxHeight = 0;
    177         int maxWidth = 0;
    178         int labelCount = mTickLabels.length;
    179         for (int i = 0; i < labelCount; ++i) {
    180             maxWidth = Math.max(maxWidth, mTickLabels[i].getWidth());
    181             maxHeight = Math.max(maxHeight, mTickLabels[i].getHeight());
    182         }
    183 
    184         mMaxLabelHeight = maxHeight;
    185         mMaxLabelWidth = maxWidth;
    186         invalidate();
    187     }
    188 
    189     private int getLabelStep(final int valueCount) {
    190         if (valueCount < 5) return 1;
    191         for (int step = valueCount / 5;; ++step) {
    192             if (valueCount / step <= 5) return step;
    193         }
    194     }
    195 
    196     @Override
    197     protected void onMeasure(int widthSpec, int heightSpec) {
    198         int labelCount = mTickLabels.length;
    199         int ratioCount = mRatios.length;
    200 
    201         int height = (mMaxLabelHeight + sVerticalPadding)
    202                 * (labelCount - 1) * ratioCount / (mLabelStep * labelCount)
    203                 + Math.max(sSlider.getHeight(), mMaxLabelHeight);
    204 
    205         int width = mMaxLabelWidth + sHorizontalPadding + sTickMark.getWidth()
    206                 + sHorizontalPadding + sBackground.getIntrinsicWidth();
    207         height = Math.max(sMinimalHeight, height);
    208 
    209         new MeasureHelper(this)
    210                 .setPreferredContentSize(width, height)
    211                 .measure(widthSpec, heightSpec);
    212     }
    213 
    214     @Override
    215     protected void render(GLRootView root, GL11 gl) {
    216         renderTicks(root, gl);
    217         renderSlider(root, gl);
    218     }
    219 
    220     private void renderTicks(GLRootView root, GL11 gl) {
    221         float gap = mValueGap;
    222         int labelStep = mLabelStep;
    223 
    224         // render the tick labels
    225         int xoffset = mPaddings.left + mMaxLabelWidth;
    226         float yoffset = mSliderBottom - sSlider.getHeight() / 2;
    227         for (int i = 0, n = mTickLabels.length; i < n; ++i) {
    228             Texture t = mTickLabels[i];
    229             t.draw(root, xoffset - t.getWidth(),
    230                     (int) (yoffset - t.getHeight() / 2));
    231             yoffset -= labelStep * gap;
    232         }
    233 
    234         // render the main tick marks
    235         Texture tickMark = sTickMark;
    236         xoffset += sHorizontalPadding;
    237         yoffset = mSliderBottom - sSlider.getHeight() / 2;
    238         int halfHeight = tickMark.getHeight() / 2;
    239         for (int i = 0, n = mTickLabels.length; i < n; ++i) {
    240             tickMark.draw(root, xoffset, (int) (yoffset - halfHeight));
    241             yoffset -= labelStep * gap;
    242         }
    243 
    244         if (mFineTickStep > 0) {
    245             // render the fine tick marks
    246             tickMark = sFineTickMark;
    247             xoffset += sTickMark.getWidth() - tickMark.getWidth();
    248             yoffset = mSliderBottom - sSlider.getHeight() / 2;
    249             halfHeight = tickMark.getHeight() / 2;
    250             for (int i = 0, n = mRatios.length; i < n; ++i) {
    251                 if (i % mLabelStep != 0) {
    252                     tickMark.draw(root, xoffset, (int) (yoffset - halfHeight));
    253                 }
    254                 yoffset -= gap;
    255             }
    256         }
    257     }
    258 
    259     private void renderSlider(GLRootView root, GL11 gl) {
    260         int left = mSliderLeft;
    261         int bottom = mSliderBottom;
    262         int top = mSliderTop;
    263         sBackground.setSize(sBackground.getIntrinsicWidth(), bottom - top);
    264         sBackground.draw(root, left, top);
    265 
    266         if (mSliderPosition == INVALID_POSITION) {
    267             sSlider.draw(root, left, (int)
    268                     (top + mValueGap * (mRatios.length - 1 - mIndex)));
    269         } else {
    270             sSlider.draw(root, left, mSliderPosition);
    271         }
    272     }
    273 
    274     public void setZoomListener(ZoomListener listener) {
    275         mZoomListener = listener;
    276     }
    277 
    278     public void setZoomIndex(int index) {
    279         index = Util.clamp(index, 0, mRatios.length - 1);
    280         if (mIndex == index) return;
    281         mIndex = index;
    282         if (mZoomListener != null) {
    283             mZoomListener.onZoomChanged(mIndex, mRatios[mIndex], false);
    284         }
    285     }
    286 }
    287