blob: a5e071d7407a50202681150eed4350490375f024 [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.common.layout.relative;
import static com.android.ide.common.api.MarginType.NO_MARGIN;
import static com.android.ide.common.api.SegmentType.BASELINE;
import static com.android.ide.common.api.SegmentType.BOTTOM;
import static com.android.ide.common.api.SegmentType.CENTER_HORIZONTAL;
import static com.android.ide.common.api.SegmentType.CENTER_VERTICAL;
import static com.android.ide.common.api.SegmentType.LEFT;
import static com.android.ide.common.api.SegmentType.RIGHT;
import static com.android.ide.common.api.SegmentType.TOP;
import static com.android.SdkConstants.ATTR_ID;
import static java.lang.Math.abs;
import com.android.SdkConstants;
import static com.android.SdkConstants.ANDROID_URI;
import com.android.ide.common.api.DropFeedback;
import com.android.ide.common.api.IClientRulesEngine;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.Rect;
import com.android.ide.common.api.Segment;
import com.android.ide.common.api.SegmentType;
import com.android.ide.common.layout.BaseLayoutRule;
import java.util.Collections;
import java.util.Set;
/**
* A {@link ResizeHandler} is a {@link GuidelineHandler} which handles resizing of individual
* edges in a RelativeLayout.
*/
public class ResizeHandler extends GuidelineHandler {
private final SegmentType mHorizontalEdgeType;
private final SegmentType mVerticalEdgeType;
/**
* Creates a new {@link ResizeHandler}
*
* @param layout the layout containing the resized node
* @param resized the node being resized
* @param rulesEngine the applicable {@link IClientRulesEngine}
* @param horizontalEdgeType the type of horizontal edge being resized, or null
* @param verticalEdgeType the type of vertical edge being resized, or null
*/
public ResizeHandler(INode layout, INode resized,
IClientRulesEngine rulesEngine,
SegmentType horizontalEdgeType, SegmentType verticalEdgeType) {
super(layout, rulesEngine);
assert horizontalEdgeType != null || verticalEdgeType != null;
assert horizontalEdgeType != BASELINE && verticalEdgeType != BASELINE;
assert horizontalEdgeType != CENTER_HORIZONTAL && verticalEdgeType != CENTER_HORIZONTAL;
assert horizontalEdgeType != CENTER_VERTICAL && verticalEdgeType != CENTER_VERTICAL;
mHorizontalEdgeType = horizontalEdgeType;
mVerticalEdgeType = verticalEdgeType;
Set<INode> nodes = Collections.singleton(resized);
mDraggedNodes = nodes;
mHorizontalDeps = mDependencyGraph.dependsOn(nodes, false /* vertical */);
mVerticalDeps = mDependencyGraph.dependsOn(nodes, true /* vertical */);
if (horizontalEdgeType != null) {
if (horizontalEdgeType == TOP) {
mMoveTop = true;
} else if (horizontalEdgeType == BOTTOM) {
mMoveBottom = true;
}
}
if (verticalEdgeType != null) {
if (verticalEdgeType == LEFT) {
mMoveLeft = true;
} else if (verticalEdgeType == RIGHT) {
mMoveRight = true;
}
}
for (INode child : layout.getChildren()) {
if (child != resized) {
String id = child.getStringAttr(ANDROID_URI, ATTR_ID);
addBounds(child, id,
!mHorizontalDeps.contains(child),
!mVerticalDeps.contains(child));
}
}
addBounds(layout, layout.getStringAttr(ANDROID_URI, ATTR_ID), true, true);
}
@Override
protected void snapVertical(Segment vEdge, int x, Rect newBounds) {
int maxDistance = BaseLayoutRule.getMaxMatchDistance();
if (vEdge.edgeType == LEFT) {
int margin = mSnap ? 0 : abs(newBounds.x - x);
if (margin > maxDistance) {
mLeftMargin = margin;
} else {
newBounds.w += newBounds.x - x;
newBounds.x = x;
}
} else if (vEdge.edgeType == RIGHT) {
int margin = mSnap ? 0 : abs(newBounds.x - (x - newBounds.w));
if (margin > maxDistance) {
mRightMargin = margin;
} else {
newBounds.w = x - newBounds.x;
}
} else {
assert false : vEdge;
}
}
@Override
protected void snapHorizontal(Segment hEdge, int y, Rect newBounds) {
int maxDistance = BaseLayoutRule.getMaxMatchDistance();
if (hEdge.edgeType == TOP) {
int margin = mSnap ? 0 : abs(newBounds.y - y);
if (margin > maxDistance) {
mTopMargin = margin;
} else {
newBounds.h += newBounds.y - y;
newBounds.y = y;
}
} else if (hEdge.edgeType == BOTTOM) {
int margin = mSnap ? 0 : abs(newBounds.y - (y - newBounds.h));
if (margin > maxDistance) {
mBottomMargin = margin;
} else {
newBounds.h = y - newBounds.y;
}
} else {
assert false : hEdge;
}
}
@Override
protected boolean isEdgeTypeCompatible(SegmentType edge, SegmentType dragged, int delta) {
boolean compatible = super.isEdgeTypeCompatible(edge, dragged, delta);
// When resizing and not snapping (e.g. using margins to pick a specific pixel
// width) we cannot use -negative- margins to jump back to a closer edge; we
// must always use positive margins, so mark closer edges that result in a negative
// margin as not compatible.
if (compatible && !mSnap) {
switch (dragged) {
case LEFT:
case TOP:
return delta <= 0;
default:
return delta >= 0;
}
}
return compatible;
}
/**
* Updates the handler for the given mouse resize
*
* @param feedback the feedback handler
* @param child the node being resized
* @param newBounds the new bounds of the resize rectangle
* @param modifierMask the keyboard modifiers pressed during the drag
*/
public void updateResize(DropFeedback feedback, INode child, Rect newBounds,
int modifierMask) {
mSnap = (modifierMask & DropFeedback.MODIFIER2) == 0;
mBounds = newBounds;
clearSuggestions();
Rect b = newBounds;
Segment hEdge = null;
Segment vEdge = null;
String childId = child.getStringAttr(ANDROID_URI, ATTR_ID);
// TODO: MarginType=NO_MARGIN may not be right. Consider resizing a widget
// that has margins and how that should be handled.
if (mHorizontalEdgeType == TOP) {
hEdge = new Segment(b.y, b.x, b.x2(), child, childId, mHorizontalEdgeType, NO_MARGIN);
} else if (mHorizontalEdgeType == BOTTOM) {
hEdge = new Segment(b.y2(), b.x, b.x2(), child, childId, mHorizontalEdgeType,
NO_MARGIN);
} else {
assert mHorizontalEdgeType == null;
}
if (mVerticalEdgeType == LEFT) {
vEdge = new Segment(b.x, b.y, b.y2(), child, childId, mVerticalEdgeType, NO_MARGIN);
} else if (mVerticalEdgeType == RIGHT) {
vEdge = new Segment(b.x2(), b.y, b.y2(), child, childId, mVerticalEdgeType, NO_MARGIN);
} else {
assert mVerticalEdgeType == null;
}
mTopMargin = mBottomMargin = mLeftMargin = mRightMargin = 0;
if (hEdge != null && mHorizontalEdges.size() > 0) {
// Compute horizontal matches
mHorizontalSuggestions = findClosest(hEdge, mHorizontalEdges);
Match match = pickBestMatch(mHorizontalSuggestions);
if (match != null
&& (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) {
if (mHorizontalDeps.contains(match.edge.node)) {
match.cycle = true;
}
snapHorizontal(hEdge, match.edge.at, newBounds);
if (hEdge.edgeType == TOP) {
mCurrentTopMatch = match;
} else if (hEdge.edgeType == BOTTOM) {
mCurrentBottomMatch = match;
} else {
assert hEdge.edgeType == CENTER_HORIZONTAL
|| hEdge.edgeType == BASELINE : hEdge;
mCurrentTopMatch = match;
}
}
}
if (vEdge != null && mVerticalEdges.size() > 0) {
mVerticalSuggestions = findClosest(vEdge, mVerticalEdges);
Match match = pickBestMatch(mVerticalSuggestions);
if (match != null
&& (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) {
if (mVerticalDeps.contains(match.edge.node)) {
match.cycle = true;
}
// Snap
snapVertical(vEdge, match.edge.at, newBounds);
if (vEdge.edgeType == LEFT) {
mCurrentLeftMatch = match;
} else if (vEdge.edgeType == RIGHT) {
mCurrentRightMatch = match;
} else {
assert vEdge.edgeType == CENTER_VERTICAL;
mCurrentLeftMatch = match;
}
}
}
checkCycles(feedback);
}
}