| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Eclipse Public License, Version 1.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.eclipse.org/org/documents/epl-v10.php |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ide.eclipse.adt.internal.editors.layout.gle2; |
| |
| import com.android.ide.common.api.DropFeedback; |
| import com.android.ide.common.api.Rect; |
| import com.android.ide.common.api.ResizePolicy; |
| import com.android.ide.common.api.SegmentType; |
| import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.Position; |
| import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; |
| import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; |
| import com.android.utils.Pair; |
| |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.graphics.GC; |
| |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * A {@link ResizeGesture} is a gesture for resizing a selected widget. It is initiated |
| * by a drag of a {@link SelectionHandle}. |
| */ |
| public class ResizeGesture extends Gesture { |
| /** The {@link Overlay} drawn for the gesture feedback. */ |
| private ResizeOverlay mOverlay; |
| |
| /** The canvas associated with this gesture. */ |
| private LayoutCanvas mCanvas; |
| |
| /** The selection handle we're dragging to perform this resize */ |
| private SelectionHandle mHandle; |
| |
| private NodeProxy mParentNode; |
| private NodeProxy mChildNode; |
| private DropFeedback mFeedback; |
| private ResizePolicy mResizePolicy; |
| private SegmentType mHorizontalEdge; |
| private SegmentType mVerticalEdge; |
| |
| /** |
| * Creates a new marquee selection (selection swiping). |
| * |
| * @param canvas The canvas where selection is performed. |
| * @param item The selected item the handle corresponds to |
| * @param handle The handle being dragged to perform the resize |
| */ |
| public ResizeGesture(LayoutCanvas canvas, SelectionItem item, SelectionHandle handle) { |
| mCanvas = canvas; |
| mHandle = handle; |
| |
| mChildNode = item.getNode(); |
| mParentNode = (NodeProxy) mChildNode.getParent(); |
| mResizePolicy = item.getResizePolicy(); |
| mHorizontalEdge = getHorizontalEdgeType(mHandle); |
| mVerticalEdge = getVerticalEdgeType(mHandle); |
| } |
| |
| @Override |
| public void begin(ControlPoint pos, int startMask) { |
| super.begin(pos, startMask); |
| |
| mCanvas.getSelectionOverlay().setHidden(true); |
| |
| RulesEngine rulesEngine = mCanvas.getRulesEngine(); |
| Rect newBounds = getNewBounds(pos); |
| ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); |
| CanvasViewInfo childInfo = viewHierarchy.findViewInfoFor(mChildNode); |
| CanvasViewInfo parentInfo = viewHierarchy.findViewInfoFor(mParentNode); |
| Object childView = childInfo != null ? childInfo.getViewObject() : null; |
| Object parentView = parentInfo != null ? parentInfo.getViewObject() : null; |
| mFeedback = rulesEngine.callOnResizeBegin(mChildNode, mParentNode, newBounds, |
| mHorizontalEdge, mVerticalEdge, childView, parentView); |
| update(pos); |
| mCanvas.getGestureManager().updateMessage(mFeedback); |
| } |
| |
| @Override |
| public boolean keyPressed(KeyEvent event) { |
| update(mCanvas.getGestureManager().getCurrentControlPoint()); |
| mCanvas.redraw(); |
| return true; |
| } |
| |
| @Override |
| public boolean keyReleased(KeyEvent event) { |
| update(mCanvas.getGestureManager().getCurrentControlPoint()); |
| mCanvas.redraw(); |
| return true; |
| } |
| |
| @Override |
| public void update(ControlPoint pos) { |
| super.update(pos); |
| RulesEngine rulesEngine = mCanvas.getRulesEngine(); |
| Rect newBounds = getNewBounds(pos); |
| int modifierMask = mCanvas.getGestureManager().getRuleModifierMask(); |
| rulesEngine.callOnResizeUpdate(mFeedback, mChildNode, mParentNode, newBounds, |
| modifierMask); |
| mCanvas.getGestureManager().updateMessage(mFeedback); |
| } |
| |
| @Override |
| public void end(ControlPoint pos, boolean canceled) { |
| super.end(pos, canceled); |
| |
| if (!canceled) { |
| RulesEngine rulesEngine = mCanvas.getRulesEngine(); |
| Rect newBounds = getNewBounds(pos); |
| rulesEngine.callOnResizeEnd(mFeedback, mChildNode, mParentNode, newBounds); |
| } |
| |
| mCanvas.getSelectionOverlay().setHidden(false); |
| } |
| |
| @Override |
| public Pair<Boolean, Boolean> getTooltipPosition() { |
| return Pair.of(mHorizontalEdge != SegmentType.TOP, mVerticalEdge != SegmentType.LEFT); |
| } |
| |
| /** |
| * For the new mouse position, compute the resized bounds (the bounding rectangle that |
| * the view should be resized to). This is not just a width or height, since in some |
| * cases resizing will change the x/y position of the view as well (for example, in |
| * RelativeLayout or in AbsoluteLayout). |
| */ |
| private Rect getNewBounds(ControlPoint pos) { |
| LayoutPoint p = pos.toLayout(); |
| LayoutPoint start = mStart.toLayout(); |
| Rect b = mChildNode.getBounds(); |
| Position direction = mHandle.getPosition(); |
| |
| int x = b.x; |
| int y = b.y; |
| int w = b.w; |
| int h = b.h; |
| int deltaX = p.x - start.x; |
| int deltaY = p.y - start.y; |
| |
| if (deltaX == 0 && deltaY == 0) { |
| // No move - just use the existing bounds |
| return b; |
| } |
| |
| if (mResizePolicy.isAspectPreserving() && w != 0 && h != 0) { |
| double aspectRatio = w / (double) h; |
| int newW = Math.abs(b.w + (direction.isLeft() ? -deltaX : deltaX)); |
| int newH = Math.abs(b.h + (direction.isTop() ? -deltaY : deltaY)); |
| double newAspectRatio = newW / (double) newH; |
| if (newH == 0 || newAspectRatio > aspectRatio) { |
| deltaY = (int) (deltaX / aspectRatio); |
| } else { |
| deltaX = (int) (deltaY * aspectRatio); |
| } |
| } |
| if (direction.isLeft()) { |
| // The user is dragging the left edge, so the position is anchored on the |
| // right. |
| int x2 = b.x + b.w; |
| int nx1 = b.x + deltaX; |
| if (nx1 <= x2) { |
| x = nx1; |
| w = x2 - x; |
| } else { |
| w = 0; |
| x = x2; |
| } |
| } else if (direction.isRight()) { |
| // The user is dragging the right edge, so the position is anchored on the |
| // left. |
| int nx2 = b.x + b.w + deltaX; |
| if (nx2 >= b.x) { |
| w = nx2 - b.x; |
| } else { |
| w = 0; |
| } |
| } else { |
| assert direction == Position.BOTTOM_MIDDLE || direction == Position.TOP_MIDDLE; |
| } |
| |
| if (direction.isTop()) { |
| // The user is dragging the top edge, so the position is anchored on the |
| // bottom. |
| int y2 = b.y + b.h; |
| int ny1 = b.y + deltaY; |
| if (ny1 < y2) { |
| y = ny1; |
| h = y2 - y; |
| } else { |
| h = 0; |
| y = y2; |
| } |
| } else if (direction.isBottom()) { |
| // The user is dragging the bottom edge, so the position is anchored on the |
| // top. |
| int ny2 = b.y + b.h + deltaY; |
| if (ny2 >= b.y) { |
| h = ny2 - b.y; |
| } else { |
| h = 0; |
| } |
| } else { |
| assert direction == Position.LEFT_MIDDLE || direction == Position.RIGHT_MIDDLE; |
| } |
| |
| return new Rect(x, y, w, h); |
| } |
| |
| private static SegmentType getHorizontalEdgeType(SelectionHandle handle) { |
| switch (handle.getPosition()) { |
| case BOTTOM_LEFT: |
| case BOTTOM_RIGHT: |
| case BOTTOM_MIDDLE: |
| return SegmentType.BOTTOM; |
| case LEFT_MIDDLE: |
| case RIGHT_MIDDLE: |
| return null; |
| case TOP_LEFT: |
| case TOP_MIDDLE: |
| case TOP_RIGHT: |
| return SegmentType.TOP; |
| default: assert false : handle.getPosition(); |
| } |
| return null; |
| } |
| |
| private static SegmentType getVerticalEdgeType(SelectionHandle handle) { |
| switch (handle.getPosition()) { |
| case TOP_LEFT: |
| case LEFT_MIDDLE: |
| case BOTTOM_LEFT: |
| return SegmentType.LEFT; |
| case BOTTOM_MIDDLE: |
| case TOP_MIDDLE: |
| return null; |
| case TOP_RIGHT: |
| case RIGHT_MIDDLE: |
| case BOTTOM_RIGHT: |
| return SegmentType.RIGHT; |
| default: assert false : handle.getPosition(); |
| } |
| return null; |
| } |
| |
| |
| @Override |
| public List<Overlay> createOverlays() { |
| mOverlay = new ResizeOverlay(); |
| return Collections.<Overlay> singletonList(mOverlay); |
| } |
| |
| /** |
| * An {@link Overlay} to paint the resize feedback. This just delegates to the |
| * layout rule for the parent which is handling the resizing. |
| */ |
| private class ResizeOverlay extends Overlay { |
| @Override |
| public void paint(GC gc) { |
| if (mChildNode != null && mFeedback != null) { |
| RulesEngine rulesEngine = mCanvas.getRulesEngine(); |
| rulesEngine.callDropFeedbackPaint(mCanvas.getGcWrapper(), mChildNode, mFeedback); |
| } |
| } |
| } |
| } |