blob: 4d51c07de5ef91e3fce2c46dd962bd23976c4507 [file] [log] [blame]
/*
* 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);
}
}
}
}