diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative')
9 files changed, 0 insertions, 3328 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintPainter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintPainter.java deleted file mode 100644 index 447d2d880..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintPainter.java +++ /dev/null @@ -1,783 +0,0 @@ -/* - * 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.DrawingStyle.DEPENDENCY; -import static com.android.ide.common.api.DrawingStyle.GUIDELINE; -import static com.android.ide.common.api.DrawingStyle.GUIDELINE_DASHED; -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.ide.common.api.SegmentType.UNKNOWN; -import static com.android.ide.common.layout.relative.ConstraintType.ALIGN_BASELINE; -import static com.android.ide.common.layout.relative.ConstraintType.ALIGN_BOTTOM; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_ABOVE; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_BELOW; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_LEFT_OF; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_RIGHT_OF; - -import com.android.ide.common.api.DrawingStyle; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.SegmentType; -import com.android.ide.common.layout.relative.DependencyGraph.Constraint; -import com.android.ide.common.layout.relative.DependencyGraph.ViewData; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * The {@link ConstraintPainter} is responsible for painting relative layout constraints - - * such as a source node having its top edge constrained to a target node with a given margin. - * This painter is used both to show static constraints, as well as visualizing proposed - * constraints during a move or resize operation. - */ -public class ConstraintPainter { - /** The size of the arrow head */ - private static final int ARROW_SIZE = 5; - /** Size (height for horizontal, and width for vertical) parent feedback rectangles */ - private static final int PARENT_RECT_SIZE = 12; - - /** - * Paints a given match as a constraint. - * - * @param graphics the graphics context - * @param sourceBounds the source bounds - * @param match the match - */ - static void paintConstraint(IGraphics graphics, Rect sourceBounds, Match match) { - Rect targetBounds = match.edge.node.getBounds(); - ConstraintType type = match.type; - assert type != null; - paintConstraint(graphics, type, match.with.node, sourceBounds, match.edge.node, - targetBounds, null /* allConstraints */, true /* highlightTargetEdge */); - } - - /** - * Paints a constraint. - * <p> - * TODO: when there are multiple links originating in the same direction from - * center, maybe offset them slightly from each other? - * - * @param graphics the graphics context to draw into - * @param constraint The constraint to be drawn - */ - private static void paintConstraint(IGraphics graphics, Constraint constraint, - Set<Constraint> allConstraints) { - ViewData source = constraint.from; - ViewData target = constraint.to; - - INode sourceNode = source.node; - INode targetNode = target.node; - if (sourceNode == targetNode) { - // Self reference - don't visualize - return; - } - - Rect sourceBounds = sourceNode.getBounds(); - Rect targetBounds = targetNode.getBounds(); - paintConstraint(graphics, constraint.type, sourceNode, sourceBounds, targetNode, - targetBounds, allConstraints, false /* highlightTargetEdge */); - } - - /** - * Paint selection feedback by painting constraints for the selected nodes - * - * @param graphics the graphics context - * @param parentNode the parent relative layout - * @param childNodes the nodes whose constraints should be painted - * @param showDependents whether incoming constraints should be shown as well - */ - public static void paintSelectionFeedback(IGraphics graphics, INode parentNode, - List<? extends INode> childNodes, boolean showDependents) { - - DependencyGraph dependencyGraph = new DependencyGraph(parentNode); - Set<INode> horizontalDeps = dependencyGraph.dependsOn(childNodes, false /* vertical */); - Set<INode> verticalDeps = dependencyGraph.dependsOn(childNodes, true /* vertical */); - Set<INode> deps = new HashSet<INode>(horizontalDeps.size() + verticalDeps.size()); - deps.addAll(horizontalDeps); - deps.addAll(verticalDeps); - if (deps.size() > 0) { - graphics.useStyle(DEPENDENCY); - for (INode node : deps) { - // Don't highlight the selected nodes themselves - if (childNodes.contains(node)) { - continue; - } - Rect bounds = node.getBounds(); - graphics.fillRect(bounds); - } - } - - graphics.useStyle(GUIDELINE); - for (INode childNode : childNodes) { - ViewData view = dependencyGraph.getView(childNode); - if (view == null) { - continue; - } - - // Paint all incoming constraints - if (showDependents) { - paintConstraints(graphics, view.dependedOnBy); - } - - // Paint all outgoing constraints - paintConstraints(graphics, view.dependsOn); - } - } - - /** - * Paints a set of constraints. - */ - private static void paintConstraints(IGraphics graphics, List<Constraint> constraints) { - Set<Constraint> mutableConstraintSet = new HashSet<Constraint>(constraints); - - // WORKAROUND! Hide alignBottom attachments if we also have a alignBaseline - // constraint; this is because we also *add* alignBottom attachments when you add - // alignBaseline constraints to work around a surprising behavior of baseline - // constraints. - for (Constraint constraint : constraints) { - if (constraint.type == ALIGN_BASELINE) { - // Remove any baseline - for (Constraint c : constraints) { - if (c.type == ALIGN_BOTTOM && c.to.node == constraint.to.node) { - mutableConstraintSet.remove(c); - } - } - } - } - - for (Constraint constraint : constraints) { - // paintConstraint can digest more than one constraint, so we need to keep - // checking to see if the given constraint is still relevant. - if (mutableConstraintSet.contains(constraint)) { - paintConstraint(graphics, constraint, mutableConstraintSet); - } - } - } - - /** - * Paints a constraint of the given type from the given source node, to the - * given target node, with the specified bounds. - */ - private static void paintConstraint(IGraphics graphics, ConstraintType type, INode sourceNode, - Rect sourceBounds, INode targetNode, Rect targetBounds, - Set<Constraint> allConstraints, boolean highlightTargetEdge) { - - SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX; - SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY; - SegmentType targetSegmentTypeX = type.targetSegmentTypeX; - SegmentType targetSegmentTypeY = type.targetSegmentTypeY; - - // Horizontal center constraint? - if (sourceSegmentTypeX == CENTER_VERTICAL && targetSegmentTypeX == CENTER_VERTICAL) { - paintHorizontalCenterConstraint(graphics, sourceBounds, targetBounds); - return; - } - - // Vertical center constraint? - if (sourceSegmentTypeY == CENTER_HORIZONTAL && targetSegmentTypeY == CENTER_HORIZONTAL) { - paintVerticalCenterConstraint(graphics, sourceBounds, targetBounds); - return; - } - - // Corner constraint? - if (allConstraints != null - && (type == LAYOUT_ABOVE || type == LAYOUT_BELOW - || type == LAYOUT_LEFT_OF || type == LAYOUT_RIGHT_OF)) { - if (paintCornerConstraint(graphics, type, sourceNode, sourceBounds, targetNode, - targetBounds, allConstraints)) { - return; - } - } - - // Vertical constraint? - if (sourceSegmentTypeX == UNKNOWN) { - paintVerticalConstraint(graphics, type, sourceNode, sourceBounds, targetNode, - targetBounds, highlightTargetEdge); - return; - } - - // Horizontal constraint? - if (sourceSegmentTypeY == UNKNOWN) { - paintHorizontalConstraint(graphics, type, sourceNode, sourceBounds, targetNode, - targetBounds, highlightTargetEdge); - return; - } - - // This shouldn't happen - it means we have a constraint that defines all sides - // and is not a centering constraint - assert false; - } - - /** - * Paints a corner constraint, or returns false if this constraint is not a corner. - * A corner is one where there are two constraints from this source node to the - * same target node, one horizontal and one vertical, to the closest edges on - * the target node. - * <p> - * Corners are a common occurrence. If we treat the horizontal and vertical - * constraints separately (below & toRightOf), then we end up with a lot of - * extra lines and arrows -- e.g. two shared edges and arrows pointing to these - * shared edges: - * - * <pre> - * +--------+ | - * | Target --> - * +----|---+ | - * v - * - - - - - -|- - - - - - - * ^ - * | +---|----+ - * <-- Source | - * | +--------+ - * - * Instead, we can simply draw a diagonal arrow here to represent BOTH constraints and - * reduce clutter: - * - * +---------+ - * | Target _| - * +-------|\+ - * \ - * \--------+ - * | Source | - * +--------+ - * </pre> - * - * @param graphics the graphics context to draw - * @param type the constraint to be drawn - * @param sourceNode the source node - * @param sourceBounds the bounds of the source node - * @param targetNode the target node - * @param targetBounds the bounds of the target node - * @param allConstraints the set of all constraints; if a corner is found and painted the - * matching corner constraint is removed from the set - * @return true if the constraint was handled and painted as a corner, false otherwise - */ - private static boolean paintCornerConstraint(IGraphics graphics, ConstraintType type, - INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds, - Set<Constraint> allConstraints) { - - SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX; - SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY; - SegmentType targetSegmentTypeX = type.targetSegmentTypeX; - SegmentType targetSegmentTypeY = type.targetSegmentTypeY; - - ConstraintType opposite1 = null, opposite2 = null; - switch (type) { - case LAYOUT_BELOW: - case LAYOUT_ABOVE: - opposite1 = LAYOUT_LEFT_OF; - opposite2 = LAYOUT_RIGHT_OF; - break; - case LAYOUT_LEFT_OF: - case LAYOUT_RIGHT_OF: - opposite1 = LAYOUT_ABOVE; - opposite2 = LAYOUT_BELOW; - break; - default: - return false; - } - Constraint pair = null; - for (Constraint constraint : allConstraints) { - if ((constraint.type == opposite1 || constraint.type == opposite2) && - constraint.to.node == targetNode && constraint.from.node == sourceNode) { - pair = constraint; - break; - } - } - - // TODO -- ensure that the nodes are adjacent! In other words, that - // their bounds are within N pixels. - - if (pair != null) { - // Visualize the corner constraint - if (sourceSegmentTypeX == UNKNOWN) { - sourceSegmentTypeX = pair.type.sourceSegmentTypeX; - } - if (sourceSegmentTypeY == UNKNOWN) { - sourceSegmentTypeY = pair.type.sourceSegmentTypeY; - } - if (targetSegmentTypeX == UNKNOWN) { - targetSegmentTypeX = pair.type.targetSegmentTypeX; - } - if (targetSegmentTypeY == UNKNOWN) { - targetSegmentTypeY = pair.type.targetSegmentTypeY; - } - - int x1, y1, x2, y2; - if (sourceSegmentTypeX == LEFT) { - x1 = sourceBounds.x + 1 * sourceBounds.w / 4; - } else { - x1 = sourceBounds.x + 3 * sourceBounds.w / 4; - } - if (sourceSegmentTypeY == TOP) { - y1 = sourceBounds.y + 1 * sourceBounds.h / 4; - } else { - y1 = sourceBounds.y + 3 * sourceBounds.h / 4; - } - if (targetSegmentTypeX == LEFT) { - x2 = targetBounds.x + 1 * targetBounds.w / 4; - } else { - x2 = targetBounds.x + 3 * targetBounds.w / 4; - } - if (targetSegmentTypeY == TOP) { - y2 = targetBounds.y + 1 * targetBounds.h / 4; - } else { - y2 = targetBounds.y + 3 * targetBounds.h / 4; - } - - graphics.useStyle(GUIDELINE); - graphics.drawArrow(x1, y1, x2, y2, ARROW_SIZE); - - // Don't process this constraint on its own later. - allConstraints.remove(pair); - - return true; - } - - return false; - } - - /** - * Paints a vertical constraint, handling the various scenarios where there are - * margins, or where the two nodes overlap horizontally and where they don't, etc. - * <p> - * Here's an example of what will be shown for a "below" constraint where the - * nodes do not overlap horizontally and the target node has a bottom margin: - * <pre> - * +--------+ - * | Target | - * +--------+ - * | - * v - * - - - - - - - - - - - - - - - * ^ - * | - * +--------+ - * | Source | - * +--------+ - * </pre> - */ - private static void paintVerticalConstraint(IGraphics graphics, ConstraintType type, - INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds, - boolean highlightTargetEdge) { - SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY; - SegmentType targetSegmentTypeY = type.targetSegmentTypeY; - Margins targetMargins = targetNode.getMargins(); - - assert sourceSegmentTypeY != UNKNOWN; - assert targetBounds != null; - - int sourceY = sourceSegmentTypeY.getY(sourceNode, sourceBounds); - int targetY = targetSegmentTypeY == - UNKNOWN ? sourceY : targetSegmentTypeY.getY(targetNode, targetBounds); - - if (highlightTargetEdge && type.isRelativeToParentEdge()) { - graphics.useStyle(DrawingStyle.DROP_ZONE_ACTIVE); - graphics.fillRect(targetBounds.x, targetY - PARENT_RECT_SIZE / 2, - targetBounds.x2(), targetY + PARENT_RECT_SIZE / 2); - } - - // First see if the two views overlap horizontally. If so, we can just draw a direct - // arrow from the source up to (or down to) the target. - // - // +--------+ - // | Target | - // +--------+ - // ^ - // | - // | - // +--------+ - // | Source | - // +--------+ - // - int maxLeft = Math.max(sourceBounds.x, targetBounds.x); - int minRight = Math.min(sourceBounds.x2(), targetBounds.x2()); - - int center = (maxLeft + minRight) / 2; - if (center > sourceBounds.x && center < sourceBounds.x2()) { - // Yes, the lines overlap -- just draw a straight arrow - // - // - // If however there is a margin on the target edge, it should be drawn like this: - // - // +--------+ - // | Target | - // +--------+ - // | - // | - // v - // - - - - - - - - // ^ - // | - // | - // +--------+ - // | Source | - // +--------+ - // - // Use a minimum threshold for this visualization since it doesn't look good - // for small margins - if (targetSegmentTypeY == BOTTOM && targetMargins.bottom > 5) { - int sharedY = targetY + targetMargins.bottom; - if (sourceY > sharedY + 2) { // Skip when source falls on the margin line - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(targetBounds.x, sharedY, targetBounds.x2(), sharedY); - graphics.useStyle(GUIDELINE); - graphics.drawArrow(center, sourceY, center, sharedY + 2, ARROW_SIZE); - graphics.drawArrow(center, targetY, center, sharedY - 3, ARROW_SIZE); - } else { - graphics.useStyle(GUIDELINE); - // Draw reverse arrow to make it clear the node is as close - // at it can be - graphics.drawArrow(center, targetY, center, sourceY, ARROW_SIZE); - } - return; - } else if (targetSegmentTypeY == TOP && targetMargins.top > 5) { - int sharedY = targetY - targetMargins.top; - if (sourceY < sharedY - 2) { - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(targetBounds.x, sharedY, targetBounds.x2(), sharedY); - graphics.useStyle(GUIDELINE); - graphics.drawArrow(center, sourceY, center, sharedY - 3, ARROW_SIZE); - graphics.drawArrow(center, targetY, center, sharedY + 3, ARROW_SIZE); - } else { - graphics.useStyle(GUIDELINE); - graphics.drawArrow(center, targetY, center, sourceY, ARROW_SIZE); - } - return; - } - - // TODO: If the center falls smack in the center of the sourceBounds, - // AND the source node is part of the selection, then adjust the - // center location such that it is off to the side, let's say 1/4 or 3/4 of - // the overlap region, to ensure that it does not overlap the center selection - // handle - - // When the constraint is for two immediately adjacent edges, we - // need to make some adjustments to make sure the arrow points in the right - // direction - if (sourceY == targetY) { - if (sourceSegmentTypeY == BOTTOM || sourceSegmentTypeY == BASELINE) { - sourceY -= 2 * ARROW_SIZE; - } else if (sourceSegmentTypeY == TOP) { - sourceY += 2 * ARROW_SIZE; - } else { - assert sourceSegmentTypeY == CENTER_HORIZONTAL : sourceSegmentTypeY; - sourceY += sourceBounds.h / 2 - 2 * ARROW_SIZE; - } - } else if (sourceSegmentTypeY == BASELINE) { - sourceY = targetY - 2 * ARROW_SIZE; - } - - // Center the vertical line in the overlap region - graphics.useStyle(GUIDELINE); - graphics.drawArrow(center, sourceY, center, targetY, ARROW_SIZE); - - return; - } - - // If there is no horizontal overlap in the vertical constraints, then we - // will show the attachment relative to a dashed line that extends beyond - // the target bounds, like this: - // - // +--------+ - // | Target | - // +--------+ - - - - - - - - - - // ^ - // | - // +--------+ - // | Source | - // +--------+ - // - // However, if the target node has a vertical margin, we may need to offset - // the line: - // - // +--------+ - // | Target | - // +--------+ - // | - // v - // - - - - - - - - - - - - - - - // ^ - // | - // +--------+ - // | Source | - // +--------+ - // - // If not, we'll need to indicate a shared edge. This is the edge that separate - // them (but this will require me to evaluate margins!) - - // Compute overlap region and pick the middle - int sharedY = targetSegmentTypeY == - UNKNOWN ? sourceY : targetSegmentTypeY.getY(targetNode, targetBounds); - if (type.relativeToMargin) { - if (targetSegmentTypeY == TOP) { - sharedY -= targetMargins.top; - } else if (targetSegmentTypeY == BOTTOM) { - sharedY += targetMargins.bottom; - } - } - - int startX; - int endX; - if (center <= sourceBounds.x) { - startX = targetBounds.x + targetBounds.w / 4; - endX = sourceBounds.x2(); - } else { - assert (center >= sourceBounds.x2()); - startX = sourceBounds.x; - endX = targetBounds.x + 3 * targetBounds.w / 4; - } - // Must draw segmented line instead - // Place the arrow 1/4 instead of 1/2 in the source to avoid overlapping with the - // selection handles - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(startX, sharedY, endX, sharedY); - - // Adjust position of source arrow such that it does not sit across edge; it - // should point directly at the edge - if (Math.abs(sharedY - sourceY) < 2 * ARROW_SIZE) { - if (sourceSegmentTypeY == BASELINE) { - sourceY = sharedY - 2 * ARROW_SIZE; - } else if (sourceSegmentTypeY == TOP) { - sharedY = sourceY; - sourceY = sharedY + 2 * ARROW_SIZE; - } else { - sharedY = sourceY; - sourceY = sharedY - 2 * ARROW_SIZE; - } - } - - graphics.useStyle(GUIDELINE); - - // Draw the line from the source anchor to the shared edge - int x = sourceBounds.x + ((sourceSegmentTypeY == BASELINE) ? - sourceBounds.w / 2 : sourceBounds.w / 4); - graphics.drawArrow(x, sourceY, x, sharedY, ARROW_SIZE); - - // Draw the line from the target to the horizontal shared edge - int tx = targetBounds.centerX(); - if (targetSegmentTypeY == TOP) { - int ty = targetBounds.y; - int margin = targetMargins.top; - if (margin == 0 || !type.relativeToMargin) { - graphics.drawArrow(tx, ty + 2 * ARROW_SIZE, tx, ty, ARROW_SIZE); - } else { - graphics.drawArrow(tx, ty, tx, ty - margin, ARROW_SIZE); - } - } else if (targetSegmentTypeY == BOTTOM) { - int ty = targetBounds.y2(); - int margin = targetMargins.bottom; - if (margin == 0 || !type.relativeToMargin) { - graphics.drawArrow(tx, ty - 2 * ARROW_SIZE, tx, ty, ARROW_SIZE); - } else { - graphics.drawArrow(tx, ty, tx, ty + margin, ARROW_SIZE); - } - } else { - assert targetSegmentTypeY == BASELINE : targetSegmentTypeY; - int ty = targetSegmentTypeY.getY(targetNode, targetBounds); - graphics.drawArrow(tx, ty - 2 * ARROW_SIZE, tx, ty, ARROW_SIZE); - } - - return; - } - - /** - * Paints a horizontal constraint, handling the various scenarios where there are margins, - * or where the two nodes overlap horizontally and where they don't, etc. - */ - private static void paintHorizontalConstraint(IGraphics graphics, ConstraintType type, - INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds, - boolean highlightTargetEdge) { - SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX; - SegmentType targetSegmentTypeX = type.targetSegmentTypeX; - Margins targetMargins = targetNode.getMargins(); - - assert sourceSegmentTypeX != UNKNOWN; - assert targetBounds != null; - - // See paintVerticalConstraint for explanations of the various cases. - - int sourceX = sourceSegmentTypeX.getX(sourceNode, sourceBounds); - int targetX = targetSegmentTypeX == UNKNOWN ? - sourceX : targetSegmentTypeX.getX(targetNode, targetBounds); - - if (highlightTargetEdge && type.isRelativeToParentEdge()) { - graphics.useStyle(DrawingStyle.DROP_ZONE_ACTIVE); - graphics.fillRect(targetX - PARENT_RECT_SIZE / 2, targetBounds.y, - targetX + PARENT_RECT_SIZE / 2, targetBounds.y2()); - } - - int maxTop = Math.max(sourceBounds.y, targetBounds.y); - int minBottom = Math.min(sourceBounds.y2(), targetBounds.y2()); - - // First see if the two views overlap vertically. If so, we can just draw a direct - // arrow from the source over to the target. - int center = (maxTop + minBottom) / 2; - if (center > sourceBounds.y && center < sourceBounds.y2()) { - // See if we should draw a margin line - if (targetSegmentTypeX == RIGHT && targetMargins.right > 5) { - int sharedX = targetX + targetMargins.right; - if (sourceX > sharedX + 2) { // Skip when source falls on the margin line - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(sharedX, targetBounds.y, sharedX, targetBounds.y2()); - graphics.useStyle(GUIDELINE); - graphics.drawArrow(sourceX, center, sharedX + 2, center, ARROW_SIZE); - graphics.drawArrow(targetX, center, sharedX - 3, center, ARROW_SIZE); - } else { - graphics.useStyle(GUIDELINE); - // Draw reverse arrow to make it clear the node is as close - // at it can be - graphics.drawArrow(targetX, center, sourceX, center, ARROW_SIZE); - } - return; - } else if (targetSegmentTypeX == LEFT && targetMargins.left > 5) { - int sharedX = targetX - targetMargins.left; - if (sourceX < sharedX - 2) { - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(sharedX, targetBounds.y, sharedX, targetBounds.y2()); - graphics.useStyle(GUIDELINE); - graphics.drawArrow(sourceX, center, sharedX - 3, center, ARROW_SIZE); - graphics.drawArrow(targetX, center, sharedX + 3, center, ARROW_SIZE); - } else { - graphics.useStyle(GUIDELINE); - graphics.drawArrow(targetX, center, sourceX, center, ARROW_SIZE); - } - return; - } - - if (sourceX == targetX) { - if (sourceSegmentTypeX == RIGHT) { - sourceX -= 2 * ARROW_SIZE; - } else if (sourceSegmentTypeX == LEFT ) { - sourceX += 2 * ARROW_SIZE; - } else { - assert sourceSegmentTypeX == CENTER_VERTICAL : sourceSegmentTypeX; - sourceX += sourceBounds.w / 2 - 2 * ARROW_SIZE; - } - } - - graphics.useStyle(GUIDELINE); - graphics.drawArrow(sourceX, center, targetX, center, ARROW_SIZE); - return; - } - - // Segment line - - // Compute overlap region and pick the middle - int sharedX = targetSegmentTypeX == UNKNOWN ? - sourceX : targetSegmentTypeX.getX(targetNode, targetBounds); - if (type.relativeToMargin) { - if (targetSegmentTypeX == LEFT) { - sharedX -= targetMargins.left; - } else if (targetSegmentTypeX == RIGHT) { - sharedX += targetMargins.right; - } - } - - int startY, endY; - if (center <= sourceBounds.y) { - startY = targetBounds.y + targetBounds.h / 4; - endY = sourceBounds.y2(); - } else { - assert (center >= sourceBounds.y2()); - startY = sourceBounds.y; - endY = targetBounds.y + 3 * targetBounds.h / 2; - } - - // Must draw segmented line instead - // Place at 1/4 instead of 1/2 to avoid overlapping with selection handles - int y = sourceBounds.y + sourceBounds.h / 4; - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(sharedX, startY, sharedX, endY); - - // Adjust position of source arrow such that it does not sit across edge; it - // should point directly at the edge - if (Math.abs(sharedX - sourceX) < 2 * ARROW_SIZE) { - if (sourceSegmentTypeX == LEFT) { - sharedX = sourceX; - sourceX = sharedX + 2 * ARROW_SIZE; - } else { - sharedX = sourceX; - sourceX = sharedX - 2 * ARROW_SIZE; - } - } - - graphics.useStyle(GUIDELINE); - - // Draw the line from the source anchor to the shared edge - graphics.drawArrow(sourceX, y, sharedX, y, ARROW_SIZE); - - // Draw the line from the target to the horizontal shared edge - int ty = targetBounds.centerY(); - if (targetSegmentTypeX == LEFT) { - int tx = targetBounds.x; - int margin = targetMargins.left; - if (margin == 0 || !type.relativeToMargin) { - graphics.drawArrow(tx + 2 * ARROW_SIZE, ty, tx, ty, ARROW_SIZE); - } else { - graphics.drawArrow(tx, ty, tx - margin, ty, ARROW_SIZE); - } - } else { - assert targetSegmentTypeX == RIGHT; - int tx = targetBounds.x2(); - int margin = targetMargins.right; - if (margin == 0 || !type.relativeToMargin) { - graphics.drawArrow(tx - 2 * ARROW_SIZE, ty, tx, ty, ARROW_SIZE); - } else { - graphics.drawArrow(tx, ty, tx + margin, ty, ARROW_SIZE); - } - } - - return; - } - - /** - * Paints a vertical center constraint. The constraint is shown as a dashed line - * through the vertical view, and a solid line over the node bounds. - */ - private static void paintVerticalCenterConstraint(IGraphics graphics, Rect sourceBounds, - Rect targetBounds) { - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(targetBounds.x, targetBounds.centerY(), - targetBounds.x2(), targetBounds.centerY()); - graphics.useStyle(GUIDELINE); - graphics.drawLine(sourceBounds.x, sourceBounds.centerY(), - sourceBounds.x2(), sourceBounds.centerY()); - } - - /** - * Paints a horizontal center constraint. The constraint is shown as a dashed line - * through the horizontal view, and a solid line over the node bounds. - */ - private static void paintHorizontalCenterConstraint(IGraphics graphics, Rect sourceBounds, - Rect targetBounds) { - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(targetBounds.centerX(), targetBounds.y, - targetBounds.centerX(), targetBounds.y2()); - graphics.useStyle(GUIDELINE); - graphics.drawLine(sourceBounds.centerX(), sourceBounds.y, - sourceBounds.centerX(), sourceBounds.y2()); - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintType.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintType.java deleted file mode 100644 index ed4ac1bf4..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintType.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * 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.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.ide.common.api.SegmentType.UNKNOWN; -import static com.android.SdkConstants.ATTR_LAYOUT_ABOVE; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BASELINE; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BOTTOM; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_TOP; -import static com.android.SdkConstants.ATTR_LAYOUT_BELOW; -import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_HORIZONTAL; -import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_IN_PARENT; -import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_VERTICAL; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.SegmentType; - -import java.util.HashMap; -import java.util.Map; - -/** - * Each constraint type corresponds to a type of constraint available for the - * RelativeLayout; for example, {@link #LAYOUT_ABOVE} corresponds to the layout_above constraint. - */ -enum ConstraintType { - LAYOUT_ABOVE(ATTR_LAYOUT_ABOVE, - null /* sourceX */, BOTTOM, null /* targetX */, TOP, - false /* targetParent */, true /* horizontalEdge */, false /* verticalEdge */, - true /* relativeToMargin */), - - LAYOUT_BELOW(ATTR_LAYOUT_BELOW, null, TOP, null, BOTTOM, false, true, false, true), - ALIGN_TOP(ATTR_LAYOUT_ALIGN_TOP, null, TOP, null, TOP, false, true, false, false), - ALIGN_BOTTOM(ATTR_LAYOUT_ALIGN_BOTTOM, null, BOTTOM, null, BOTTOM, false, true, false, false), - ALIGN_LEFT(ATTR_LAYOUT_ALIGN_LEFT, LEFT, null, LEFT, null, false, false, true, false), - ALIGN_RIGHT(ATTR_LAYOUT_ALIGN_RIGHT, RIGHT, null, RIGHT, null, false, false, true, false), - LAYOUT_LEFT_OF(ATTR_LAYOUT_TO_LEFT_OF, RIGHT, null, LEFT, null, false, false, true, true), - LAYOUT_RIGHT_OF(ATTR_LAYOUT_TO_RIGHT_OF, LEFT, null, RIGHT, null, false, false, true, true), - ALIGN_PARENT_TOP(ATTR_LAYOUT_ALIGN_PARENT_TOP, null, TOP, null, TOP, true, true, false, false), - ALIGN_BASELINE(ATTR_LAYOUT_ALIGN_BASELINE, null, BASELINE, null, BASELINE, false, true, false, - false), - ALIGN_PARENT_LEFT(ATTR_LAYOUT_ALIGN_PARENT_LEFT, LEFT, null, LEFT, null, true, false, true, - false), - ALIGN_PARENT_RIGHT(ATTR_LAYOUT_ALIGN_PARENT_RIGHT, RIGHT, null, RIGHT, null, true, false, true, - false), - ALIGN_PARENT_BOTTOM(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, null, BOTTOM, null, BOTTOM, true, true, - false, false), - LAYOUT_CENTER_HORIZONTAL(ATTR_LAYOUT_CENTER_HORIZONTAL, CENTER_VERTICAL, null, CENTER_VERTICAL, - null, true, true, false, false), - LAYOUT_CENTER_VERTICAL(ATTR_LAYOUT_CENTER_VERTICAL, null, CENTER_HORIZONTAL, null, - CENTER_HORIZONTAL, true, false, true, false), - LAYOUT_CENTER_IN_PARENT(ATTR_LAYOUT_CENTER_IN_PARENT, CENTER_VERTICAL, CENTER_HORIZONTAL, - CENTER_VERTICAL, CENTER_HORIZONTAL, true, true, true, false); - - private ConstraintType(String name, SegmentType sourceSegmentTypeX, - SegmentType sourceSegmentTypeY, SegmentType targetSegmentTypeX, - SegmentType targetSegmentTypeY, boolean targetParent, boolean horizontalEdge, - boolean verticalEdge, boolean relativeToMargin) { - assert horizontalEdge || verticalEdge; - - this.name = name; - this.sourceSegmentTypeX = sourceSegmentTypeX != null ? sourceSegmentTypeX : UNKNOWN; - this.sourceSegmentTypeY = sourceSegmentTypeY != null ? sourceSegmentTypeY : UNKNOWN; - this.targetSegmentTypeX = targetSegmentTypeX != null ? targetSegmentTypeX : UNKNOWN; - this.targetSegmentTypeY = targetSegmentTypeY != null ? targetSegmentTypeY : UNKNOWN; - this.targetParent = targetParent; - this.horizontalEdge = horizontalEdge; - this.verticalEdge = verticalEdge; - this.relativeToMargin = relativeToMargin; - } - - /** The attribute name of the constraint */ - public final String name; - - /** The horizontal position of the source of the constraint */ - public final SegmentType sourceSegmentTypeX; - - /** The vertical position of the source of the constraint */ - public final SegmentType sourceSegmentTypeY; - - /** The horizontal position of the target of the constraint */ - public final SegmentType targetSegmentTypeX; - - /** The vertical position of the target of the constraint */ - public final SegmentType targetSegmentTypeY; - - /** - * If true, the constraint targets the parent layout, otherwise it targets another - * view - */ - public final boolean targetParent; - - /** If true, this constraint affects the horizontal dimension */ - public final boolean horizontalEdge; - - /** If true, this constraint affects the vertical dimension */ - public final boolean verticalEdge; - - /** - * Whether this constraint is relative to the margin bounds of the node rather than - * the node's actual bounds - */ - public final boolean relativeToMargin; - - /** Map from attribute name to constraint type */ - private static Map<String, ConstraintType> sNameToType; - - /** - * Returns the {@link ConstraintType} corresponding to the given attribute name, or - * null if not found. - * - * @param attribute the name of the attribute to look up - * @return the corresponding {@link ConstraintType} - */ - @Nullable - public static ConstraintType fromAttribute(@NonNull String attribute) { - if (sNameToType == null) { - ConstraintType[] types = ConstraintType.values(); - Map<String, ConstraintType> map = new HashMap<String, ConstraintType>(types.length); - for (ConstraintType type : types) { - map.put(type.name, type); - } - sNameToType = map; - } - return sNameToType.get(attribute); - } - - /** - * Returns true if this constraint type represents a constraint where the target edge - * is one of the parent edges (actual edge, not center/baseline segments) - * - * @return true if the target segment is a parent edge - */ - public boolean isRelativeToParentEdge() { - return this == ALIGN_PARENT_LEFT || this == ALIGN_PARENT_RIGHT || this == ALIGN_PARENT_TOP - || this == ALIGN_PARENT_BOTTOM; - } - - /** - * Returns a {@link ConstraintType} for a potential match of edges. - * - * @param withParent if true, the target is the parent - * @param from the source edge - * @param to the target edge - * @return a {@link ConstraintType}, or null - */ - @Nullable - public static ConstraintType forMatch(boolean withParent, SegmentType from, SegmentType to) { - // Attached to parent edge? - if (withParent) { - switch (from) { - case TOP: - return ALIGN_PARENT_TOP; - case BOTTOM: - return ALIGN_PARENT_BOTTOM; - case LEFT: - return ALIGN_PARENT_LEFT; - case RIGHT: - return ALIGN_PARENT_RIGHT; - case CENTER_HORIZONTAL: - return LAYOUT_CENTER_VERTICAL; - case CENTER_VERTICAL: - return LAYOUT_CENTER_HORIZONTAL; - } - - return null; - } - - // Attached to some other node. - switch (from) { - case TOP: - switch (to) { - case TOP: - return ALIGN_TOP; - case BOTTOM: - return LAYOUT_BELOW; - case BASELINE: - return ALIGN_BASELINE; - } - break; - case BOTTOM: - switch (to) { - case TOP: - return LAYOUT_ABOVE; - case BOTTOM: - return ALIGN_BOTTOM; - case BASELINE: - return ALIGN_BASELINE; - } - break; - case LEFT: - switch (to) { - case LEFT: - return ALIGN_LEFT; - case RIGHT: - return LAYOUT_RIGHT_OF; - } - break; - case RIGHT: - switch (to) { - case LEFT: - return LAYOUT_LEFT_OF; - case RIGHT: - return ALIGN_RIGHT; - } - break; - case BASELINE: - return ALIGN_BASELINE; - } - - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java deleted file mode 100644 index 3eac510df..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2012 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.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN; -import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; -import static com.android.SdkConstants.ID_PREFIX; -import static com.android.SdkConstants.NEW_ID_PREFIX; -import static com.android.ide.common.layout.BaseViewRule.stripIdPrefix; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_CENTER_HORIZONTAL; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_CENTER_VERTICAL; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.INode.IAttribute; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Handles deletions in a relative layout, transferring constraints across - * deleted nodes - * <p> - * TODO: Consider adding the - * {@link SdkConstants#ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING} attribute to a - * node if it's pointing to a node which is deleted and which has no transitive - * reference to another node. - */ -public class DeletionHandler { - private final INode mLayout; - private final INode[] mChildren; - private final List<INode> mDeleted; - private final Set<String> mDeletedIds; - private final Map<String, INode> mNodeMap; - private final List<INode> mMoved; - - /** - * Creates a new {@link DeletionHandler} - * - * @param deleted the deleted nodes - * @param moved nodes that were moved (e.g. deleted, but also inserted elsewhere) - * @param layout the parent layout of the deleted nodes - */ - public DeletionHandler(@NonNull List<INode> deleted, @NonNull List<INode> moved, - @NonNull INode layout) { - mDeleted = deleted; - mMoved = moved; - mLayout = layout; - - mChildren = mLayout.getChildren(); - mNodeMap = Maps.newHashMapWithExpectedSize(mChildren.length); - for (INode child : mChildren) { - String id = child.getStringAttr(ANDROID_URI, ATTR_ID); - if (id != null) { - mNodeMap.put(stripIdPrefix(id), child); - } - } - - mDeletedIds = Sets.newHashSetWithExpectedSize(mDeleted.size()); - for (INode node : mDeleted) { - String id = node.getStringAttr(ANDROID_URI, ATTR_ID); - if (id != null) { - mDeletedIds.add(stripIdPrefix(id)); - } - } - - // Any widgets that remain (e.g. typically because they were moved) should - // keep their incoming dependencies - for (INode node : mMoved) { - String id = node.getStringAttr(ANDROID_URI, ATTR_ID); - if (id != null) { - mDeletedIds.remove(stripIdPrefix(id)); - } - } - } - - @Nullable - private static String getId(@NonNull IAttribute attribute) { - if (attribute.getName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX) - && ANDROID_URI.equals(attribute.getUri()) - && !attribute.getName().startsWith(ATTR_LAYOUT_MARGIN)) { - String id = attribute.getValue(); - // It might not be an id reference, so check manually rather than just - // calling stripIdPrefix(): - if (id.startsWith(NEW_ID_PREFIX)) { - return id.substring(NEW_ID_PREFIX.length()); - } else if (id.startsWith(ID_PREFIX)) { - return id.substring(ID_PREFIX.length()); - } - } - - return null; - } - - /** - * Updates the constraints in the layout to handle deletion of a set of - * nodes. This ensures that any constraints pointing to one of the deleted - * nodes are changed properly to point to a non-deleted node with similar - * constraints. - */ - public void updateConstraints() { - if (mChildren.length == mDeleted.size()) { - // Deleting everything: Nothing to be done - return; - } - - // Now remove incoming edges to any views that were deleted. If possible, - // don't just delete them but replace them with a transitive constraint, e.g. - // if we have "A <= B <= C" and "B" is removed, then we end up with "A <= C", - - for (INode child : mChildren) { - if (mDeleted.contains(child)) { - continue; - } - - for (IAttribute attribute : child.getLiveAttributes()) { - String id = getId(attribute); - if (id != null) { - if (mDeletedIds.contains(id)) { - // Unset this reference to a deleted widget. It might be - // replaced if the pointed to node points to some other node - // on the same side, but it may use a different constraint name, - // or have none at all (e.g. parent). - String name = attribute.getName(); - child.setAttribute(ANDROID_URI, name, null); - - INode deleted = mNodeMap.get(id); - if (deleted != null) { - ConstraintType type = ConstraintType.fromAttribute(name); - if (type != null) { - transfer(deleted, child, type, 0); - } - } - } - } - } - } - } - - private void transfer(INode deleted, INode target, ConstraintType targetType, int depth) { - if (depth == 20) { - // Prevent really deep flow or unbounded recursion in case there is a bug in - // the cycle detection code - return; - } - - assert mDeleted.contains(deleted); - - for (IAttribute attribute : deleted.getLiveAttributes()) { - String name = attribute.getName(); - ConstraintType type = ConstraintType.fromAttribute(name); - if (type == null) { - continue; - } - - ConstraintType transfer = getCompatibleConstraint(type, targetType); - if (transfer != null) { - String id = getId(attribute); - if (id != null) { - if (mDeletedIds.contains(id)) { - INode nextDeleted = mNodeMap.get(id); - if (nextDeleted != null) { - // Points to another deleted node: recurse - transfer(nextDeleted, target, targetType, depth + 1); - } - } else { - // Found an undeleted node destination: point to it directly. - // Note that we're using the - target.setAttribute(ANDROID_URI, transfer.name, attribute.getValue()); - } - } else { - // Pointing to parent or center etc (non-id ref): replicate this on the target - target.setAttribute(ANDROID_URI, name, attribute.getValue()); - } - } - } - } - - /** - * Determines if two constraints are in the same direction and if so returns - * the constraint in the same direction. Rather than returning boolean true - * or false, this returns the constraint which is sometimes modified. For - * example, if you have a node which points left to a node which is centered - * in parent, then the constraint is turned into center horizontal. - */ - @Nullable - private static ConstraintType getCompatibleConstraint( - @NonNull ConstraintType first, @NonNull ConstraintType second) { - if (first == second) { - return first; - } - - switch (second) { - case ALIGN_LEFT: - case LAYOUT_RIGHT_OF: - switch (first) { - case LAYOUT_CENTER_HORIZONTAL: - case LAYOUT_LEFT_OF: - case ALIGN_LEFT: - return first; - case LAYOUT_CENTER_IN_PARENT: - return LAYOUT_CENTER_HORIZONTAL; - } - return null; - - case ALIGN_RIGHT: - case LAYOUT_LEFT_OF: - switch (first) { - case LAYOUT_CENTER_HORIZONTAL: - case ALIGN_RIGHT: - case LAYOUT_LEFT_OF: - return first; - case LAYOUT_CENTER_IN_PARENT: - return LAYOUT_CENTER_HORIZONTAL; - } - return null; - - case ALIGN_TOP: - case LAYOUT_BELOW: - case ALIGN_BASELINE: - switch (first) { - case LAYOUT_CENTER_VERTICAL: - case ALIGN_TOP: - case LAYOUT_BELOW: - case ALIGN_BASELINE: - return first; - case LAYOUT_CENTER_IN_PARENT: - return LAYOUT_CENTER_VERTICAL; - } - return null; - case ALIGN_BOTTOM: - case LAYOUT_ABOVE: - switch (first) { - case LAYOUT_CENTER_VERTICAL: - case ALIGN_BOTTOM: - case LAYOUT_ABOVE: - case ALIGN_BASELINE: - return first; - case LAYOUT_CENTER_IN_PARENT: - return LAYOUT_CENTER_VERTICAL; - } - return null; - } - - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DependencyGraph.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DependencyGraph.java deleted file mode 100644 index 43d52d137..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DependencyGraph.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * 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.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; -import static com.android.SdkConstants.VALUE_TRUE; - - -import com.android.SdkConstants; -import static com.android.SdkConstants.ANDROID_URI; -import com.android.ide.common.api.IDragElement; -import com.android.ide.common.api.IDragElement.IDragAttribute; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.INode.IAttribute; -import com.android.ide.common.layout.BaseLayoutRule; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Data structure about relative layout relationships which makes it possible to: - * <ul> - * <li> Quickly determine not just the dependencies on other nodes, but which nodes - * depend on this node such that they can be visualized for the selection - * <li> Determine if there are cyclic dependencies, and whether a potential move - * would result in a cycle - * <li> Determine the "depth" of a given node (in terms of how many connections it - * is away from a parent edge) such that we can prioritize connections which - * minimizes the depth - * </ul> - */ -class DependencyGraph { - /** Format to chain include cycles in: a=>b=>c=>d etc */ - static final String CHAIN_FORMAT = "%1$s=>%2$s"; //$NON-NLS-1$ - - /** Format to chain constraint dependencies: button 1 above button2 etc */ - private static final String DEPENDENCY_FORMAT = "%1$s %2$s %3$s"; //$NON-NLS-1$ - - private final Map<String, ViewData> mIdToView = new HashMap<String, ViewData>(); - private final Map<INode, ViewData> mNodeToView = new HashMap<INode, ViewData>(); - - /** Constructs a new {@link DependencyGraph} for the given relative layout */ - DependencyGraph(INode layout) { - INode[] nodes = layout.getChildren(); - - // Parent view: - String parentId = layout.getStringAttr(ANDROID_URI, ATTR_ID); - if (parentId != null) { - parentId = BaseLayoutRule.stripIdPrefix(parentId); - } else { - parentId = "RelativeLayout"; // For display purposes; we never reference - // the parent id from a constraint, only via parent-relative params - // like centerInParent - } - ViewData parentView = new ViewData(layout, parentId); - mNodeToView.put(layout, parentView); - if (parentId != null) { - mIdToView.put(parentId, parentView); - } - - for (INode child : nodes) { - String id = child.getStringAttr(ANDROID_URI, ATTR_ID); - if (id != null) { - id = BaseLayoutRule.stripIdPrefix(id); - } - ViewData view = new ViewData(child, id); - mNodeToView.put(child, view); - if (id != null) { - mIdToView.put(id, view); - } - } - - for (ViewData view : mNodeToView.values()) { - for (IAttribute attribute : view.node.getLiveAttributes()) { - String name = attribute.getName(); - ConstraintType type = ConstraintType.fromAttribute(name); - if (type != null) { - String value = attribute.getValue(); - - if (type.targetParent) { - if (value.equals(VALUE_TRUE)) { - Constraint constraint = new Constraint(type, view, parentView); - view.dependsOn.add(constraint); - parentView.dependedOnBy.add(constraint); - } - } else { - // id-based constraint. - // NOTE: The id could refer to some widget that is NOT a sibling! - String targetId = BaseLayoutRule.stripIdPrefix(value); - ViewData target = mIdToView.get(targetId); - if (target == view) { - // Self-reference. RelativeLayout ignores these so it's - // not an error like a deeper cycle (where RelativeLayout - // will throw an exception), but we might as well warn - // the user about it. - // TODO: Where do we emit this error? - } else if (target != null) { - Constraint constraint = new Constraint(type, view, target); - view.dependsOn.add(constraint); - target.dependedOnBy.add(constraint); - } else { - // This is valid but we might want to warn... - //System.out.println("Warning: no view data found for " + targetId); - } - } - } - } - } - } - - public ViewData getView(IDragElement element) { - IDragAttribute attribute = element.getAttribute(ANDROID_URI, ATTR_ID); - if (attribute != null) { - String id = attribute.getValue(); - id = BaseLayoutRule.stripIdPrefix(id); - return getView(id); - } - - return null; - } - - public ViewData getView(String id) { - return mIdToView.get(id); - } - - public ViewData getView(INode node) { - return mNodeToView.get(node); - } - - /** - * Returns the set of views that depend on the given node in either the horizontal or - * vertical direction - * - * @param nodes the set of nodes that we want to compute the transitive dependencies - * for - * @param vertical if true, look for vertical edge dependencies, otherwise look for - * horizontal edge dependencies - * @return the set of nodes that directly or indirectly depend on the given nodes in - * the given direction - */ - public Set<INode> dependsOn(Collection<? extends INode> nodes, boolean vertical) { - List<ViewData> reachable = new ArrayList<ViewData>(); - - // Traverse the graph of constraints and determine all nodes affected by - // this node - Set<ViewData> visiting = new HashSet<ViewData>(); - for (INode node : nodes) { - ViewData view = mNodeToView.get(node); - if (view != null) { - findBackwards(view, visiting, reachable, vertical, view); - } - } - - Set<INode> dependents = new HashSet<INode>(reachable.size()); - - for (ViewData v : reachable) { - dependents.add(v.node); - } - - return dependents; - } - - private void findBackwards(ViewData view, - Set<ViewData> visiting, List<ViewData> reachable, - boolean vertical, ViewData start) { - visiting.add(view); - reachable.add(view); - - for (Constraint constraint : view.dependedOnBy) { - if (vertical && !constraint.type.verticalEdge) { - continue; - } else if (!vertical && !constraint.type.horizontalEdge) { - continue; - } - - assert constraint.to == view; - ViewData from = constraint.from; - if (visiting.contains(from)) { - // Cycle - what do we do to highlight this? - List<Constraint> path = getPathTo(start.node, view.node, vertical); - if (path != null) { - // TODO: display to the user somehow. We need log access for the - // view rules. - System.out.println(Constraint.describePath(path, null, null)); - } - } else { - findBackwards(from, visiting, reachable, vertical, start); - } - } - - visiting.remove(view); - } - - public List<Constraint> getPathTo(INode from, INode to, boolean vertical) { - // Traverse the graph of constraints and determine all nodes affected by - // this node - Set<ViewData> visiting = new HashSet<ViewData>(); - List<Constraint> path = new ArrayList<Constraint>(); - ViewData view = mNodeToView.get(from); - if (view != null) { - return findForwards(view, visiting, path, vertical, to); - } - - return null; - } - - private List<Constraint> findForwards(ViewData view, Set<ViewData> visiting, - List<Constraint> path, boolean vertical, INode target) { - visiting.add(view); - - for (Constraint constraint : view.dependsOn) { - if (vertical && !constraint.type.verticalEdge) { - continue; - } else if (!vertical && !constraint.type.horizontalEdge) { - continue; - } - - try { - path.add(constraint); - - if (constraint.to.node == target) { - return new ArrayList<Constraint>(path); - } - - assert constraint.from == view; - ViewData to = constraint.to; - if (visiting.contains(to)) { - // CYCLE! - continue; - } - - List<Constraint> chain = findForwards(to, visiting, path, vertical, target); - if (chain != null) { - return chain; - } - } finally { - path.remove(constraint); - } - } - - visiting.remove(view); - - return null; - } - - /** - * Info about a specific widget child of a relative layout and its constraints. This - * is a node in the dependency graph. - */ - static class ViewData { - public final INode node; - public final String id; - public final List<Constraint> dependsOn = new ArrayList<Constraint>(4); - public final List<Constraint> dependedOnBy = new ArrayList<Constraint>(8); - - ViewData(INode node, String id) { - this.node = node; - this.id = id; - } - } - - /** - * Info about a specific constraint between two widgets in a relative layout. This is - * an edge in the dependency graph. - */ - static class Constraint { - public final ConstraintType type; - public final ViewData from; - public final ViewData to; - - // TODO: Initialize depth -- should be computed independently for top, left, etc. - // We can use this in GuidelineHandler.MatchComparator to prefer matches that - // are closer to a parent edge: - //public int depth; - - Constraint(ConstraintType type, ViewData from, ViewData to) { - this.type = type; - this.from = from; - this.to = to; - } - - static String describePath(List<Constraint> path, String newName, String newId) { - String s = ""; - for (int i = path.size() - 1; i >= 0; i--) { - Constraint constraint = path.get(i); - String suffix = (i == path.size() -1) ? constraint.to.id : s; - s = String.format(DEPENDENCY_FORMAT, constraint.from.id, - stripLayoutAttributePrefix(constraint.type.name), suffix); - } - - if (newName != null) { - s = String.format(DEPENDENCY_FORMAT, s, stripLayoutAttributePrefix(newName), - BaseLayoutRule.stripIdPrefix(newId)); - } - - return s; - } - - private static String stripLayoutAttributePrefix(String name) { - if (name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { - return name.substring(ATTR_LAYOUT_RESOURCE_PREFIX.length()); - } - - return name; - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelineHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelineHandler.java deleted file mode 100644 index db08b1857..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelineHandler.java +++ /dev/null @@ -1,839 +0,0 @@ -/* - * 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.MarginType.WITHOUT_MARGIN; -import static com.android.ide.common.api.MarginType.WITH_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.ide.common.layout.BaseLayoutRule.getMaxMatchDistance; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ATTR_LAYOUT_ABOVE; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BASELINE; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BOTTOM; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_TOP; -import static com.android.SdkConstants.ATTR_LAYOUT_BELOW; -import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_HORIZONTAL; -import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_IN_PARENT; -import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_VERTICAL; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_BOTTOM; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_TOP; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF; -import static com.android.SdkConstants.VALUE_N_DP; -import static com.android.SdkConstants.VALUE_TRUE; -import static com.android.ide.common.layout.relative.ConstraintType.ALIGN_BASELINE; - -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.Margins; -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 com.android.ide.common.layout.relative.DependencyGraph.Constraint; -import com.android.ide.common.layout.relative.DependencyGraph.ViewData; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Set; - -/** - * The {@link GuidelineHandler} class keeps track of state related to a guideline operation - * like move and resize, and performs various constraint computations. - */ -public class GuidelineHandler { - /** - * A dependency graph for the relative layout recording constraint relationships - */ - protected DependencyGraph mDependencyGraph; - - /** The RelativeLayout we are moving/resizing within */ - public INode layout; - - /** The set of nodes being dragged (may be null) */ - protected Collection<INode> mDraggedNodes; - - /** The bounds of the primary child node being dragged */ - protected Rect mBounds; - - /** Whether the left edge is being moved/resized */ - protected boolean mMoveLeft; - - /** Whether the right edge is being moved/resized */ - protected boolean mMoveRight; - - /** Whether the top edge is being moved/resized */ - protected boolean mMoveTop; - - /** Whether the bottom edge is being moved/resized */ - protected boolean mMoveBottom; - - /** - * Whether the drop/move/resize position should be snapped (which can be turned off - * with a modifier key during the operation) - */ - protected boolean mSnap = true; - - /** - * The set of nodes which depend on the currently selected nodes, including - * transitively, through horizontal constraints (a "horizontal constraint" - * is a constraint between two horizontal edges) - */ - protected Set<INode> mHorizontalDeps; - - /** - * The set of nodes which depend on the currently selected nodes, including - * transitively, through vertical constraints (a "vertical constraint" - * is a constraint between two vertical edges) - */ - protected Set<INode> mVerticalDeps; - - /** The current list of constraints which result in a horizontal cycle (if applicable) */ - protected List<Constraint> mHorizontalCycle; - - /** The current list of constraints which result in a vertical cycle (if applicable) */ - protected List<Constraint> mVerticalCycle; - - /** - * All horizontal segments in the relative layout - top and bottom edges, baseline - * edges, and top and bottom edges offset by the applicable margins in each direction - */ - protected List<Segment> mHorizontalEdges; - - /** - * All vertical segments in the relative layout - left and right edges, and left and - * right edges offset by the applicable margins in each direction - */ - protected List<Segment> mVerticalEdges; - - /** - * All center vertical segments in the relative layout. These are kept separate since - * they only match other center edges. - */ - protected List<Segment> mCenterVertEdges; - - /** - * All center horizontal segments in the relative layout. These are kept separate - * since they only match other center edges. - */ - protected List<Segment> mCenterHorizEdges; - - /** - * Suggestions for horizontal matches. There could be more than one, but all matches - * will be equidistant from the current position (as well as in the same direction, - * which means that you can't have one match 5 pixels to the left and one match 5 - * pixels to the right since it would be impossible to snap to fit with both; you can - * however have multiple matches all 5 pixels to the left.) - * <p - * The best vertical match will be found in {@link #mCurrentTopMatch} or - * {@link #mCurrentBottomMatch}. - */ - protected List<Match> mHorizontalSuggestions; - - /** - * Suggestions for vertical matches. - * <p - * The best vertical match will be found in {@link #mCurrentLeftMatch} or - * {@link #mCurrentRightMatch}. - */ - protected List<Match> mVerticalSuggestions; - - /** - * The current match on the left edge, or null if no match or if the left edge is not - * being moved or resized. - */ - protected Match mCurrentLeftMatch; - - /** - * The current match on the top edge, or null if no match or if the top edge is not - * being moved or resized. - */ - protected Match mCurrentTopMatch; - - /** - * The current match on the right edge, or null if no match or if the right edge is - * not being moved or resized. - */ - protected Match mCurrentRightMatch; - - /** - * The current match on the bottom edge, or null if no match or if the bottom edge is - * not being moved or resized. - */ - protected Match mCurrentBottomMatch; - - /** - * The amount of margin to add to the top edge, or 0 - */ - protected int mTopMargin; - - /** - * The amount of margin to add to the bottom edge, or 0 - */ - protected int mBottomMargin; - - /** - * The amount of margin to add to the left edge, or 0 - */ - protected int mLeftMargin; - - /** - * The amount of margin to add to the right edge, or 0 - */ - protected int mRightMargin; - - /** - * The associated rules engine - */ - protected IClientRulesEngine mRulesEngine; - - /** - * Construct a new {@link GuidelineHandler} for the given relative layout. - * - * @param layout the RelativeLayout to handle - */ - GuidelineHandler(INode layout, IClientRulesEngine rulesEngine) { - this.layout = layout; - mRulesEngine = rulesEngine; - - mHorizontalEdges = new ArrayList<Segment>(); - mVerticalEdges = new ArrayList<Segment>(); - mCenterVertEdges = new ArrayList<Segment>(); - mCenterHorizEdges = new ArrayList<Segment>(); - mDependencyGraph = new DependencyGraph(layout); - } - - /** - * Returns true if the handler has any suggestions to offer - * - * @return true if the handler has any suggestions to offer - */ - public boolean haveSuggestions() { - return mCurrentLeftMatch != null || mCurrentTopMatch != null - || mCurrentRightMatch != null || mCurrentBottomMatch != null; - } - - /** - * Returns the closest match. - * - * @return the closest match, or null if nothing matched - */ - protected Match pickBestMatch(List<Match> matches) { - int alternatives = matches.size(); - if (alternatives == 0) { - return null; - } else if (alternatives == 1) { - Match match = matches.get(0); - return match; - } else { - assert alternatives > 1; - Collections.sort(matches, new MatchComparator()); - return matches.get(0); - } - } - - private boolean checkCycle(DropFeedback feedback, Match match, boolean vertical) { - if (match != null && match.cycle) { - for (INode node : mDraggedNodes) { - INode from = match.edge.node; - assert match.with.node == null || match.with.node == node; - INode to = node; - List<Constraint> path = mDependencyGraph.getPathTo(from, to, vertical); - if (path != null) { - if (vertical) { - mVerticalCycle = path; - } else { - mHorizontalCycle = path; - } - String desc = Constraint.describePath(path, - match.type.name, match.edge.id); - - feedback.errorMessage = "Constraint creates a cycle: " + desc; - return true; - } - } - } - - return false; - } - - /** - * Checks for any cycles in the dependencies - * - * @param feedback the drop feedback state - */ - public void checkCycles(DropFeedback feedback) { - // Deliberate short circuit evaluation -- only list the first cycle - feedback.errorMessage = null; - mHorizontalCycle = null; - mVerticalCycle = null; - - if (checkCycle(feedback, mCurrentTopMatch, true /* vertical */) - || checkCycle(feedback, mCurrentBottomMatch, true)) { - } - - if (checkCycle(feedback, mCurrentLeftMatch, false) - || checkCycle(feedback, mCurrentRightMatch, false)) { - } - } - - /** Records the matchable outside edges for the given node to the potential match list */ - protected void addBounds(INode node, String id, - boolean addHorizontal, boolean addVertical) { - Rect b = node.getBounds(); - Margins margins = node.getMargins(); - if (addHorizontal) { - if (margins.top != 0) { - mHorizontalEdges.add(new Segment(b.y, b.x, b.x2(), node, id, TOP, WITHOUT_MARGIN)); - mHorizontalEdges.add(new Segment(b.y - margins.top, b.x, b.x2(), node, id, - TOP, WITH_MARGIN)); - } else { - mHorizontalEdges.add(new Segment(b.y, b.x, b.x2(), node, id, TOP, NO_MARGIN)); - } - if (margins.bottom != 0) { - mHorizontalEdges.add(new Segment(b.y2(), b.x, b.x2(), node, id, BOTTOM, - WITHOUT_MARGIN)); - mHorizontalEdges.add(new Segment(b.y2() + margins.bottom, b.x, b.x2(), node, - id, BOTTOM, WITH_MARGIN)); - } else { - mHorizontalEdges.add(new Segment(b.y2(), b.x, b.x2(), node, id, - BOTTOM, NO_MARGIN)); - } - } - if (addVertical) { - if (margins.left != 0) { - mVerticalEdges.add(new Segment(b.x, b.y, b.y2(), node, id, LEFT, WITHOUT_MARGIN)); - mVerticalEdges.add(new Segment(b.x - margins.left, b.y, b.y2(), node, id, LEFT, - WITH_MARGIN)); - } else { - mVerticalEdges.add(new Segment(b.x, b.y, b.y2(), node, id, LEFT, NO_MARGIN)); - } - - if (margins.right != 0) { - mVerticalEdges.add(new Segment(b.x2(), b.y, b.y2(), node, id, - RIGHT, WITHOUT_MARGIN)); - mVerticalEdges.add(new Segment(b.x2() + margins.right, b.y, b.y2(), node, id, - RIGHT, WITH_MARGIN)); - } else { - mVerticalEdges.add(new Segment(b.x2(), b.y, b.y2(), node, id, - RIGHT, NO_MARGIN)); - } - } - } - - /** Records the center edges for the given node to the potential match list */ - protected void addCenter(INode node, String id, - boolean addHorizontal, boolean addVertical) { - Rect b = node.getBounds(); - - if (addHorizontal) { - mCenterHorizEdges.add(new Segment(b.centerY(), b.x, b.x2(), - node, id, CENTER_HORIZONTAL, NO_MARGIN)); - } - if (addVertical) { - mCenterVertEdges.add(new Segment(b.centerX(), b.y, b.y2(), - node, id, CENTER_VERTICAL, NO_MARGIN)); - } - } - - /** Records the baseline edge for the given node to the potential match list */ - protected int addBaseLine(INode node, String id) { - int baselineY = node.getBaseline(); - if (baselineY != -1) { - Rect b = node.getBounds(); - mHorizontalEdges.add(new Segment(b.y + baselineY, b.x, b.x2(), node, id, BASELINE, - NO_MARGIN)); - } - - return baselineY; - } - - protected void snapVertical(Segment vEdge, int x, Rect newBounds) { - newBounds.x = x; - } - - protected void snapHorizontal(Segment hEdge, int y, Rect newBounds) { - newBounds.y = y; - } - - /** - * Returns whether two edge types are compatible. For example, we only match the - * center of one object with the center of another. - * - * @param edge the first edge type to compare - * @param dragged the second edge type to compare the first one with - * @param delta the delta between the two edge locations - * @return true if the two edge types can be compatibly matched - */ - protected boolean isEdgeTypeCompatible(SegmentType edge, SegmentType dragged, int delta) { - - if (Math.abs(delta) > BaseLayoutRule.getMaxMatchDistance()) { - if (dragged == LEFT || dragged == TOP) { - if (delta > 0) { - return false; - } - } else { - if (delta < 0) { - return false; - } - } - } - - switch (edge) { - case BOTTOM: - case TOP: - return dragged == TOP || dragged == BOTTOM; - case LEFT: - case RIGHT: - return dragged == LEFT || dragged == RIGHT; - - // Center horizontal, center vertical and Baseline only matches the same - // type, and only within the matching distance -- no margins! - case BASELINE: - case CENTER_HORIZONTAL: - case CENTER_VERTICAL: - return dragged == edge && Math.abs(delta) < getMaxMatchDistance(); - default: assert false : edge; - } - return false; - } - - /** - * Finds the closest matching segments among the given list of edges for the given - * dragged edge, and returns these as a list of matches - */ - protected List<Match> findClosest(Segment draggedEdge, List<Segment> edges) { - List<Match> closest = new ArrayList<Match>(); - addClosest(draggedEdge, edges, closest); - return closest; - } - - protected void addClosest(Segment draggedEdge, List<Segment> edges, - List<Match> closest) { - int at = draggedEdge.at; - int closestDelta = closest.size() > 0 ? closest.get(0).delta : Integer.MAX_VALUE; - int closestDistance = abs(closestDelta); - for (Segment edge : edges) { - assert draggedEdge.edgeType.isHorizontal() == edge.edgeType.isHorizontal(); - - int delta = edge.at - at; - int distance = abs(delta); - if (distance > closestDistance) { - continue; - } - - if (!isEdgeTypeCompatible(edge.edgeType, draggedEdge.edgeType, delta)) { - continue; - } - - boolean withParent = edge.node == layout; - ConstraintType type = ConstraintType.forMatch(withParent, - draggedEdge.edgeType, edge.edgeType); - if (type == null) { - continue; - } - - // Ensure that the edge match is compatible; for example, a "below" - // constraint can only apply to the margin bounds and a "bottom" - // constraint can only apply to the non-margin bounds. - if (type.relativeToMargin && edge.marginType == WITHOUT_MARGIN) { - continue; - } else if (!type.relativeToMargin && edge.marginType == WITH_MARGIN) { - continue; - } - - Match match = new Match(this, edge, draggedEdge, type, delta); - - if (distance < closestDistance) { - closest.clear(); - closestDistance = distance; - closestDelta = delta; - } else if (delta * closestDelta < 0) { - // They have different signs, e.g. the matches are equal but - // on opposite sides; can't accept them both - continue; - } - closest.add(match); - } - } - - protected void clearSuggestions() { - mHorizontalSuggestions = mVerticalSuggestions = null; - mCurrentLeftMatch = mCurrentRightMatch = null; - mCurrentTopMatch = mCurrentBottomMatch = null; - } - - /** - * Given a node, apply the suggestions by expressing them as relative layout param - * values - * - * @param n the node to apply constraints to - */ - public void applyConstraints(INode n) { - // Process each edge separately - String centerBoth = n.getStringAttr(ANDROID_URI, ATTR_LAYOUT_CENTER_IN_PARENT); - if (centerBoth != null && centerBoth.equals(VALUE_TRUE)) { - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_IN_PARENT, null); - - // If you had a center-in-both-directions attribute, and you're - // only resizing in one dimension, then leave the other dimension - // centered, e.g. if you have centerInParent and apply alignLeft, - // then you should end up with alignLeft and centerVertically - if (mCurrentTopMatch == null && mCurrentBottomMatch == null) { - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_VERTICAL, VALUE_TRUE); - } - if (mCurrentLeftMatch == null && mCurrentRightMatch == null) { - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL, VALUE_TRUE); - } - } - - if (mMoveTop) { - // Remove top attachments - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_TOP, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_TOP, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_BELOW, null); - - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_VERTICAL, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_BASELINE, null); - - } - - if (mMoveBottom) { - // Remove bottom attachments - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_BOTTOM, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ABOVE, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_VERTICAL, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_BASELINE, null); - } - - if (mMoveLeft) { - // Remove left attachments - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_LEFT, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_LEFT, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_TO_RIGHT_OF, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL, null); - } - - if (mMoveRight) { - // Remove right attachments - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_RIGHT, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_RIGHT, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_TO_LEFT_OF, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL, null); - } - - if (mMoveTop && mCurrentTopMatch != null) { - applyConstraint(n, mCurrentTopMatch.getConstraint(true /* generateId */)); - if (mCurrentTopMatch.type == ALIGN_BASELINE) { - // HACK! WORKAROUND! Baseline doesn't provide a new bottom edge for attachments - String c = mCurrentTopMatch.getConstraint(true); - c = c.replace(ATTR_LAYOUT_ALIGN_BASELINE, ATTR_LAYOUT_ALIGN_BOTTOM); - applyConstraint(n, c); - } - } - - if (mMoveBottom && mCurrentBottomMatch != null) { - applyConstraint(n, mCurrentBottomMatch.getConstraint(true)); - } - - if (mMoveLeft && mCurrentLeftMatch != null) { - applyConstraint(n, mCurrentLeftMatch.getConstraint(true)); - } - - if (mMoveRight && mCurrentRightMatch != null) { - applyConstraint(n, mCurrentRightMatch.getConstraint(true)); - } - - if (mMoveLeft) { - applyMargin(n, ATTR_LAYOUT_MARGIN_LEFT, mLeftMargin); - } - if (mMoveRight) { - applyMargin(n, ATTR_LAYOUT_MARGIN_RIGHT, mRightMargin); - } - if (mMoveTop) { - applyMargin(n, ATTR_LAYOUT_MARGIN_TOP, mTopMargin); - } - if (mMoveBottom) { - applyMargin(n, ATTR_LAYOUT_MARGIN_BOTTOM, mBottomMargin); - } - } - - private void applyConstraint(INode n, String constraint) { - assert constraint.contains("=") : constraint; - String name = constraint.substring(0, constraint.indexOf('=')); - String value = constraint.substring(constraint.indexOf('=') + 1); - n.setAttribute(ANDROID_URI, name, value); - } - - private void applyMargin(INode n, String marginAttribute, int margin) { - if (margin > 0) { - int dp = mRulesEngine.pxToDp(margin); - n.setAttribute(ANDROID_URI, marginAttribute, String.format(VALUE_N_DP, dp)); - } else if (n.getStringAttr(ANDROID_URI, marginAttribute) != null) { - // Clear out existing margin - n.setAttribute(ANDROID_URI, marginAttribute, null); - } - } - - private void removeRelativeParams(INode node) { - for (ConstraintType type : ConstraintType.values()) { - node.setAttribute(ANDROID_URI, type.name, null); - } - node.setAttribute(ANDROID_URI,ATTR_LAYOUT_MARGIN_LEFT, null); - node.setAttribute(ANDROID_URI,ATTR_LAYOUT_MARGIN_RIGHT, null); - node.setAttribute(ANDROID_URI,ATTR_LAYOUT_MARGIN_TOP, null); - node.setAttribute(ANDROID_URI,ATTR_LAYOUT_MARGIN_BOTTOM, null); - } - - /** - * Attach the new child to the previous node - * @param previous the previous child - * @param node the new child to attach it to - */ - public void attachPrevious(INode previous, INode node) { - removeRelativeParams(node); - - String id = previous.getStringAttr(ANDROID_URI, ATTR_ID); - if (id == null) { - return; - } - - if (mCurrentTopMatch != null || mCurrentBottomMatch != null) { - // Attaching the top: arrange below, and for bottom arrange above - node.setAttribute(ANDROID_URI, - mCurrentTopMatch != null ? ATTR_LAYOUT_BELOW : ATTR_LAYOUT_ABOVE, id); - // Apply same left/right constraints as the parent - if (mCurrentLeftMatch != null) { - applyConstraint(node, mCurrentLeftMatch.getConstraint(true)); - applyMargin(node, ATTR_LAYOUT_MARGIN_LEFT, mLeftMargin); - } else if (mCurrentRightMatch != null) { - applyConstraint(node, mCurrentRightMatch.getConstraint(true)); - applyMargin(node, ATTR_LAYOUT_MARGIN_RIGHT, mRightMargin); - } - } else if (mCurrentLeftMatch != null || mCurrentRightMatch != null) { - node.setAttribute(ANDROID_URI, - mCurrentLeftMatch != null ? ATTR_LAYOUT_TO_RIGHT_OF : ATTR_LAYOUT_TO_LEFT_OF, - id); - // Apply same top/bottom constraints as the parent - if (mCurrentTopMatch != null) { - applyConstraint(node, mCurrentTopMatch.getConstraint(true)); - applyMargin(node, ATTR_LAYOUT_MARGIN_TOP, mTopMargin); - } else if (mCurrentBottomMatch != null) { - applyConstraint(node, mCurrentBottomMatch.getConstraint(true)); - applyMargin(node, ATTR_LAYOUT_MARGIN_BOTTOM, mBottomMargin); - } - } else { - return; - } - } - - /** Breaks any cycles detected by the handler */ - public void removeCycles() { - if (mHorizontalCycle != null) { - removeCycles(mHorizontalDeps); - } - if (mVerticalCycle != null) { - removeCycles(mVerticalDeps); - } - } - - private void removeCycles(Set<INode> deps) { - for (INode node : mDraggedNodes) { - ViewData view = mDependencyGraph.getView(node); - if (view != null) { - for (Constraint constraint : view.dependedOnBy) { - // For now, remove ALL constraints pointing to this node in this orientation. - // Later refine this to be smarter. (We can't JUST remove the constraints - // identified in the cycle since there could be multiple.) - constraint.from.node.setAttribute(ANDROID_URI, constraint.type.name, null); - } - } - } - } - - /** - * Comparator used to sort matches such that the first match is the most desirable - * match (where we prefer attaching to parent bounds, we avoid matches that lead to a - * cycle, we prefer constraints on closer widgets rather than ones further away, and - * so on.) - * <p> - * There are a number of sorting criteria. One of them is the distance between the - * matched edges. We may end up with multiple matches that are the same distance. In - * that case we look at the orientation; on the left side, prefer left-oriented - * attachments, and on the right-side prefer right-oriented attachments. For example, - * consider the following scenario: - * - * <pre> - * +--------------------+-------------------------+ - * | Attached on left | | - * +--------------------+ | - * | | - * | +-----+ | - * | | A | | - * | +-----+ | - * | | - * | +-------------------------+ - * | | Attached on right | - * +--------------------+-------------------------+ - * </pre> - * - * Here, dragging the left edge should attach to the top left attached view, whereas - * in the following layout dragging the right edge would attach to the bottom view: - * - * <pre> - * +--------------------------+-------------------+ - * | Attached on left | | - * +--------------------------+ | - * | | - * | +-----+ | - * | | A | | - * | +-----+ | - * | | - * | +-------------------+ - * | | Attached on right | - * +--------------------------+-------------------+ - * - * </pre> - * - * </ul> - */ - private final class MatchComparator implements Comparator<Match> { - @Override - public int compare(Match m1, Match m2) { - // Always prefer matching parent bounds - int parent1 = m1.edge.node == layout ? -1 : 1; - int parent2 = m2.edge.node == layout ? -1 : 1; - // unless it's a center bound -- those should always get lowest priority since - // they overlap with other usually more interesting edges near the center of - // the layout. - if (m1.edge.edgeType == CENTER_HORIZONTAL - || m1.edge.edgeType == CENTER_VERTICAL) { - parent1 = 2; - } - if (m2.edge.edgeType == CENTER_HORIZONTAL - || m2.edge.edgeType == CENTER_VERTICAL) { - parent2 = 2; - } - if (parent1 != parent2) { - return parent1 - parent2; - } - - // Avoid matching edges that would lead to a cycle - if (m1.edge.edgeType.isHorizontal()) { - int cycle1 = mHorizontalDeps.contains(m1.edge.node) ? 1 : -1; - int cycle2 = mHorizontalDeps.contains(m2.edge.node) ? 1 : -1; - if (cycle1 != cycle2) { - return cycle1 - cycle2; - } - } else { - int cycle1 = mVerticalDeps.contains(m1.edge.node) ? 1 : -1; - int cycle2 = mVerticalDeps.contains(m2.edge.node) ? 1 : -1; - if (cycle1 != cycle2) { - return cycle1 - cycle2; - } - } - - // TODO: Sort by minimum depth -- do we have the depth anywhere? - - // Prefer nodes that are closer - int distance1, distance2; - if (m1.edge.to <= m1.with.from) { - distance1 = m1.with.from - m1.edge.to; - } else if (m1.edge.from >= m1.with.to) { - distance1 = m1.edge.from - m1.with.to; - } else { - // Some kind of overlap - not sure how to prioritize these yet... - distance1 = 0; - } - if (m2.edge.to <= m2.with.from) { - distance2 = m2.with.from - m2.edge.to; - } else if (m2.edge.from >= m2.with.to) { - distance2 = m2.edge.from - m2.with.to; - } else { - // Some kind of overlap - not sure how to prioritize these yet... - distance2 = 0; - } - - if (distance1 != distance2) { - return distance1 - distance2; - } - - // Prefer matching on baseline - int baseline1 = (m1.edge.edgeType == BASELINE) ? -1 : 1; - int baseline2 = (m2.edge.edgeType == BASELINE) ? -1 : 1; - if (baseline1 != baseline2) { - return baseline1 - baseline2; - } - - // Prefer matching top/left edges before matching bottom/right edges - int orientation1 = (m1.with.edgeType == LEFT || - m1.with.edgeType == TOP) ? -1 : 1; - int orientation2 = (m2.with.edgeType == LEFT || - m2.with.edgeType == TOP) ? -1 : 1; - if (orientation1 != orientation2) { - return orientation1 - orientation2; - } - - // Prefer opposite-matching over same-matching. - // In other words, if we have the choice of matching - // our left edge with another element's left edge, - // or matching our left edge with another element's right - // edge, prefer the right edge since that - // The two matches have identical distance; try to sort by - // orientation - int edgeType1 = (m1.edge.edgeType != m1.with.edgeType) ? -1 : 1; - int edgeType2 = (m2.edge.edgeType != m2.with.edgeType) ? -1 : 1; - if (edgeType1 != edgeType2) { - return edgeType1 - edgeType2; - } - - return 0; - } - } - - /** - * Returns the {@link IClientRulesEngine} IDE callback - * - * @return the {@link IClientRulesEngine} IDE callback, never null - */ - public IClientRulesEngine getRulesEngine() { - return mRulesEngine; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelinePainter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelinePainter.java deleted file mode 100644 index 2fe74768f..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelinePainter.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * 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.SdkConstants.ATTR_LAYOUT_MARGIN_BOTTOM; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_TOP; -import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; -import static com.android.SdkConstants.ID_PREFIX; -import static com.android.SdkConstants.NEW_ID_PREFIX; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.DrawingStyle; -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.IFeedbackPainter; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Point; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.SegmentType; -import com.android.ide.common.layout.relative.DependencyGraph.Constraint; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * The {@link GuidelinePainter} is responsible for painting guidelines during an operation - * which uses a {@link GuidelineHandler} such as a resize operation. - */ -public final class GuidelinePainter implements IFeedbackPainter { - // ---- Implements IFeedbackPainter ---- - @Override - public void paint(@NonNull IGraphics gc, @NonNull INode node, @NonNull DropFeedback feedback) { - GuidelineHandler state = (GuidelineHandler) feedback.userData; - - for (INode dragged : state.mDraggedNodes) { - gc.useStyle(DrawingStyle.DRAGGED); - Rect bounds = dragged.getBounds(); - if (bounds.isValid()) { - gc.fillRect(bounds); - } - } - - Set<INode> horizontalDeps = state.mHorizontalDeps; - Set<INode> verticalDeps = state.mVerticalDeps; - Set<INode> deps = new HashSet<INode>(horizontalDeps.size() + verticalDeps.size()); - deps.addAll(horizontalDeps); - deps.addAll(verticalDeps); - if (deps.size() > 0) { - gc.useStyle(DrawingStyle.DEPENDENCY); - for (INode n : deps) { - // Don't highlight the selected nodes themselves - if (state.mDraggedNodes.contains(n)) { - continue; - } - Rect bounds = n.getBounds(); - gc.fillRect(bounds); - } - } - - if (state.mBounds != null) { - if (state instanceof MoveHandler) { - gc.useStyle(DrawingStyle.DROP_PREVIEW); - } else { - // Resizing - if (state.haveSuggestions()) { - gc.useStyle(DrawingStyle.RESIZE_PREVIEW); - } else { - gc.useStyle(DrawingStyle.RESIZE_FAIL); - } - } - gc.drawRect(state.mBounds); - - // Draw baseline preview too - if (feedback.dragBaseline != -1) { - int y = state.mBounds.y + feedback.dragBaseline; - gc.drawLine(state.mBounds.x, y, state.mBounds.x2(), y); - } - } - - List<String> strings = new ArrayList<String>(); - - showMatch(gc, state.mCurrentLeftMatch, state, strings, - state.mLeftMargin, ATTR_LAYOUT_MARGIN_LEFT); - showMatch(gc, state.mCurrentRightMatch, state, strings, - state.mRightMargin, ATTR_LAYOUT_MARGIN_RIGHT); - showMatch(gc, state.mCurrentTopMatch, state, strings, - state.mTopMargin, ATTR_LAYOUT_MARGIN_TOP); - showMatch(gc, state.mCurrentBottomMatch, state, strings, - state.mBottomMargin, ATTR_LAYOUT_MARGIN_BOTTOM); - - if (strings.size() > 0) { - // Update the drag tooltip - StringBuilder sb = new StringBuilder(200); - for (String s : strings) { - if (sb.length() > 0) { - sb.append('\n'); - } - sb.append(s); - } - feedback.tooltip = sb.toString(); - - // Set the tooltip orientation to ensure that it does not interfere with - // the constraint arrows - if (state.mCurrentLeftMatch != null) { - feedback.tooltipX = SegmentType.RIGHT; - } else if (state.mCurrentRightMatch != null) { - feedback.tooltipX = SegmentType.LEFT; - } - if (state.mCurrentTopMatch != null) { - feedback.tooltipY = SegmentType.BOTTOM; - } else if (state.mCurrentBottomMatch != null) { - feedback.tooltipY = SegmentType.TOP; - } - } else { - feedback.tooltip = null; - } - - if (state.mHorizontalCycle != null) { - paintCycle(gc, state, state.mHorizontalCycle); - } - if (state.mVerticalCycle != null) { - paintCycle(gc, state, state.mVerticalCycle); - } - } - - /** Paints a particular match constraint */ - private void showMatch(IGraphics gc, Match m, GuidelineHandler state, List<String> strings, - int margin, String marginAttribute) { - if (m == null) { - return; - } - ConstraintPainter.paintConstraint(gc, state.mBounds, m); - - // Display the constraint. Remove the @id/ and @+id/ prefixes to make the text - // shorter and easier to read. This doesn't use stripPrefix() because the id is - // usually not a prefix of the value (for example, 'layout_alignBottom=@+id/foo'). - String constraint = m.getConstraint(false /* generateId */); - String description = constraint.replace(NEW_ID_PREFIX, "").replace(ID_PREFIX, ""); - if (description.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { - description = description.substring(ATTR_LAYOUT_RESOURCE_PREFIX.length()); - } - if (margin > 0) { - int dp = state.getRulesEngine().pxToDp(margin); - description = String.format("%1$s, margin=%2$d dp", description, dp); - } - strings.add(description); - } - - /** Paints a constraint cycle */ - void paintCycle(IGraphics gc, GuidelineHandler state, List<Constraint> cycle) { - gc.useStyle(DrawingStyle.CYCLE); - assert cycle.size() > 0; - - INode from = cycle.get(0).from.node; - Rect fromBounds = from.getBounds(); - if (state.mDraggedNodes.contains(from)) { - fromBounds = state.mBounds; - } - Point fromCenter = fromBounds.center(); - INode to = null; - - List<Point> points = new ArrayList<Point>(); - points.add(fromCenter); - - for (Constraint constraint : cycle) { - assert constraint.from.node == from; - to = constraint.to.node; - assert from != null && to != null; - - Point toCenter = to.getBounds().center(); - points.add(toCenter); - - // Also go through the dragged node bounds - boolean isDragged = state.mDraggedNodes.contains(to); - if (isDragged) { - toCenter = state.mBounds.center(); - points.add(toCenter); - } - - from = to; - fromCenter = toCenter; - } - - points.add(fromCenter); - points.add(points.get(0)); - - for (int i = 1, n = points.size(); i < n; i++) { - gc.drawLine(points.get(i-1), points.get(i)); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/Match.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/Match.java deleted file mode 100644 index 6f3f0d0f7..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/Match.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.VALUE_TRUE; - - -import com.android.SdkConstants; -import static com.android.SdkConstants.ANDROID_URI; -import com.android.ide.common.api.Segment; - -/** A match is a potential pairing of two segments with a given {@link ConstraintType}. */ -class Match { - /** the edge of the dragged node that is matched */ - public final Segment with; - - /** the "other" edge that the dragged edge is matched with */ - public final Segment edge; - - /** the signed distance between the matched edges */ - public final int delta; - - /** the type of constraint this is a match for */ - public final ConstraintType type; - - /** whether this {@link Match} results in a cycle */ - public boolean cycle; - - /** The associated {@link GuidelineHander} which performed the match */ - private final GuidelineHandler mHandler; - - /** - * Create a new match. - * - * @param handler the handler which performed the match - * @param edge the "other" edge that the dragged edge is matched with - * @param with the edge of the dragged node that is matched - * @param type the type of constraint this is a match for - * @param delta the signed distance between the matched edges - */ - public Match(GuidelineHandler handler, Segment edge, Segment with, - ConstraintType type, int delta) { - mHandler = handler; - - this.edge = edge; - this.with = with; - this.type = type; - this.delta = delta; - } - - /** - * Returns the XML constraint attribute value for this match - * - * @param generateId whether an id should be generated if one is missing - * @return the XML constraint attribute value for this match - */ - public String getConstraint(boolean generateId) { - if (type.targetParent) { - return type.name + '=' + VALUE_TRUE; - } else { - String id = edge.id; - if (id == null || id.length() == -1) { - if (!generateId) { - // Placeholder to display for the user during dragging - id = "<generated>"; - } else { - // Must generate an id on the fly! - // See if it's been set by a different constraint we've already applied - // to this same node - id = edge.node.getStringAttr(ANDROID_URI, ATTR_ID); - if (id == null || id.length() == 0) { - id = mHandler.getRulesEngine().getUniqueId(edge.node.getFqcn()); - edge.node.setAttribute(ANDROID_URI, ATTR_ID, id); - } - } - } - return type.name + '=' + id; - } - } - - @Override - public String toString() { - return "Match [type=" + type + ", delta=" + delta + ", edge=" + edge - + "]"; - } -} 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 deleted file mode 100644 index 0fa915d81..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/MoveHandler.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * 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); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ResizeHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ResizeHandler.java deleted file mode 100644 index a5e071d74..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ResizeHandler.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * 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); - } -} |