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 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 
     73     public ZoomController(Context context) {
     74         initializeStaticVariable(context);
     75     }
     76 
     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 
     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
    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 
    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
    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 
    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 
    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
    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
    211     protected void render(GLRootView root, GL11 gl) {
    212         renderTicks(root, gl);
    213         renderSlider(root, gl);
    214     }
    215 
    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 
    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 
    269     public void setZoomListener(ZoomControllerListener listener) {
    270         mZoomListener = listener;
    271     }
    272 
    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