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 }