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.photoeditor.actions;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.graphics.Canvas;
     22 import android.graphics.Color;
     23 import android.graphics.Paint;
     24 import android.graphics.Rect;
     25 import android.graphics.RectF;
     26 import android.graphics.Region;
     27 import android.graphics.RegionIterator;
     28 import android.graphics.drawable.Drawable;
     29 import android.util.AttributeSet;
     30 import android.view.MotionEvent;
     31 import android.view.View;
     32 
     33 import com.android.photoeditor.R;
     34 
     35 import java.util.Vector;
     36 
     37 /**
     38  * A view that track touch motions and adjust crop bounds accordingly.
     39  */
     40 class CropView extends View {
     41 
     42     /**
     43      * Listener of crop bounds.
     44      */
     45     public interface OnCropChangeListener {
     46 
     47         void onCropChanged(RectF bounds, boolean fromUser);
     48     }
     49 
     50     private static final int TOUCH_AREA_NONE = 0;
     51     private static final int TOUCH_AREA_LEFT = 1;
     52     private static final int TOUCH_AREA_TOP = 2;
     53     private static final int TOUCH_AREA_RIGHT = 4;
     54     private static final int TOUCH_AREA_BOTTOM = 8;
     55     private static final int TOUCH_AREA_INSIDE = 15;
     56     private static final int TOUCH_AREA_OUTSIDE = 16;
     57     private static final int TOUCH_AREA_TOP_LEFT = 3;
     58     private static final int TOUCH_AREA_TOP_RIGHT = 6;
     59     private static final int TOUCH_AREA_BOTTOM_LEFT = 9;
     60     private static final int TOUCH_AREA_BOTTOM_RIGHT = 12;
     61 
     62     private static final int BORDER_COLOR = 0xFF008AFF;
     63     private static final int OUTER_COLOR = 0xA0000000;
     64     private static final int INDICATION_COLOR = 0xFFCC9900;
     65     private static final int TOUCH_AREA_SPAN = 20;
     66     private static final int TOUCH_AREA_SPAN2 = TOUCH_AREA_SPAN * 2;
     67     private static final float BORDER_WIDTH = 2.0f;
     68 
     69     private final Paint outerAreaPaint;
     70     private final Paint borderPaint;
     71     private final Paint highlightPaint;
     72 
     73     private final Drawable heightIndicator;
     74     private final Drawable widthIndicator;
     75     private final int indicatorSize;
     76 
     77     private RectF cropBounds;
     78     private RectF photoBounds;
     79 
     80     private OnCropChangeListener listener;
     81 
     82     private float lastX;
     83     private float lastY;
     84     private int currentTouchArea;
     85 
     86     public CropView(Context context, AttributeSet attrs) {
     87         super(context, attrs);
     88 
     89         Resources resources = context.getResources();
     90         heightIndicator = resources.getDrawable(R.drawable.crop_height_holo);
     91         widthIndicator = resources.getDrawable(R.drawable.crop_width_holo);
     92         indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size);
     93 
     94         outerAreaPaint = new Paint();
     95         outerAreaPaint.setStyle(Paint.Style.FILL);
     96         outerAreaPaint.setColor(OUTER_COLOR);
     97 
     98         borderPaint = new Paint();
     99         borderPaint.setStyle(Paint.Style.STROKE);
    100         borderPaint.setColor(BORDER_COLOR);
    101         borderPaint.setStrokeWidth(BORDER_WIDTH);
    102 
    103         highlightPaint = new Paint();
    104         highlightPaint.setStyle(Paint.Style.STROKE);
    105         highlightPaint.setColor(INDICATION_COLOR);
    106         highlightPaint.setStrokeWidth(BORDER_WIDTH);
    107 
    108         currentTouchArea = TOUCH_AREA_NONE;
    109     }
    110 
    111     public void setOnCropChangeListener(OnCropChangeListener listener) {
    112         this.listener = listener;
    113     }
    114 
    115     private void notifyCropChange(boolean fromUser) {
    116         if (listener != null) {
    117             listener.onCropChanged(cropBounds, fromUser);
    118         }
    119     }
    120 
    121     public void setCropBounds(RectF bounds) {
    122         bounds.intersect(photoBounds);
    123         cropBounds = bounds;
    124         if (photoBounds.width() <= TOUCH_AREA_SPAN2) {
    125             cropBounds.left = photoBounds.left;
    126             cropBounds.right = photoBounds.right;
    127         }
    128         if (photoBounds.height() <= TOUCH_AREA_SPAN2) {
    129             cropBounds.top = photoBounds.top;
    130             cropBounds.bottom = photoBounds.bottom;
    131         }
    132         notifyCropChange(false);
    133         invalidate();
    134     }
    135 
    136     /**
    137      * Sets bounds to crop within.
    138      */
    139     public void setPhotoBounds(RectF bounds) {
    140         photoBounds = bounds;
    141     }
    142 
    143     public boolean fullPhotoCropped() {
    144         return cropBounds.contains(photoBounds);
    145     }
    146 
    147     private int detectTouchArea(float x, float y) {
    148         RectF area = new RectF();
    149         area.set(cropBounds);
    150         area.inset(-TOUCH_AREA_SPAN, -TOUCH_AREA_SPAN);
    151         if (!area.contains(x, y)) {
    152             return TOUCH_AREA_OUTSIDE;
    153         }
    154 
    155         // left
    156         area.set(cropBounds.left - TOUCH_AREA_SPAN, cropBounds.top  + TOUCH_AREA_SPAN,
    157                 cropBounds.left + TOUCH_AREA_SPAN, cropBounds.bottom - TOUCH_AREA_SPAN);
    158         if (area.contains(x, y)) {
    159             return TOUCH_AREA_LEFT;
    160         }
    161         // right
    162         area.offset(cropBounds.width(), 0f);
    163         if (area.contains(x, y)) {
    164             return TOUCH_AREA_RIGHT;
    165         }
    166         // top
    167         area.set(cropBounds.left + TOUCH_AREA_SPAN, cropBounds.top - TOUCH_AREA_SPAN,
    168                 cropBounds.right - TOUCH_AREA_SPAN, cropBounds.top + TOUCH_AREA_SPAN);
    169         if (area.contains(x, y)) {
    170             return TOUCH_AREA_TOP;
    171         }
    172         // bottom
    173         area.offset(0f, cropBounds.height());
    174         if (area.contains(x, y)) {
    175             return TOUCH_AREA_BOTTOM;
    176         }
    177         // top left
    178         area.set(cropBounds.left - TOUCH_AREA_SPAN, cropBounds.top - TOUCH_AREA_SPAN,
    179                 cropBounds.left + TOUCH_AREA_SPAN, cropBounds.top + TOUCH_AREA_SPAN);
    180         if (area.contains(x, y)) {
    181             return TOUCH_AREA_TOP_LEFT;
    182         }
    183         // top right
    184         area.offset(cropBounds.width(), 0f);
    185         if (area.contains(x, y)) {
    186             return TOUCH_AREA_TOP_RIGHT;
    187         }
    188         // bottom right
    189         area.offset(0f, cropBounds.height());
    190         if (area.contains(x, y)) {
    191             return TOUCH_AREA_BOTTOM_RIGHT;
    192         }
    193         // bottom left
    194         area.offset(-cropBounds.width(), 0f);
    195         if (area.contains(x, y)) {
    196             return TOUCH_AREA_BOTTOM_LEFT;
    197         }
    198         return TOUCH_AREA_INSIDE;
    199     }
    200 
    201     private void performMove(float deltaX, float deltaY) {
    202         if (currentTouchArea == TOUCH_AREA_INSIDE){  // moving the rect.
    203             cropBounds.offset(deltaX, deltaY);
    204             if (cropBounds.left < photoBounds.left) {
    205                 cropBounds.offset(photoBounds.left - cropBounds.left, 0f);
    206             } else if (cropBounds.right > photoBounds.right) {
    207                 cropBounds.offset(photoBounds.right - cropBounds.right, 0f);
    208             }
    209             if (cropBounds.top < photoBounds.top) {
    210                 cropBounds.offset(0f, photoBounds.top - cropBounds.top);
    211             } else if (cropBounds.bottom > photoBounds.bottom) {
    212                 cropBounds.offset(0f, photoBounds.bottom - cropBounds.bottom);
    213             }
    214         } else {  // adjusting bounds.
    215             if ((currentTouchArea & TOUCH_AREA_LEFT) != 0) {
    216                 cropBounds.left = Math.min(cropBounds.left + deltaX,
    217                         cropBounds.right - TOUCH_AREA_SPAN2);
    218             }
    219             if ((currentTouchArea & TOUCH_AREA_TOP) != 0) {
    220                 cropBounds.top = Math.min(cropBounds.top + deltaY,
    221                         cropBounds.bottom - TOUCH_AREA_SPAN2);
    222             }
    223             if ((currentTouchArea & TOUCH_AREA_RIGHT) != 0) {
    224                 cropBounds.right = Math.max(cropBounds.right + deltaX,
    225                         cropBounds.left + TOUCH_AREA_SPAN2);
    226             }
    227             if ((currentTouchArea & TOUCH_AREA_BOTTOM) != 0) {
    228                 cropBounds.bottom = Math.max(cropBounds.bottom + deltaY,
    229                         cropBounds.top + TOUCH_AREA_SPAN2);
    230             }
    231             cropBounds.intersect(photoBounds);
    232         }
    233     }
    234 
    235     @Override
    236     public boolean onTouchEvent(MotionEvent event) {
    237         super.onTouchEvent(event);
    238 
    239         if (!isEnabled()) {
    240             return true;
    241         }
    242         float x = event.getX();
    243         float y = event.getY();
    244 
    245         switch (event.getAction()) {
    246             case MotionEvent.ACTION_DOWN:
    247                 currentTouchArea = detectTouchArea(x, y);
    248                 lastX = x;
    249                 lastY = y;
    250                 invalidate();
    251                 break;
    252 
    253             case MotionEvent.ACTION_MOVE:
    254                 performMove(x - lastX, y - lastY);
    255 
    256                 lastX = x;
    257                 lastY = y;
    258                 notifyCropChange(true);
    259                 invalidate();
    260                 break;
    261 
    262             case MotionEvent.ACTION_CANCEL:
    263             case MotionEvent.ACTION_UP:
    264                 currentTouchArea = TOUCH_AREA_NONE;
    265                 invalidate();
    266                 break;
    267         }
    268         return true;
    269     }
    270 
    271     private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) {
    272         int left = (int) centerX - indicatorSize / 2;
    273         int top = (int) centerY - indicatorSize / 2;
    274         int right = left + indicatorSize;
    275         int bottom = top + indicatorSize;
    276         indicator.setBounds(left, top, right, bottom);
    277         indicator.draw(canvas);
    278     }
    279 
    280     private void drawIndicators(Canvas canvas) {
    281         drawIndicator(canvas, heightIndicator, cropBounds.centerX(), cropBounds.top);
    282         drawIndicator(canvas, heightIndicator, cropBounds.centerX(), cropBounds.bottom);
    283         drawIndicator(canvas, widthIndicator, cropBounds.left, cropBounds.centerY());
    284         drawIndicator(canvas, widthIndicator, cropBounds.right, cropBounds.centerY());
    285     }
    286 
    287     private void drawTouchHighlights(Canvas canvas) {
    288         if ((currentTouchArea & TOUCH_AREA_TOP) != 0) {
    289             canvas.drawLine(cropBounds.left, cropBounds.top, cropBounds.right, cropBounds.top,
    290                     highlightPaint);
    291         }
    292         if ((currentTouchArea & TOUCH_AREA_BOTTOM) != 0) {
    293             canvas.drawLine(cropBounds.left, cropBounds.bottom, cropBounds.right,
    294                     cropBounds.bottom, highlightPaint);
    295         }
    296         if ((currentTouchArea & TOUCH_AREA_LEFT) != 0) {
    297             canvas.drawLine(cropBounds.left, cropBounds.top, cropBounds.left, cropBounds.bottom,
    298                     highlightPaint);
    299         }
    300         if ((currentTouchArea & TOUCH_AREA_RIGHT) != 0) {
    301             canvas.drawLine(cropBounds.right, cropBounds.top, cropBounds.right, cropBounds.bottom,
    302                     highlightPaint);
    303         }
    304     }
    305 
    306     private void drawBounds(Canvas canvas) {
    307         Rect r = new Rect();
    308         photoBounds.roundOut(r);
    309         Region drawRegion = new Region(r);
    310         cropBounds.roundOut(r);
    311         drawRegion.op(r, Region.Op.DIFFERENCE);
    312         RegionIterator iter = new RegionIterator(drawRegion);
    313         while (iter.next(r)) {
    314             canvas.drawRect(r, outerAreaPaint);
    315         }
    316 
    317         canvas.drawRect(cropBounds, borderPaint);
    318     }
    319 
    320     @Override
    321     protected void onDraw(Canvas canvas) {
    322         super.onDraw(canvas);
    323 
    324         drawBounds(canvas);
    325         if (currentTouchArea != TOUCH_AREA_NONE) {
    326             drawTouchHighlights(canvas);
    327             drawIndicators(canvas);
    328         }
    329     }
    330 }
    331