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