Home | History | Annotate | Download | only in relative
      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      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 }