blob: 343d18e6a15555135c7acfc9b53ab6218f6dda65 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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.intellij.android.designer.model.layout.relative;
import com.android.tools.idea.designer.Segment;
import com.android.tools.idea.designer.SegmentType;
import com.intellij.android.designer.model.RadViewComponent;
import com.intellij.designer.designSurface.OperationContext;
import com.intellij.designer.model.RadComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.awt.event.InputEvent;
import java.util.Collections;
import java.util.Set;
import static com.android.tools.idea.designer.MarginType.NO_MARGIN;
import static com.intellij.android.designer.designSurface.graphics.DrawingStyle.MAX_MATCH_DISTANCE;
import static java.lang.Math.abs;
/**
* A {@link ResizeHandler} is a {@link GuidelineHandler} which handles resizing of individual
* edges in a RelativeLayout.
*/
public class ResizeHandler extends GuidelineHandler {
@Nullable private final SegmentType myHorizontalEdgeType;
@Nullable private final SegmentType myVerticalEdgeType;
/**
* Creates a new {@link ResizeHandler}
*
* @param layout the layout containing the resized node
* @param resized the node being resized
* @param context the applicable {@link OperationContext}
* @param horizontalEdgeType the type of horizontal edge being resized, or null
* @param verticalEdgeType the type of vertical edge being resized, or null
*/
public ResizeHandler(@NotNull RadViewComponent layout,
@NotNull RadViewComponent resized,
@NotNull OperationContext context,
@Nullable SegmentType horizontalEdgeType,
@Nullable SegmentType verticalEdgeType) {
super(layout, context);
assert horizontalEdgeType != null || verticalEdgeType != null;
assert horizontalEdgeType != SegmentType.BASELINE && verticalEdgeType != SegmentType.BASELINE;
assert horizontalEdgeType != SegmentType.CENTER_HORIZONTAL && verticalEdgeType != SegmentType.CENTER_HORIZONTAL;
assert horizontalEdgeType != SegmentType.CENTER_VERTICAL && verticalEdgeType != SegmentType.CENTER_VERTICAL;
myHorizontalEdgeType = horizontalEdgeType;
myVerticalEdgeType = verticalEdgeType;
Set<RadViewComponent> nodes = Collections.singleton(resized);
myDraggedNodes = nodes;
myHorizontalDeps = myDependencyGraph.dependsOn(nodes, false /* vertical */);
myVerticalDeps = myDependencyGraph.dependsOn(nodes, true /* vertical */);
if (horizontalEdgeType != null) {
if (horizontalEdgeType == SegmentType.TOP) {
myMoveTop = true;
}
else if (horizontalEdgeType == SegmentType.BOTTOM) {
myMoveBottom = true;
}
}
if (verticalEdgeType != null) {
if (myTextDirection.isLeftSegment(verticalEdgeType)) {
myMoveLeft = true;
}
else if (myTextDirection.isRightSegment(verticalEdgeType)) {
myMoveRight = true;
}
}
for (RadComponent child : layout.getChildren()) {
if (child != resized && child instanceof RadViewComponent) {
RadViewComponent view = (RadViewComponent)child;
String id = view.getId();
addBounds(view, id, !myHorizontalDeps.contains(view), !myVerticalDeps.contains(view), false /*includePadding*/);
}
}
addBounds(layout, layout.getId(), true, true, true /*includePadding*/);
}
@Override
protected void snapVertical(Segment vEdge, int x, Rectangle newBounds) {
int maxDistance = MAX_MATCH_DISTANCE;
if (vEdge.edgeType == SegmentType.LEFT) {
int margin = mySnap ? 0 : abs(newBounds.x - x);
if (margin > maxDistance) {
myLeftMargin = margin;
}
else {
newBounds.width += newBounds.x - x;
newBounds.x = x;
}
}
else if (vEdge.edgeType == SegmentType.RIGHT) {
int margin = mySnap ? 0 : abs(newBounds.x - (x - newBounds.width));
if (margin > maxDistance) {
myRightMargin = margin;
}
else {
newBounds.width = x - newBounds.x;
}
}
else {
assert false : vEdge;
}
}
@Override
protected void snapHorizontal(Segment hEdge, int y, Rectangle newBounds) {
int maxDistance = MAX_MATCH_DISTANCE;
if (hEdge.edgeType == SegmentType.TOP) {
int margin = mySnap ? 0 : abs(newBounds.y - y);
if (margin > maxDistance) {
myTopMargin = margin;
}
else {
newBounds.height += newBounds.y - y;
newBounds.y = y;
}
}
else if (hEdge.edgeType == SegmentType.BOTTOM) {
int margin = mySnap ? 0 : abs(newBounds.y - (y - newBounds.height));
if (margin > maxDistance) {
myBottomMargin = margin;
}
else {
newBounds.height = 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 && !mySnap) {
switch (dragged) {
case LEFT:
case TOP:
return delta <= 0;
default:
return delta >= 0;
}
}
return compatible;
}
/**
* Updates the handler for the given mouse resize
*
* @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(RadViewComponent child, Rectangle newBounds, int modifierMask) {
mySnap = (modifierMask & InputEvent.SHIFT_MASK) == 0;
myBounds = newBounds;
clearSuggestions();
@SuppressWarnings("UnnecessaryLocalVariable") // To make arithmetic with x/y/w/h below fit on one line
Rectangle b = newBounds;
Segment hEdge = null;
Segment vEdge = null;
String childId = child.getId();
// TODO: MarginType=NO_MARGIN may not be right. Consider resizing a widget
// that has margins and how that should be handled.
if (myHorizontalEdgeType == SegmentType.TOP) {
hEdge = new Segment(b.y, b.x, x2(b), child, childId, myHorizontalEdgeType, NO_MARGIN);
}
else if (myHorizontalEdgeType == SegmentType.BOTTOM) {
hEdge = new Segment(y2(b), b.x, x2(b), child, childId, myHorizontalEdgeType, NO_MARGIN);
}
else {
assert myHorizontalEdgeType == null;
}
if (myVerticalEdgeType != null && myTextDirection.isLeftSegment(myVerticalEdgeType)) {
vEdge = new Segment(b.x, b.y, y2(b), child, childId, myVerticalEdgeType, NO_MARGIN);
}
else if (myVerticalEdgeType != null && myTextDirection.isRightSegment(myVerticalEdgeType)) {
vEdge = new Segment(x2(b), b.y, y2(b), child, childId, myVerticalEdgeType, NO_MARGIN);
}
else {
assert myVerticalEdgeType == null;
}
myTopMargin = myBottomMargin = myLeftMargin = myRightMargin = 0;
if (hEdge != null && myHorizontalEdges.size() > 0) {
// Compute horizontal matches
myHorizontalSuggestions = findClosest(hEdge, myHorizontalEdges);
Match match = pickBestMatch(myHorizontalSuggestions);
if (match != null && (!mySnap || abs(match.delta) < MAX_MATCH_DISTANCE)) {
if (myHorizontalDeps.contains(match.edge.node)) {
match.cycle = true;
}
snapHorizontal(hEdge, match.edge.at, newBounds);
if (hEdge.edgeType == SegmentType.TOP) {
myCurrentTopMatch = match;
}
else if (hEdge.edgeType == SegmentType.BOTTOM) {
myCurrentBottomMatch = match;
}
else {
assert hEdge.edgeType == SegmentType.CENTER_HORIZONTAL || hEdge.edgeType == SegmentType.BASELINE : hEdge;
myCurrentTopMatch = match;
}
}
}
if (vEdge != null && myVerticalEdges.size() > 0) {
myVerticalSuggestions = findClosest(vEdge, myVerticalEdges);
Match match = pickBestMatch(myVerticalSuggestions);
if (match != null && (!mySnap || abs(match.delta) < MAX_MATCH_DISTANCE)) {
if (myVerticalDeps.contains(match.edge.node)) {
match.cycle = true;
}
// Snap
snapVertical(vEdge, match.edge.at, newBounds);
if (myTextDirection.isLeftSegment(vEdge.edgeType)) {
myCurrentLeftMatch = match;
}
else if (myTextDirection.isRightSegment(vEdge.edgeType)) {
myCurrentRightMatch = match;
}
else {
assert vEdge.edgeType == SegmentType.CENTER_VERTICAL;
myCurrentLeftMatch = match;
}
}
}
checkCycles();
}
}