diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/MoveHandler.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/MoveHandler.java | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/MoveHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/MoveHandler.java new file mode 100644 index 000000000..0fa915d81 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/MoveHandler.java @@ -0,0 +1,299 @@ +/* + * 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.IDragElement; +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.layout.BaseLayoutRule; +import com.android.ide.common.layout.relative.DependencyGraph.ViewData; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link MoveHandler} is a {@link GuidelineHandler} which handles move and drop + * gestures, and offers guideline suggestions and snapping. + * <p> + * Unlike the {@link ResizeHandler}, the {@link MoveHandler} looks for matches for all + * different segment types -- the left edge, the right edge, the baseline, the center + * edges, and so on -- and picks the best among these. + */ +public class MoveHandler extends GuidelineHandler { + private int mDraggedBaseline; + + /** + * Creates a new {@link MoveHandler}. + * + * @param layout the layout element the handler is operating on + * @param elements the elements being dragged in the move operation + * @param rulesEngine the corresponding {@link IClientRulesEngine} + */ + public MoveHandler(INode layout, IDragElement[] elements, IClientRulesEngine rulesEngine) { + super(layout, rulesEngine); + + // Compute list of nodes being dragged within the layout, if any + List<INode> nodes = new ArrayList<INode>(); + for (IDragElement element : elements) { + ViewData view = mDependencyGraph.getView(element); + if (view != null) { + nodes.add(view.node); + } + } + mDraggedNodes = nodes; + + mHorizontalDeps = mDependencyGraph.dependsOn(nodes, false /* verticalEdge */); + mVerticalDeps = mDependencyGraph.dependsOn(nodes, true /* verticalEdge */); + + for (INode child : layout.getChildren()) { + Rect bc = child.getBounds(); + if (bc.isValid()) { + // First see if this node looks like it's the same as one of the + // *dragged* bounds + boolean isDragged = false; + for (IDragElement element : elements) { + // This tries to determine if an INode corresponds to an + // IDragElement, by comparing their bounds. + if (bc.equals(element.getBounds())) { + isDragged = true; + } + } + + if (!isDragged) { + String id = child.getStringAttr(ANDROID_URI, ATTR_ID); + // It's okay for id to be null; if you apply a constraint + // to a node with a missing id we will generate the id + + boolean addHorizontal = !mHorizontalDeps.contains(child); + boolean addVertical = !mVerticalDeps.contains(child); + + addBounds(child, id, addHorizontal, addVertical); + if (addHorizontal) { + addBaseLine(child, id); + } + } + } + } + + String id = layout.getStringAttr(ANDROID_URI, ATTR_ID); + addBounds(layout, id, true, true); + addCenter(layout, 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.x = x; + } + } else if (vEdge.edgeType == RIGHT) { + int margin = !mSnap ? 0 : abs(newBounds.x - (x - newBounds.w)); + if (margin > maxDistance) { + mRightMargin = margin; + } else { + newBounds.x = x - newBounds.w; + } + } else if (vEdge.edgeType == CENTER_VERTICAL) { + newBounds.x = x - newBounds.w / 2; + } else { + assert false : vEdge; + } + } + + // TODO: Consider unifying this with the snapping logic in ResizeHandler + @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.y = y; + } + } else if (hEdge.edgeType == BOTTOM) { + int margin = !mSnap ? 0 : abs(newBounds.y - (y - newBounds.h)); + if (margin > maxDistance) { + mBottomMargin = margin; + } else { + newBounds.y = y - newBounds.h; + } + } else if (hEdge.edgeType == CENTER_HORIZONTAL) { + int margin = !mSnap ? 0 : abs(newBounds.y - (y - newBounds.h / 2)); + if (margin > maxDistance) { + mTopMargin = margin; + // or bottomMargin? + } else { + newBounds.y = y - newBounds.h / 2; + } + } else if (hEdge.edgeType == BASELINE) { + newBounds.y = y - mDraggedBaseline; + } else { + assert false : hEdge; + } + } + + /** + * Updates the handler for the given mouse move + * + * @param feedback the feedback handler + * @param elements the elements being dragged + * @param offsetX the new mouse X coordinate + * @param offsetY the new mouse Y coordinate + * @param modifierMask the keyboard modifiers pressed during the drag + */ + public void updateMove(DropFeedback feedback, IDragElement[] elements, + int offsetX, int offsetY, int modifierMask) { + mSnap = (modifierMask & DropFeedback.MODIFIER2) == 0; + + Rect firstBounds = elements[0].getBounds(); + INode firstNode = null; + if (mDraggedNodes != null && mDraggedNodes.size() > 0) { + // TODO - this isn't quite right; this could be a different node than we have + // bounds for! + firstNode = mDraggedNodes.iterator().next(); + firstBounds = firstNode.getBounds(); + } + + mBounds = new Rect(offsetX, offsetY, firstBounds.w, firstBounds.h); + Rect layoutBounds = layout.getBounds(); + if (mBounds.x2() > layoutBounds.x2()) { + mBounds.x -= mBounds.x2() - layoutBounds.x2(); + } + if (mBounds.y2() > layoutBounds.y2()) { + mBounds.y -= mBounds.y2() - layoutBounds.y2(); + } + if (mBounds.x < layoutBounds.x) { + mBounds.x = layoutBounds.x; + } + if (mBounds.y < layoutBounds.y) { + mBounds.y = layoutBounds.y; + } + + clearSuggestions(); + + Rect b = mBounds; + Segment edge = new Segment(b.y, b.x, b.x2(), null, null, TOP, NO_MARGIN); + List<Match> horizontalMatches = findClosest(edge, mHorizontalEdges); + edge = new Segment(b.y2(), b.x, b.x2(), null, null, BOTTOM, NO_MARGIN); + addClosest(edge, mHorizontalEdges, horizontalMatches); + + edge = new Segment(b.x, b.y, b.y2(), null, null, LEFT, NO_MARGIN); + List<Match> verticalMatches = findClosest(edge, mVerticalEdges); + edge = new Segment(b.x2(), b.y, b.y2(), null, null, RIGHT, NO_MARGIN); + addClosest(edge, mVerticalEdges, verticalMatches); + + // Match center + edge = new Segment(b.centerX(), b.y, b.y2(), null, null, CENTER_VERTICAL, NO_MARGIN); + addClosest(edge, mCenterVertEdges, verticalMatches); + edge = new Segment(b.centerY(), b.x, b.x2(), null, null, CENTER_HORIZONTAL, NO_MARGIN); + addClosest(edge, mCenterHorizEdges, horizontalMatches); + + // Match baseline + if (firstNode != null) { + int baseline = firstNode.getBaseline(); + if (baseline != -1) { + mDraggedBaseline = baseline; + edge = new Segment(b.y + baseline, b.x, b.x2(), firstNode, null, BASELINE, + NO_MARGIN); + addClosest(edge, mHorizontalEdges, horizontalMatches); + } + } else { + int baseline = feedback.dragBaseline; + if (baseline != -1) { + mDraggedBaseline = baseline; + edge = new Segment(offsetY + baseline, b.x, b.x2(), null, null, BASELINE, + NO_MARGIN); + addClosest(edge, mHorizontalEdges, horizontalMatches); + } + } + + mHorizontalSuggestions = horizontalMatches; + mVerticalSuggestions = verticalMatches; + mTopMargin = mBottomMargin = mLeftMargin = mRightMargin = 0; + + Match match = pickBestMatch(mHorizontalSuggestions); + if (match != null) { + if (mHorizontalDeps.contains(match.edge.node)) { + match.cycle = true; + } + + // Reset top AND bottom bounds regardless of whether both are bound + mMoveTop = true; + mMoveBottom = true; + + // TODO: Consider doing the snap logic on all the possible matches + // BEFORE sorting, in case this affects the best-pick algorithm (since some + // edges snap and others don't). + snapHorizontal(match.with, match.edge.at, mBounds); + + if (match.with.edgeType == TOP) { + mCurrentTopMatch = match; + } else if (match.with.edgeType == BOTTOM) { + mCurrentBottomMatch = match; + } else { + assert match.with.edgeType == CENTER_HORIZONTAL + || match.with.edgeType == BASELINE : match.with.edgeType; + mCurrentTopMatch = match; + } + } + + match = pickBestMatch(mVerticalSuggestions); + if (match != null) { + if (mVerticalDeps.contains(match.edge.node)) { + match.cycle = true; + } + + // Reset left AND right bounds regardless of whether both are bound + mMoveLeft = true; + mMoveRight = true; + + snapVertical(match.with, match.edge.at, mBounds); + + if (match.with.edgeType == LEFT) { + mCurrentLeftMatch = match; + } else if (match.with.edgeType == RIGHT) { + mCurrentRightMatch = match; + } else { + assert match.with.edgeType == CENTER_VERTICAL; + mCurrentLeftMatch = match; + } + } + + checkCycles(feedback); + } +} |