Home | History | Annotate | Download | only in gle2
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
      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.ide.eclipse.adt.internal.editors.layout.gle2;
     18 
     19 import com.android.ide.common.api.DropFeedback;
     20 import com.android.ide.common.api.Rect;
     21 import com.android.ide.common.api.ResizePolicy;
     22 import com.android.ide.common.api.SegmentType;
     23 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.Position;
     24 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
     25 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
     26 import com.android.utils.Pair;
     27 
     28 import org.eclipse.swt.events.KeyEvent;
     29 import org.eclipse.swt.graphics.GC;
     30 
     31 import java.util.Collections;
     32 import java.util.List;
     33 
     34 /**
     35  * A {@link ResizeGesture} is a gesture for resizing a selected widget. It is initiated
     36  * by a drag of a {@link SelectionHandle}.
     37  */
     38 public class ResizeGesture extends Gesture {
     39     /** The {@link Overlay} drawn for the gesture feedback. */
     40     private ResizeOverlay mOverlay;
     41 
     42     /** The canvas associated with this gesture. */
     43     private LayoutCanvas mCanvas;
     44 
     45     /** The selection handle we're dragging to perform this resize */
     46     private SelectionHandle mHandle;
     47 
     48     private NodeProxy mParentNode;
     49     private NodeProxy mChildNode;
     50     private DropFeedback mFeedback;
     51     private ResizePolicy mResizePolicy;
     52     private SegmentType mHorizontalEdge;
     53     private SegmentType mVerticalEdge;
     54 
     55     /**
     56      * Creates a new marquee selection (selection swiping).
     57      *
     58      * @param canvas The canvas where selection is performed.
     59      * @param item The selected item the handle corresponds to
     60      * @param handle The handle being dragged to perform the resize
     61      */
     62     public ResizeGesture(LayoutCanvas canvas, SelectionItem item, SelectionHandle handle) {
     63         mCanvas = canvas;
     64         mHandle = handle;
     65 
     66         mChildNode = item.getNode();
     67         mParentNode = (NodeProxy) mChildNode.getParent();
     68         mResizePolicy = item.getResizePolicy();
     69         mHorizontalEdge = getHorizontalEdgeType(mHandle);
     70         mVerticalEdge = getVerticalEdgeType(mHandle);
     71     }
     72 
     73     @Override
     74     public void begin(ControlPoint pos, int startMask) {
     75         super.begin(pos, startMask);
     76 
     77         mCanvas.getSelectionOverlay().setHidden(true);
     78 
     79         RulesEngine rulesEngine = mCanvas.getRulesEngine();
     80         Rect newBounds = getNewBounds(pos);
     81         ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy();
     82         CanvasViewInfo childInfo = viewHierarchy.findViewInfoFor(mChildNode);
     83         CanvasViewInfo parentInfo = viewHierarchy.findViewInfoFor(mParentNode);
     84         Object childView = childInfo != null ? childInfo.getViewObject() : null;
     85         Object parentView = parentInfo != null ? parentInfo.getViewObject() : null;
     86         mFeedback = rulesEngine.callOnResizeBegin(mChildNode, mParentNode, newBounds,
     87                 mHorizontalEdge, mVerticalEdge, childView, parentView);
     88         update(pos);
     89         mCanvas.getGestureManager().updateMessage(mFeedback);
     90     }
     91 
     92     @Override
     93     public boolean keyPressed(KeyEvent event) {
     94         update(mCanvas.getGestureManager().getCurrentControlPoint());
     95         mCanvas.redraw();
     96         return true;
     97     }
     98 
     99     @Override
    100     public boolean keyReleased(KeyEvent event) {
    101         update(mCanvas.getGestureManager().getCurrentControlPoint());
    102         mCanvas.redraw();
    103         return true;
    104     }
    105 
    106     @Override
    107     public void update(ControlPoint pos) {
    108         super.update(pos);
    109         RulesEngine rulesEngine = mCanvas.getRulesEngine();
    110         Rect newBounds = getNewBounds(pos);
    111         int modifierMask = mCanvas.getGestureManager().getRuleModifierMask();
    112         rulesEngine.callOnResizeUpdate(mFeedback, mChildNode, mParentNode, newBounds,
    113                 modifierMask);
    114         mCanvas.getGestureManager().updateMessage(mFeedback);
    115     }
    116 
    117     @Override
    118     public void end(ControlPoint pos, boolean canceled) {
    119         super.end(pos, canceled);
    120 
    121         if (!canceled) {
    122             RulesEngine rulesEngine = mCanvas.getRulesEngine();
    123             Rect newBounds = getNewBounds(pos);
    124             rulesEngine.callOnResizeEnd(mFeedback, mChildNode, mParentNode, newBounds);
    125         }
    126 
    127         mCanvas.getSelectionOverlay().setHidden(false);
    128     }
    129 
    130     @Override
    131     public Pair<Boolean, Boolean> getTooltipPosition() {
    132         return Pair.of(mHorizontalEdge != SegmentType.TOP, mVerticalEdge != SegmentType.LEFT);
    133     }
    134 
    135     /**
    136      * For the new mouse position, compute the resized bounds (the bounding rectangle that
    137      * the view should be resized to). This is not just a width or height, since in some
    138      * cases resizing will change the x/y position of the view as well (for example, in
    139      * RelativeLayout or in AbsoluteLayout).
    140      */
    141     private Rect getNewBounds(ControlPoint pos) {
    142         LayoutPoint p = pos.toLayout();
    143         LayoutPoint start = mStart.toLayout();
    144         Rect b = mChildNode.getBounds();
    145         Position direction = mHandle.getPosition();
    146 
    147         int x = b.x;
    148         int y = b.y;
    149         int w = b.w;
    150         int h = b.h;
    151         int deltaX = p.x - start.x;
    152         int deltaY = p.y - start.y;
    153 
    154         if (deltaX == 0 && deltaY == 0) {
    155             // No move - just use the existing bounds
    156             return b;
    157         }
    158 
    159         if (mResizePolicy.isAspectPreserving() && w != 0 && h != 0) {
    160             double aspectRatio = w / (double) h;
    161             int newW = Math.abs(b.w + (direction.isLeft() ? -deltaX : deltaX));
    162             int newH = Math.abs(b.h + (direction.isTop() ? -deltaY : deltaY));
    163             double newAspectRatio = newW / (double) newH;
    164             if (newH == 0 || newAspectRatio > aspectRatio) {
    165                 deltaY = (int) (deltaX / aspectRatio);
    166             } else {
    167                 deltaX = (int) (deltaY * aspectRatio);
    168             }
    169         }
    170         if (direction.isLeft()) {
    171             // The user is dragging the left edge, so the position is anchored on the
    172             // right.
    173             int x2 = b.x + b.w;
    174             int nx1 = b.x + deltaX;
    175             if (nx1 <= x2) {
    176                 x = nx1;
    177                 w = x2 - x;
    178             } else {
    179                 w = 0;
    180                 x = x2;
    181             }
    182         } else if (direction.isRight()) {
    183             // The user is dragging the right edge, so the position is anchored on the
    184             // left.
    185             int nx2 = b.x + b.w + deltaX;
    186             if (nx2 >= b.x) {
    187                 w = nx2 - b.x;
    188             } else {
    189                 w = 0;
    190             }
    191         } else {
    192             assert direction == Position.BOTTOM_MIDDLE || direction == Position.TOP_MIDDLE;
    193         }
    194 
    195         if (direction.isTop()) {
    196             // The user is dragging the top edge, so the position is anchored on the
    197             // bottom.
    198             int y2 = b.y + b.h;
    199             int ny1 = b.y + deltaY;
    200             if (ny1 < y2) {
    201                 y = ny1;
    202                 h = y2 - y;
    203             } else {
    204                 h = 0;
    205                 y = y2;
    206             }
    207         } else if (direction.isBottom()) {
    208             // The user is dragging the bottom edge, so the position is anchored on the
    209             // top.
    210             int ny2 = b.y + b.h + deltaY;
    211             if (ny2 >= b.y) {
    212                 h = ny2 - b.y;
    213             } else {
    214                 h = 0;
    215             }
    216         } else {
    217             assert direction == Position.LEFT_MIDDLE || direction == Position.RIGHT_MIDDLE;
    218         }
    219 
    220         return new Rect(x, y, w, h);
    221     }
    222 
    223     private static SegmentType getHorizontalEdgeType(SelectionHandle handle) {
    224         switch (handle.getPosition()) {
    225             case BOTTOM_LEFT:
    226             case BOTTOM_RIGHT:
    227             case BOTTOM_MIDDLE:
    228                 return SegmentType.BOTTOM;
    229             case LEFT_MIDDLE:
    230             case RIGHT_MIDDLE:
    231                 return null;
    232             case TOP_LEFT:
    233             case TOP_MIDDLE:
    234             case TOP_RIGHT:
    235                 return SegmentType.TOP;
    236             default: assert false : handle.getPosition();
    237         }
    238         return null;
    239     }
    240 
    241     private static SegmentType getVerticalEdgeType(SelectionHandle handle) {
    242         switch (handle.getPosition()) {
    243             case TOP_LEFT:
    244             case LEFT_MIDDLE:
    245             case BOTTOM_LEFT:
    246                 return SegmentType.LEFT;
    247             case BOTTOM_MIDDLE:
    248             case TOP_MIDDLE:
    249                 return null;
    250             case TOP_RIGHT:
    251             case RIGHT_MIDDLE:
    252             case BOTTOM_RIGHT:
    253                 return SegmentType.RIGHT;
    254             default: assert false : handle.getPosition();
    255         }
    256         return null;
    257     }
    258 
    259 
    260     @Override
    261     public List<Overlay> createOverlays() {
    262         mOverlay = new ResizeOverlay();
    263         return Collections.<Overlay> singletonList(mOverlay);
    264     }
    265 
    266     /**
    267      * An {@link Overlay} to paint the resize feedback. This just delegates to the
    268      * layout rule for the parent which is handling the resizing.
    269      */
    270     private class ResizeOverlay extends Overlay {
    271         @Override
    272         public void paint(GC gc) {
    273             if (mChildNode != null && mFeedback != null) {
    274                 RulesEngine rulesEngine = mCanvas.getRulesEngine();
    275                 rulesEngine.callDropFeedbackPaint(mCanvas.getGcWrapper(), mChildNode, mFeedback);
    276             }
    277         }
    278     }
    279 }
    280