aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintPainter.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintPainter.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintPainter.java783
1 files changed, 783 insertions, 0 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
new file mode 100644
index 000000000..447d2d880
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintPainter.java
@@ -0,0 +1,783 @@
+/*
+ * 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