/* * 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); } } } }