1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.ide.common.layout.relative;
17 
18 import static com.android.ide.common.api.DrawingStyle.DEPENDENCY;
19 import static com.android.ide.common.api.DrawingStyle.GUIDELINE;
20 import static com.android.ide.common.api.DrawingStyle.GUIDELINE_DASHED;
21 import static com.android.ide.common.api.SegmentType.BASELINE;
22 import static com.android.ide.common.api.SegmentType.BOTTOM;
23 import static com.android.ide.common.api.SegmentType.CENTER_HORIZONTAL;
24 import static com.android.ide.common.api.SegmentType.CENTER_VERTICAL;
25 import static com.android.ide.common.api.SegmentType.LEFT;
26 import static com.android.ide.common.api.SegmentType.RIGHT;
27 import static com.android.ide.common.api.SegmentType.TOP;
28 import static com.android.ide.common.api.SegmentType.UNKNOWN;
29 import static com.android.ide.common.layout.relative.ConstraintType.ALIGN_BASELINE;
30 import static com.android.ide.common.layout.relative.ConstraintType.ALIGN_BOTTOM;
31 import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_ABOVE;
32 import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_BELOW;
33 import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_LEFT_OF;
34 import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_RIGHT_OF;
35 
36 import com.android.ide.common.api.DrawingStyle;
37 import com.android.ide.common.api.IGraphics;
38 import com.android.ide.common.api.INode;
39 import com.android.ide.common.api.Margins;
40 import com.android.ide.common.api.Rect;
41 import com.android.ide.common.api.SegmentType;
42 import com.android.ide.common.layout.relative.DependencyGraph.Constraint;
43 import com.android.ide.common.layout.relative.DependencyGraph.ViewData;
44 
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Set;
48 
49 /**
50  * The {@link ConstraintPainter} is responsible for painting relative layout constraints -
51  * such as a source node having its top edge constrained to a target node with a given margin.
52  * This painter is used both to show static constraints, as well as visualizing proposed
53  * constraints during a move or resize operation.
54  */
55 public class ConstraintPainter {
56     /** The size of the arrow head */
57     private static final int ARROW_SIZE = 5;
58     /** Size (height for horizontal, and width for vertical) parent feedback rectangles */
59     private static final int PARENT_RECT_SIZE = 12;
60 
61     /**
62      * Paints a given match as a constraint.
63      *
64      * @param graphics the graphics context
65      * @param sourceBounds the source bounds
66      * @param match the match
67      */
paintConstraint(IGraphics graphics, Rect sourceBounds, Match match)68     static void paintConstraint(IGraphics graphics, Rect sourceBounds, Match match) {
69         Rect targetBounds = match.edge.node.getBounds();
70         ConstraintType type = match.type;
71         assert type != null;
72         paintConstraint(graphics, type, match.with.node, sourceBounds, match.edge.node,
73                 targetBounds, null /* allConstraints */, true /* highlightTargetEdge */);
74     }
75 
76     /**
77      * Paints a constraint.
78      * <p>
79      * TODO: when there are multiple links originating in the same direction from
80      * center, maybe offset them slightly from each other?
81      *
82      * @param graphics the graphics context to draw into
83      * @param constraint The constraint to be drawn
84      */
paintConstraint(IGraphics graphics, Constraint constraint, Set<Constraint> allConstraints)85     private static void paintConstraint(IGraphics graphics, Constraint constraint,
86             Set<Constraint> allConstraints) {
87         ViewData source = constraint.from;
88         ViewData target = constraint.to;
89 
90         INode sourceNode = source.node;
91         INode targetNode = target.node;
92         if (sourceNode == targetNode) {
93             // Self reference - don't visualize
94             return;
95         }
96 
97         Rect sourceBounds = sourceNode.getBounds();
98         Rect targetBounds = targetNode.getBounds();
99         paintConstraint(graphics, constraint.type, sourceNode, sourceBounds, targetNode,
100                 targetBounds, allConstraints, false /* highlightTargetEdge */);
101     }
102 
103     /**
104      * Paint selection feedback by painting constraints for the selected nodes
105      *
106      * @param graphics the graphics context
107      * @param parentNode the parent relative layout
108      * @param childNodes the nodes whose constraints should be painted
109      * @param showDependents whether incoming constraints should be shown as well
110      */
paintSelectionFeedback(IGraphics graphics, INode parentNode, List<? extends INode> childNodes, boolean showDependents)111     public static void paintSelectionFeedback(IGraphics graphics, INode parentNode,
112             List<? extends INode> childNodes, boolean showDependents) {
113 
114         DependencyGraph dependencyGraph = new DependencyGraph(parentNode);
115         Set<INode> horizontalDeps = dependencyGraph.dependsOn(childNodes, false /* vertical */);
116         Set<INode> verticalDeps = dependencyGraph.dependsOn(childNodes, true /* vertical */);
117         Set<INode> deps = new HashSet<INode>(horizontalDeps.size() + verticalDeps.size());
118         deps.addAll(horizontalDeps);
119         deps.addAll(verticalDeps);
120         if (deps.size() > 0) {
121             graphics.useStyle(DEPENDENCY);
122             for (INode node : deps) {
123                 // Don't highlight the selected nodes themselves
124                 if (childNodes.contains(node)) {
125                     continue;
126                 }
127                 Rect bounds = node.getBounds();
128                 graphics.fillRect(bounds);
129             }
130         }
131 
132         graphics.useStyle(GUIDELINE);
133         for (INode childNode : childNodes) {
134             ViewData view = dependencyGraph.getView(childNode);
135             if (view == null) {
136                 continue;
137             }
138 
139             // Paint all incoming constraints
140             if (showDependents) {
141                 paintConstraints(graphics, view.dependedOnBy);
142             }
143 
144             // Paint all outgoing constraints
145             paintConstraints(graphics, view.dependsOn);
146         }
147     }
148 
149     /**
150      * Paints a set of constraints.
151      */
paintConstraints(IGraphics graphics, List<Constraint> constraints)152     private static void paintConstraints(IGraphics graphics, List<Constraint> constraints) {
153         Set<Constraint> mutableConstraintSet = new HashSet<Constraint>(constraints);
154 
155         // WORKAROUND! Hide alignBottom attachments if we also have a alignBaseline
156         // constraint; this is because we also *add* alignBottom attachments when you add
157         // alignBaseline constraints to work around a surprising behavior of baseline
158         // constraints.
159         for (Constraint constraint : constraints) {
160             if (constraint.type == ALIGN_BASELINE) {
161                 // Remove any baseline
162                 for (Constraint c : constraints) {
163                     if (c.type == ALIGN_BOTTOM && c.to.node == constraint.to.node) {
164                         mutableConstraintSet.remove(c);
165                     }
166                 }
167             }
168         }
169 
170         for (Constraint constraint : constraints) {
171             // paintConstraint can digest more than one constraint, so we need to keep
172             // checking to see if the given constraint is still relevant.
173             if (mutableConstraintSet.contains(constraint)) {
174                 paintConstraint(graphics, constraint, mutableConstraintSet);
175             }
176         }
177     }
178 
179     /**
180      * Paints a constraint of the given type from the given source node, to the
181      * given target node, with the specified bounds.
182      */
paintConstraint(IGraphics graphics, ConstraintType type, INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds, Set<Constraint> allConstraints, boolean highlightTargetEdge)183     private static void paintConstraint(IGraphics graphics, ConstraintType type, INode sourceNode,
184             Rect sourceBounds, INode targetNode, Rect targetBounds,
185             Set<Constraint> allConstraints, boolean highlightTargetEdge) {
186 
187         SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX;
188         SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY;
189         SegmentType targetSegmentTypeX = type.targetSegmentTypeX;
190         SegmentType targetSegmentTypeY = type.targetSegmentTypeY;
191 
192         // Horizontal center constraint?
193         if (sourceSegmentTypeX == CENTER_VERTICAL && targetSegmentTypeX == CENTER_VERTICAL) {
194             paintHorizontalCenterConstraint(graphics, sourceBounds, targetBounds);
195             return;
196         }
197 
198         // Vertical center constraint?
199         if (sourceSegmentTypeY == CENTER_HORIZONTAL && targetSegmentTypeY == CENTER_HORIZONTAL) {
200             paintVerticalCenterConstraint(graphics, sourceBounds, targetBounds);
201             return;
202         }
203 
204         // Corner constraint?
205         if (allConstraints != null
206                 && (type == LAYOUT_ABOVE || type == LAYOUT_BELOW
207                         || type == LAYOUT_LEFT_OF || type == LAYOUT_RIGHT_OF)) {
208             if (paintCornerConstraint(graphics, type, sourceNode, sourceBounds, targetNode,
209                     targetBounds, allConstraints)) {
210                 return;
211             }
212         }
213 
214         // Vertical constraint?
215         if (sourceSegmentTypeX == UNKNOWN) {
216             paintVerticalConstraint(graphics, type, sourceNode, sourceBounds, targetNode,
217                     targetBounds, highlightTargetEdge);
218             return;
219         }
220 
221         // Horizontal constraint?
222         if (sourceSegmentTypeY == UNKNOWN) {
223             paintHorizontalConstraint(graphics, type, sourceNode, sourceBounds, targetNode,
224                     targetBounds, highlightTargetEdge);
225             return;
226         }
227 
228         // This shouldn't happen - it means we have a constraint that defines all sides
229         // and is not a centering constraint
230         assert false;
231     }
232 
233     /**
234      * Paints a corner constraint, or returns false if this constraint is not a corner.
235      * A corner is one where there are two constraints from this source node to the
236      * same target node, one horizontal and one vertical, to the closest edges on
237      * the target node.
238      * <p>
239      * Corners are a common occurrence. If we treat the horizontal and vertical
240      * constraints separately (below & toRightOf), then we end up with a lot of
241      * extra lines and arrows -- e.g. two shared edges and arrows pointing to these
242      * shared edges:
243      *
244      * <pre>
245      *  +--------+ |
246      *  | Target -->
247      *  +----|---+ |
248      *       v
249      *  - - - - - -|- - - - - -
250      *                   ^
251      *             | +---|----+
252      *             <-- Source |
253      *             | +--------+
254      *
255      * Instead, we can simply draw a diagonal arrow here to represent BOTH constraints and
256      * reduce clutter:
257      *
258      *  +---------+
259      *  | Target _|
260      *  +-------|\+
261      *            \
262      *             \--------+
263      *             | Source |
264      *             +--------+
265      * </pre>
266      *
267      * @param graphics the graphics context to draw
268      * @param type the constraint to be drawn
269      * @param sourceNode the source node
270      * @param sourceBounds the bounds of the source node
271      * @param targetNode the target node
272      * @param targetBounds the bounds of the target node
273      * @param allConstraints the set of all constraints; if a corner is found and painted the
274      *    matching corner constraint is removed from the set
275      * @return true if the constraint was handled and painted as a corner, false otherwise
276      */
paintCornerConstraint(IGraphics graphics, ConstraintType type, INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds, Set<Constraint> allConstraints)277     private static boolean paintCornerConstraint(IGraphics graphics, ConstraintType type,
278             INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds,
279             Set<Constraint> allConstraints) {
280 
281         SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX;
282         SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY;
283         SegmentType targetSegmentTypeX = type.targetSegmentTypeX;
284         SegmentType targetSegmentTypeY = type.targetSegmentTypeY;
285 
286         ConstraintType opposite1 = null, opposite2 = null;
287         switch (type) {
288             case LAYOUT_BELOW:
289             case LAYOUT_ABOVE:
290                 opposite1 = LAYOUT_LEFT_OF;
291                 opposite2 = LAYOUT_RIGHT_OF;
292                 break;
293             case LAYOUT_LEFT_OF:
294             case LAYOUT_RIGHT_OF:
295                 opposite1 = LAYOUT_ABOVE;
296                 opposite2 = LAYOUT_BELOW;
297                 break;
298             default:
299                 return false;
300         }
301         Constraint pair = null;
302         for (Constraint constraint : allConstraints) {
303             if ((constraint.type == opposite1 || constraint.type == opposite2) &&
304                     constraint.to.node == targetNode && constraint.from.node == sourceNode) {
305                 pair = constraint;
306                 break;
307             }
308         }
309 
310         // TODO -- ensure that the nodes are adjacent! In other words, that
311         // their bounds are within N pixels.
312 
313         if (pair != null) {
314             // Visualize the corner constraint
315             if (sourceSegmentTypeX == UNKNOWN) {
316                 sourceSegmentTypeX = pair.type.sourceSegmentTypeX;
317             }
318             if (sourceSegmentTypeY == UNKNOWN) {
319                 sourceSegmentTypeY = pair.type.sourceSegmentTypeY;
320             }
321             if (targetSegmentTypeX == UNKNOWN) {
322                 targetSegmentTypeX = pair.type.targetSegmentTypeX;
323             }
324             if (targetSegmentTypeY == UNKNOWN) {
325                 targetSegmentTypeY = pair.type.targetSegmentTypeY;
326             }
327 
328             int x1, y1, x2, y2;
329             if (sourceSegmentTypeX == LEFT) {
330                 x1 = sourceBounds.x + 1 * sourceBounds.w / 4;
331             } else {
332                 x1 = sourceBounds.x + 3 * sourceBounds.w / 4;
333             }
334             if (sourceSegmentTypeY == TOP) {
335                 y1 = sourceBounds.y + 1 * sourceBounds.h / 4;
336             } else {
337                 y1 = sourceBounds.y + 3 * sourceBounds.h / 4;
338             }
339             if (targetSegmentTypeX == LEFT) {
340                 x2 = targetBounds.x + 1 * targetBounds.w / 4;
341             } else {
342                 x2 = targetBounds.x + 3 * targetBounds.w / 4;
343             }
344             if (targetSegmentTypeY == TOP) {
345                 y2 = targetBounds.y + 1 * targetBounds.h / 4;
346             } else {
347                 y2 = targetBounds.y + 3 * targetBounds.h / 4;
348             }
349 
350             graphics.useStyle(GUIDELINE);
351             graphics.drawArrow(x1, y1, x2, y2, ARROW_SIZE);
352 
353             // Don't process this constraint on its own later.
354             allConstraints.remove(pair);
355 
356             return true;
357         }
358 
359         return false;
360     }
361 
362     /**
363      * Paints a vertical constraint, handling the various scenarios where there are
364      * margins, or where the two nodes overlap horizontally and where they don't, etc.
365      * <p>
366      * Here's an example of what will be shown for a "below" constraint where the
367      * nodes do not overlap horizontally and the target node has a bottom margin:
368      * <pre>
369      *  +--------+
370      *  | Target |
371      *  +--------+
372      *       |
373      *       v
374      *   - - - - - - - - - - - - - -
375      *                         ^
376      *                         |
377      *                    +--------+
378      *                    | Source |
379      *                    +--------+
380      * </pre>
381      */
paintVerticalConstraint(IGraphics graphics, ConstraintType type, INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds, boolean highlightTargetEdge)382     private static void paintVerticalConstraint(IGraphics graphics, ConstraintType type,
383             INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds,
384             boolean highlightTargetEdge) {
385         SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY;
386         SegmentType targetSegmentTypeY = type.targetSegmentTypeY;
387         Margins targetMargins = targetNode.getMargins();
388 
389         assert sourceSegmentTypeY != UNKNOWN;
390         assert targetBounds != null;
391 
392         int sourceY = sourceSegmentTypeY.getY(sourceNode, sourceBounds);
393         int targetY = targetSegmentTypeY ==
394             UNKNOWN ? sourceY : targetSegmentTypeY.getY(targetNode, targetBounds);
395 
396         if (highlightTargetEdge && type.isRelativeToParentEdge()) {
397             graphics.useStyle(DrawingStyle.DROP_ZONE_ACTIVE);
398             graphics.fillRect(targetBounds.x, targetY - PARENT_RECT_SIZE / 2,
399                     targetBounds.x2(), targetY + PARENT_RECT_SIZE / 2);
400         }
401 
402         // First see if the two views overlap horizontally. If so, we can just draw a direct
403         // arrow from the source up to (or down to) the target.
404         //
405         //  +--------+
406         //  | Target |
407         //  +--------+
408         //         ^
409         //         |
410         //         |
411         //       +--------+
412         //       | Source |
413         //       +--------+
414         //
415         int maxLeft = Math.max(sourceBounds.x, targetBounds.x);
416         int minRight = Math.min(sourceBounds.x2(), targetBounds.x2());
417 
418         int center = (maxLeft + minRight) / 2;
419         if (center > sourceBounds.x && center < sourceBounds.x2()) {
420             // Yes, the lines overlap -- just draw a straight arrow
421             //
422             //
423             // If however there is a margin on the target edge, it should be drawn like this:
424             //
425             //  +--------+
426             //  | Target |
427             //  +--------+
428             //         |
429             //         |
430             //         v
431             //   - - - - - - -
432             //         ^
433             //         |
434             //         |
435             //       +--------+
436             //       | Source |
437             //       +--------+
438             //
439             // Use a minimum threshold for this visualization since it doesn't look good
440             // for small margins
441             if (targetSegmentTypeY == BOTTOM && targetMargins.bottom > 5) {
442                 int sharedY = targetY + targetMargins.bottom;
443                 if (sourceY > sharedY + 2) { // Skip when source falls on the margin line
444                     graphics.useStyle(GUIDELINE_DASHED);
445                     graphics.drawLine(targetBounds.x, sharedY, targetBounds.x2(), sharedY);
446                     graphics.useStyle(GUIDELINE);
447                     graphics.drawArrow(center, sourceY, center, sharedY + 2, ARROW_SIZE);
448                     graphics.drawArrow(center, targetY, center, sharedY - 3, ARROW_SIZE);
449                 } else {
450                     graphics.useStyle(GUIDELINE);
451                     // Draw reverse arrow to make it clear the node is as close
452                     // at it can be
453                     graphics.drawArrow(center, targetY, center, sourceY, ARROW_SIZE);
454                 }
455                 return;
456             } else if (targetSegmentTypeY == TOP && targetMargins.top > 5) {
457                 int sharedY = targetY - targetMargins.top;
458                 if (sourceY < sharedY - 2) {
459                     graphics.useStyle(GUIDELINE_DASHED);
460                     graphics.drawLine(targetBounds.x, sharedY, targetBounds.x2(), sharedY);
461                     graphics.useStyle(GUIDELINE);
462                     graphics.drawArrow(center, sourceY, center, sharedY - 3, ARROW_SIZE);
463                     graphics.drawArrow(center, targetY, center, sharedY + 3, ARROW_SIZE);
464                 } else {
465                     graphics.useStyle(GUIDELINE);
466                     graphics.drawArrow(center, targetY, center, sourceY, ARROW_SIZE);
467                 }
468                 return;
469             }
470 
471             // TODO: If the center falls smack in the center of the sourceBounds,
472             // AND the source node is part of the selection, then adjust the
473             // center location such that it is off to the side, let's say 1/4 or 3/4 of
474             // the overlap region, to ensure that it does not overlap the center selection
475             // handle
476 
477             // When the constraint is for two immediately adjacent edges, we
478             // need to make some adjustments to make sure the arrow points in the right
479             // direction
480             if (sourceY == targetY) {
481                 if (sourceSegmentTypeY == BOTTOM || sourceSegmentTypeY == BASELINE) {
482                     sourceY -= 2 * ARROW_SIZE;
483                 } else if (sourceSegmentTypeY == TOP) {
484                     sourceY += 2 * ARROW_SIZE;
485                 } else {
486                     assert sourceSegmentTypeY == CENTER_HORIZONTAL : sourceSegmentTypeY;
487                     sourceY += sourceBounds.h / 2 - 2 * ARROW_SIZE;
488                 }
489             } else if (sourceSegmentTypeY == BASELINE) {
490                 sourceY = targetY - 2 * ARROW_SIZE;
491             }
492 
493             // Center the vertical line in the overlap region
494             graphics.useStyle(GUIDELINE);
495             graphics.drawArrow(center, sourceY, center, targetY, ARROW_SIZE);
496 
497             return;
498         }
499 
500         // If there is no horizontal overlap in the vertical constraints, then we
501         // will show the attachment relative to a dashed line that extends beyond
502         // the target bounds, like this:
503         //
504         //  +--------+
505         //  | Target |
506         //  +--------+ - - - - - - - - -
507         //                         ^
508         //                         |
509         //                    +--------+
510         //                    | Source |
511         //                    +--------+
512         //
513         // However, if the target node has a vertical margin, we may need to offset
514         // the line:
515         //
516         //  +--------+
517         //  | Target |
518         //  +--------+
519         //       |
520         //       v
521         //   - - - - - - - - - - - - - -
522         //                         ^
523         //                         |
524         //                    +--------+
525         //                    | Source |
526         //                    +--------+
527         //
528         // If not, we'll need to indicate a shared edge. This is the edge that separate
529         // them (but this will require me to evaluate margins!)
530 
531         // Compute overlap region and pick the middle
532         int sharedY = targetSegmentTypeY ==
533             UNKNOWN ? sourceY : targetSegmentTypeY.getY(targetNode, targetBounds);
534         if (type.relativeToMargin) {
535             if (targetSegmentTypeY == TOP) {
536                 sharedY -= targetMargins.top;
537             } else if (targetSegmentTypeY == BOTTOM) {
538                 sharedY += targetMargins.bottom;
539             }
540         }
541 
542         int startX;
543         int endX;
544         if (center <= sourceBounds.x) {
545             startX = targetBounds.x + targetBounds.w / 4;
546             endX = sourceBounds.x2();
547         } else {
548             assert (center >= sourceBounds.x2());
549             startX = sourceBounds.x;
550             endX = targetBounds.x + 3 * targetBounds.w / 4;
551         }
552         // Must draw segmented line instead
553         // Place the arrow 1/4 instead of 1/2 in the source to avoid overlapping with the
554         // selection handles
555         graphics.useStyle(GUIDELINE_DASHED);
556         graphics.drawLine(startX, sharedY, endX, sharedY);
557 
558         // Adjust position of source arrow such that it does not sit across edge; it
559         // should point directly at the edge
560         if (Math.abs(sharedY - sourceY) < 2 * ARROW_SIZE) {
561             if (sourceSegmentTypeY == BASELINE) {
562                 sourceY = sharedY - 2 * ARROW_SIZE;
563             } else if (sourceSegmentTypeY == TOP) {
564                 sharedY = sourceY;
565                 sourceY = sharedY + 2 * ARROW_SIZE;
566             } else {
567                 sharedY = sourceY;
568                 sourceY = sharedY - 2 * ARROW_SIZE;
569             }
570         }
571 
572         graphics.useStyle(GUIDELINE);
573 
574         // Draw the line from the source anchor to the shared edge
575         int x = sourceBounds.x + ((sourceSegmentTypeY == BASELINE) ?
576                 sourceBounds.w / 2 :  sourceBounds.w / 4);
577         graphics.drawArrow(x, sourceY, x, sharedY, ARROW_SIZE);
578 
579         // Draw the line from the target to the horizontal shared edge
580         int tx = targetBounds.centerX();
581         if (targetSegmentTypeY == TOP) {
582             int ty = targetBounds.y;
583             int margin = targetMargins.top;
584             if (margin == 0 || !type.relativeToMargin) {
585                 graphics.drawArrow(tx, ty + 2 * ARROW_SIZE, tx, ty, ARROW_SIZE);
586             } else {
587                 graphics.drawArrow(tx, ty, tx, ty - margin, ARROW_SIZE);
588             }
589         } else if (targetSegmentTypeY == BOTTOM) {
590             int ty = targetBounds.y2();
591             int margin = targetMargins.bottom;
592             if (margin == 0 || !type.relativeToMargin) {
593                 graphics.drawArrow(tx, ty - 2 * ARROW_SIZE, tx, ty, ARROW_SIZE);
594             } else {
595                 graphics.drawArrow(tx, ty, tx, ty + margin, ARROW_SIZE);
596             }
597         } else {
598             assert targetSegmentTypeY == BASELINE : targetSegmentTypeY;
599             int ty = targetSegmentTypeY.getY(targetNode, targetBounds);
600             graphics.drawArrow(tx, ty - 2 * ARROW_SIZE, tx, ty, ARROW_SIZE);
601         }
602 
603         return;
604     }
605 
606     /**
607      * Paints a horizontal constraint, handling the various scenarios where there are margins,
608      * or where the two nodes overlap horizontally and where they don't, etc.
609      */
paintHorizontalConstraint(IGraphics graphics, ConstraintType type, INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds, boolean highlightTargetEdge)610     private static void paintHorizontalConstraint(IGraphics graphics, ConstraintType type,
611             INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds,
612             boolean highlightTargetEdge) {
613         SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX;
614         SegmentType targetSegmentTypeX = type.targetSegmentTypeX;
615         Margins targetMargins = targetNode.getMargins();
616 
617         assert sourceSegmentTypeX != UNKNOWN;
618         assert targetBounds != null;
619 
620         // See paintVerticalConstraint for explanations of the various cases.
621 
622         int sourceX = sourceSegmentTypeX.getX(sourceNode, sourceBounds);
623         int targetX = targetSegmentTypeX == UNKNOWN ?
624                 sourceX : targetSegmentTypeX.getX(targetNode, targetBounds);
625 
626         if (highlightTargetEdge && type.isRelativeToParentEdge()) {
627             graphics.useStyle(DrawingStyle.DROP_ZONE_ACTIVE);
628             graphics.fillRect(targetX - PARENT_RECT_SIZE / 2, targetBounds.y,
629                     targetX + PARENT_RECT_SIZE / 2, targetBounds.y2());
630         }
631 
632         int maxTop = Math.max(sourceBounds.y, targetBounds.y);
633         int minBottom = Math.min(sourceBounds.y2(), targetBounds.y2());
634 
635         // First see if the two views overlap vertically. If so, we can just draw a direct
636         // arrow from the source over to the target.
637         int center = (maxTop + minBottom) / 2;
638         if (center > sourceBounds.y && center < sourceBounds.y2()) {
639             // See if we should draw a margin line
640             if (targetSegmentTypeX == RIGHT && targetMargins.right > 5) {
641                 int sharedX = targetX + targetMargins.right;
642                 if (sourceX > sharedX + 2) { // Skip when source falls on the margin line
643                     graphics.useStyle(GUIDELINE_DASHED);
644                     graphics.drawLine(sharedX, targetBounds.y, sharedX, targetBounds.y2());
645                     graphics.useStyle(GUIDELINE);
646                     graphics.drawArrow(sourceX, center, sharedX + 2, center, ARROW_SIZE);
647                     graphics.drawArrow(targetX, center, sharedX - 3, center, ARROW_SIZE);
648                 } else {
649                     graphics.useStyle(GUIDELINE);
650                     // Draw reverse arrow to make it clear the node is as close
651                     // at it can be
652                     graphics.drawArrow(targetX, center, sourceX, center, ARROW_SIZE);
653                 }
654                 return;
655             } else if (targetSegmentTypeX == LEFT && targetMargins.left > 5) {
656                 int sharedX = targetX - targetMargins.left;
657                 if (sourceX < sharedX - 2) {
658                     graphics.useStyle(GUIDELINE_DASHED);
659                     graphics.drawLine(sharedX, targetBounds.y, sharedX, targetBounds.y2());
660                     graphics.useStyle(GUIDELINE);
661                     graphics.drawArrow(sourceX, center, sharedX - 3, center, ARROW_SIZE);
662                     graphics.drawArrow(targetX, center, sharedX + 3, center, ARROW_SIZE);
663                 } else {
664                     graphics.useStyle(GUIDELINE);
665                     graphics.drawArrow(targetX, center, sourceX, center, ARROW_SIZE);
666                 }
667                 return;
668             }
669 
670             if (sourceX == targetX) {
671                 if (sourceSegmentTypeX == RIGHT) {
672                     sourceX -= 2 * ARROW_SIZE;
673                 } else if (sourceSegmentTypeX == LEFT ) {
674                     sourceX += 2 * ARROW_SIZE;
675                 } else {
676                     assert sourceSegmentTypeX == CENTER_VERTICAL : sourceSegmentTypeX;
677                     sourceX += sourceBounds.w / 2 - 2 * ARROW_SIZE;
678                 }
679             }
680 
681             graphics.useStyle(GUIDELINE);
682             graphics.drawArrow(sourceX, center, targetX, center, ARROW_SIZE);
683             return;
684         }
685 
686         // Segment line
687 
688         // Compute overlap region and pick the middle
689         int sharedX = targetSegmentTypeX == UNKNOWN ?
690                 sourceX : targetSegmentTypeX.getX(targetNode, targetBounds);
691         if (type.relativeToMargin) {
692             if (targetSegmentTypeX == LEFT) {
693                 sharedX -= targetMargins.left;
694             } else if (targetSegmentTypeX == RIGHT) {
695                 sharedX += targetMargins.right;
696             }
697         }
698 
699         int startY, endY;
700         if (center <= sourceBounds.y) {
701             startY = targetBounds.y + targetBounds.h / 4;
702             endY = sourceBounds.y2();
703         } else {
704             assert (center >= sourceBounds.y2());
705             startY = sourceBounds.y;
706             endY = targetBounds.y + 3 * targetBounds.h / 2;
707         }
708 
709         // Must draw segmented line instead
710         // Place at 1/4 instead of 1/2 to avoid overlapping with selection handles
711         int y = sourceBounds.y + sourceBounds.h / 4;
712         graphics.useStyle(GUIDELINE_DASHED);
713         graphics.drawLine(sharedX, startY, sharedX, endY);
714 
715         // Adjust position of source arrow such that it does not sit across edge; it
716         // should point directly at the edge
717         if (Math.abs(sharedX - sourceX) < 2 * ARROW_SIZE) {
718             if (sourceSegmentTypeX == LEFT) {
719                 sharedX = sourceX;
720                 sourceX = sharedX + 2 * ARROW_SIZE;
721             } else {
722                 sharedX = sourceX;
723                 sourceX = sharedX - 2 * ARROW_SIZE;
724             }
725         }
726 
727         graphics.useStyle(GUIDELINE);
728 
729         // Draw the line from the source anchor to the shared edge
730         graphics.drawArrow(sourceX, y, sharedX, y, ARROW_SIZE);
731 
732         // Draw the line from the target to the horizontal shared edge
733         int ty = targetBounds.centerY();
734         if (targetSegmentTypeX == LEFT) {
735             int tx = targetBounds.x;
736             int margin = targetMargins.left;
737             if (margin == 0 || !type.relativeToMargin) {
738                 graphics.drawArrow(tx + 2 * ARROW_SIZE, ty, tx, ty, ARROW_SIZE);
739             } else {
740                 graphics.drawArrow(tx, ty, tx - margin, ty, ARROW_SIZE);
741             }
742         } else {
743             assert targetSegmentTypeX == RIGHT;
744             int tx = targetBounds.x2();
745             int margin = targetMargins.right;
746             if (margin == 0 || !type.relativeToMargin) {
747                 graphics.drawArrow(tx - 2 * ARROW_SIZE, ty, tx, ty, ARROW_SIZE);
748             } else {
749                 graphics.drawArrow(tx, ty, tx + margin, ty, ARROW_SIZE);
750             }
751         }
752 
753         return;
754     }
755 
756     /**
757      * Paints a vertical center constraint. The constraint is shown as a dashed line
758      * through the vertical view, and a solid line over the node bounds.
759      */
paintVerticalCenterConstraint(IGraphics graphics, Rect sourceBounds, Rect targetBounds)760     private static void paintVerticalCenterConstraint(IGraphics graphics, Rect sourceBounds,
761             Rect targetBounds) {
762         graphics.useStyle(GUIDELINE_DASHED);
763         graphics.drawLine(targetBounds.x, targetBounds.centerY(),
764                 targetBounds.x2(), targetBounds.centerY());
765         graphics.useStyle(GUIDELINE);
766         graphics.drawLine(sourceBounds.x, sourceBounds.centerY(),
767                 sourceBounds.x2(), sourceBounds.centerY());
768     }
769 
770     /**
771      * Paints a horizontal center constraint. The constraint is shown as a dashed line
772      * through the horizontal view, and a solid line over the node bounds.
773      */
paintHorizontalCenterConstraint(IGraphics graphics, Rect sourceBounds, Rect targetBounds)774     private static void paintHorizontalCenterConstraint(IGraphics graphics, Rect sourceBounds,
775             Rect targetBounds) {
776         graphics.useStyle(GUIDELINE_DASHED);
777         graphics.drawLine(targetBounds.centerX(), targetBounds.y,
778                 targetBounds.centerX(), targetBounds.y2());
779         graphics.useStyle(GUIDELINE);
780         graphics.drawLine(sourceBounds.centerX(), sourceBounds.y,
781                 sourceBounds.centerX(), sourceBounds.y2());
782     }
783 }