OSDN Git Service

Add resize feedback tooltip
[android-x86/sdk.git] / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / editors / layout / gle2 / ResizeGesture.java
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.util.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         mFeedback = rulesEngine.callOnResizeBegin(mChildNode, mParentNode, newBounds,
82                 mHorizontalEdge, mVerticalEdge);
83         mCanvas.getGestureManager().updateMessage(mFeedback);
84     }
85
86     @Override
87     public boolean keyPressed(KeyEvent event) {
88         update(mCanvas.getGestureManager().getCurrentControlPoint());
89         mCanvas.redraw();
90         return true;
91     }
92
93     @Override
94     public boolean keyReleased(KeyEvent event) {
95         update(mCanvas.getGestureManager().getCurrentControlPoint());
96         mCanvas.redraw();
97         return true;
98     }
99
100     @Override
101     public void update(ControlPoint pos) {
102         super.update(pos);
103         RulesEngine rulesEngine = mCanvas.getRulesEngine();
104         Rect newBounds = getNewBounds(pos);
105         int modifierMask = mCanvas.getGestureManager().getRuleModifierMask();
106         rulesEngine.callOnResizeUpdate(mFeedback, mChildNode, mParentNode, newBounds,
107                 modifierMask);
108         mCanvas.getGestureManager().updateMessage(mFeedback);
109     }
110
111     @Override
112     public void end(ControlPoint pos, boolean canceled) {
113         super.end(pos, canceled);
114
115         if (!canceled) {
116             RulesEngine rulesEngine = mCanvas.getRulesEngine();
117             Rect newBounds = getNewBounds(pos);
118             rulesEngine.callOnResizeEnd(mFeedback, mChildNode, mParentNode, newBounds);
119         }
120
121         mCanvas.getSelectionOverlay().setHidden(false);
122     }
123
124     @Override
125     public Pair<Boolean, Boolean> getTooltipPosition() {
126         return Pair.of(mHorizontalEdge != SegmentType.TOP, mVerticalEdge != SegmentType.LEFT);
127     }
128
129     /**
130      * For the new mouse position, compute the resized bounds (the bounding rectangle that
131      * the view should be resized to). This is not just a width or height, since in some
132      * cases resizing will change the x/y position of the view as well (for example, in
133      * RelativeLayout or in AbsoluteLayout).
134      */
135     private Rect getNewBounds(ControlPoint pos) {
136         LayoutPoint p = pos.toLayout();
137         LayoutPoint start = mStart.toLayout();
138         Rect b = mChildNode.getBounds();
139         Position direction = mHandle.getPosition();
140
141         int x = b.x;
142         int y = b.y;
143         int w = b.w;
144         int h = b.h;
145         int deltaX = p.x - start.x;
146         int deltaY = p.y - start.y;
147
148         if (deltaX == 0 && deltaY == 0) {
149             // No move - just use the existing bounds
150             return b;
151         }
152
153         if (mResizePolicy.isAspectPreserving() && w != 0 && h != 0) {
154             double aspectRatio = w / (double) h;
155             int newW = Math.abs(b.w + (direction.isLeft() ? -deltaX : deltaX));
156             int newH = Math.abs(b.h + (direction.isTop() ? -deltaY : deltaY));
157             double newAspectRatio = newW / (double) newH;
158             if (newH == 0 || newAspectRatio > aspectRatio) {
159                 deltaY = (int) (deltaX / aspectRatio);
160             } else {
161                 deltaX = (int) (deltaY * aspectRatio);
162             }
163         }
164         if (direction.isLeft()) {
165             // The user is dragging the left edge, so the position is anchored on the
166             // right.
167             int x2 = b.x + b.w;
168             int nx1 = b.x + deltaX;
169             if (nx1 <= x2) {
170                 x = nx1;
171                 w = x2 - x;
172             } else {
173                 w = 0;
174                 x = x2;
175             }
176         } else if (direction.isRight()) {
177             // The user is dragging the right edge, so the position is anchored on the
178             // left.
179             int nx2 = b.x + b.w + deltaX;
180             if (nx2 >= b.x) {
181                 w = nx2 - b.x;
182             } else {
183                 w = 0;
184             }
185         } else {
186             assert direction == Position.BOTTOM_MIDDLE || direction == Position.TOP_MIDDLE;
187         }
188
189         if (direction.isTop()) {
190             // The user is dragging the top edge, so the position is anchored on the
191             // bottom.
192             int y2 = b.y + b.h;
193             int ny1 = b.y + deltaY;
194             if (ny1 < y2) {
195                 y = ny1;
196                 h = y2 - y;
197             } else {
198                 h = 0;
199                 y = y2;
200             }
201         } else if (direction.isBottom()) {
202             // The user is dragging the bottom edge, so the position is anchored on the
203             // top.
204             int ny2 = b.y + b.h + deltaY;
205             if (ny2 >= b.y) {
206                 h = ny2 - b.y;
207             } else {
208                 h = 0;
209             }
210         } else {
211             assert direction == Position.LEFT_MIDDLE || direction == Position.RIGHT_MIDDLE;
212         }
213
214         return new Rect(x, y, w, h);
215     }
216
217     private static SegmentType getHorizontalEdgeType(SelectionHandle handle) {
218         switch (handle.getPosition()) {
219             case BOTTOM_LEFT:
220             case BOTTOM_RIGHT:
221             case BOTTOM_MIDDLE:
222                 return SegmentType.BOTTOM;
223             case LEFT_MIDDLE:
224             case RIGHT_MIDDLE:
225                 return null;
226             case TOP_LEFT:
227             case TOP_MIDDLE:
228             case TOP_RIGHT:
229                 return SegmentType.TOP;
230             default: assert false : handle.getPosition();
231         }
232         return null;
233     }
234
235     private static SegmentType getVerticalEdgeType(SelectionHandle handle) {
236         switch (handle.getPosition()) {
237             case TOP_LEFT:
238             case LEFT_MIDDLE:
239             case BOTTOM_LEFT:
240                 return SegmentType.LEFT;
241             case BOTTOM_MIDDLE:
242             case TOP_MIDDLE:
243                 return null;
244             case TOP_RIGHT:
245             case RIGHT_MIDDLE:
246             case BOTTOM_RIGHT:
247                 return SegmentType.RIGHT;
248             default: assert false : handle.getPosition();
249         }
250         return null;
251     }
252
253
254     @Override
255     public List<Overlay> createOverlays() {
256         mOverlay = new ResizeOverlay();
257         return Collections.<Overlay> singletonList(mOverlay);
258     }
259
260     /**
261      * An {@link Overlay} to paint the resize feedback. This just delegates to the
262      * layout rule for the parent which is handling the resizing.
263      */
264     private class ResizeOverlay extends Overlay {
265         @Override
266         public void paint(GC gc) {
267             if (mChildNode != null && mFeedback != null) {
268                 RulesEngine rulesEngine = mCanvas.getRulesEngine();
269                 rulesEngine.callDropFeedbackPaint(mCanvas.getGcWrapper(), mChildNode, mFeedback);
270             }
271         }
272     }
273 }