Home | History | Annotate | Download | only in actions
      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.gallery3d.photoeditor.actions;
     18 
     19 import android.content.Context;
     20 import android.graphics.Canvas;
     21 import android.graphics.DashPathEffect;
     22 import android.graphics.Paint;
     23 import android.graphics.Path;
     24 import android.util.AttributeSet;
     25 import android.view.MotionEvent;
     26 
     27 import com.android.gallery3d.R;
     28 
     29 /**
     30  * View that shows grids and handles touch-events to adjust angle of rotation.
     31  */
     32 class RotateView extends FullscreenToolView {
     33 
     34     /**
     35      * Listens to rotate changes.
     36      */
     37     public interface OnRotateChangeListener {
     38 
     39         void onAngleChanged(float degrees, boolean fromUser);
     40 
     41         void onStartTrackingTouch();
     42 
     43         void onStopTrackingTouch();
     44     }
     45 
     46     // All angles used are defined between PI and -PI.
     47     private static final float MATH_PI = (float) Math.PI;
     48     private static final float MATH_HALF_PI = MATH_PI / 2;
     49     private static final float RADIAN_TO_DEGREE = 180f / MATH_PI;
     50 
     51     private final Paint dashStrokePaint;
     52     private final Path grids = new Path();
     53     private final Path referenceLine = new Path();
     54     private final int gridsColor;
     55     private final int referenceColor;
     56 
     57     private OnRotateChangeListener listener;
     58     private boolean drawGrids;
     59     private int centerX;
     60     private int centerY;
     61     private float maxRotatedAngle;
     62     private float minRotatedAngle;
     63     private float currentRotatedAngle;
     64     private float lastRotatedAngle;
     65     private float touchStartAngle;
     66 
     67     public RotateView(Context context, AttributeSet attrs) {
     68         super(context, attrs);
     69 
     70         dashStrokePaint = new Paint();
     71         dashStrokePaint.setAntiAlias(true);
     72         dashStrokePaint.setStyle(Paint.Style.STROKE);
     73         dashStrokePaint.setPathEffect(new DashPathEffect(new float[] {15.0f, 5.0f}, 1.0f));
     74         dashStrokePaint.setStrokeWidth(2f);
     75         gridsColor = context.getResources().getColor(R.color.translucent_white);
     76         referenceColor = context.getResources().getColor(R.color.translucent_cyan);
     77     }
     78 
     79     public void setRotatedAngle(float degrees) {
     80         refreshAngle(degrees, false);
     81     }
     82 
     83     /**
     84      * Sets allowed degrees for rotation span before rotating the view.
     85      */
     86     public void setRotateSpan(float degrees) {
     87         if (degrees >= 360f) {
     88             maxRotatedAngle = Float.POSITIVE_INFINITY;
     89         } else {
     90             maxRotatedAngle = (degrees / RADIAN_TO_DEGREE) / 2;
     91         }
     92         minRotatedAngle = -maxRotatedAngle;
     93     }
     94 
     95     public void setOnRotateChangeListener(OnRotateChangeListener listener) {
     96         this.listener = listener;
     97     }
     98 
     99     public void setDrawGrids(boolean drawGrids) {
    100         this.drawGrids = drawGrids;
    101         invalidate();
    102     }
    103 
    104     @Override
    105     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    106         super.onSizeChanged(w, h, oldw, oldh);
    107 
    108         centerX = w / 2;
    109         centerY = h / 2;
    110 
    111         // Make reference line long enough to cross the bounds diagonally after being rotated.
    112         referenceLine.reset();
    113         float radius = (float) Math.hypot(centerX, centerY);
    114         float delta = radius - centerX;
    115         referenceLine.moveTo(-delta, centerY);
    116         referenceLine.lineTo(getWidth() + delta, centerY);
    117         delta = radius - centerY;
    118         referenceLine.moveTo(centerX, -delta);
    119         referenceLine.lineTo(centerX, getHeight() + delta);
    120 
    121         // Set grids inside photo display bounds.
    122         grids.reset();
    123         delta = displayBounds.width() / 4.0f;
    124         for (float x = displayBounds.left + delta; x < displayBounds.right; x += delta) {
    125             grids.moveTo(x, displayBounds.top);
    126             grids.lineTo(x, displayBounds.bottom);
    127         }
    128         delta = displayBounds.height() / 4.0f;
    129         for (float y = displayBounds.top + delta; y < displayBounds.bottom; y += delta) {
    130             grids.moveTo(displayBounds.left, y);
    131             grids.lineTo(displayBounds.right, y);
    132         }
    133     }
    134 
    135     @Override
    136     protected void onDraw(Canvas canvas) {
    137         super.onDraw(canvas);
    138 
    139         if (drawGrids) {
    140             canvas.save();
    141             canvas.clipRect(displayBounds);
    142             dashStrokePaint.setColor(gridsColor);
    143             canvas.drawPath(grids, dashStrokePaint);
    144 
    145             canvas.rotate(-currentRotatedAngle * RADIAN_TO_DEGREE, centerX, centerY);
    146             dashStrokePaint.setColor(referenceColor);
    147             canvas.drawPath(referenceLine, dashStrokePaint);
    148             canvas.restore();
    149         }
    150     }
    151 
    152     private float calculateAngle(MotionEvent ev) {
    153         float x = ev.getX() - centerX;
    154         float y = centerY - ev.getY();
    155 
    156         float angle;
    157         if (x == 0) {
    158             angle = (y >= 0) ? MATH_HALF_PI : -MATH_HALF_PI;
    159         } else {
    160             angle = (float) Math.atan(y / x);
    161         }
    162 
    163         if ((angle >= 0) && (x < 0)) {
    164             angle = angle - MATH_PI;
    165         } else if ((angle < 0) && (x < 0)) {
    166             angle = MATH_PI + angle;
    167         }
    168         return angle;
    169     }
    170 
    171     @Override
    172     public boolean onTouchEvent(MotionEvent ev) {
    173         super.onTouchEvent(ev);
    174 
    175         if (isEnabled()) {
    176             switch (ev.getAction()) {
    177                 case MotionEvent.ACTION_DOWN:
    178                     lastRotatedAngle = currentRotatedAngle;
    179                     touchStartAngle = calculateAngle(ev);
    180 
    181                     if (listener != null) {
    182                         listener.onStartTrackingTouch();
    183                     }
    184                     break;
    185 
    186                 case MotionEvent.ACTION_MOVE:
    187                     float touchAngle = calculateAngle(ev);
    188                     float rotatedAngle = touchAngle - touchStartAngle + lastRotatedAngle;
    189 
    190                     if ((rotatedAngle > maxRotatedAngle) || (rotatedAngle < minRotatedAngle)) {
    191                         // Angles are out of range; restart rotating.
    192                         // TODO: Fix discontinuity around boundary.
    193                         lastRotatedAngle = currentRotatedAngle;
    194                         touchStartAngle = touchAngle;
    195                     } else {
    196                         refreshAngle(-rotatedAngle * RADIAN_TO_DEGREE, true);
    197                     }
    198                     break;
    199 
    200                 case MotionEvent.ACTION_CANCEL:
    201                 case MotionEvent.ACTION_UP:
    202                     if (listener != null) {
    203                         listener.onStopTrackingTouch();
    204                     }
    205                     break;
    206             }
    207         }
    208         return true;
    209     }
    210 
    211     private void refreshAngle(float degrees, boolean fromUser) {
    212         currentRotatedAngle = -degrees / RADIAN_TO_DEGREE;
    213         if (listener != null) {
    214             listener.onAngleChanged(degrees, fromUser);
    215         }
    216         invalidate();
    217     }
    218 }
    219