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