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 }