1 /* 2 * Copyright (C) 2008 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 17 package com.android.ide.eclipse.adt.internal.editors.layout.parts; 18 19 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; 20 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; 21 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutConstants; 22 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor.UiEditorActions; 23 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiLayoutEditPart.HighlightInfo; 24 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; 25 import com.android.sdklib.SdkConstants; 26 27 import org.eclipse.draw2d.geometry.Point; 28 import org.eclipse.draw2d.geometry.Rectangle; 29 30 import java.util.HashMap; 31 import java.util.Map.Entry; 32 33 /** 34 * Utility methods used when dealing with dropping EditPart on the GLE. 35 * <p/> 36 * This class uses some temporary static storage to avoid excessive allocations during 37 * drop operations. It is expected to only be invoked from the main UI thread with no 38 * concurrent access. 39 * 40 * @since GLE1 41 */ 42 class DropFeedback { 43 44 private static final int TOP = 0; 45 private static final int LEFT = 1; 46 private static final int BOTTOM = 2; 47 private static final int RIGHT = 3; 48 private static final int MAX_DIR = RIGHT; 49 50 private static final int sOppositeDirection[] = { BOTTOM, RIGHT, TOP, LEFT }; 51 52 private static final UiElementEditPart sTempClosests[] = new UiElementEditPart[4]; 53 private static final int sTempMinDists[] = new int[4]; 54 55 56 /** 57 * Target information computed from a drop on a RelativeLayout. 58 * We need only one instance of this and it is sRelativeInfo. 59 */ 60 private static class RelativeInfo { 61 /** The two target parts 0 and 1. They can be null, meaning a border is used. 62 * The direction from part 0 to 1 is always to-the-right or to-the-bottom. */ 63 final UiElementEditPart targetParts[] = new UiElementEditPart[2]; 64 /** Direction from the anchor part to the drop point. */ 65 int direction; 66 /** The index of the "anchor" part, i.e. the closest one selected by the drop. 67 * This can be either 0 or 1. The corresponding part can be null. */ 68 int anchorIndex; 69 } 70 71 /** The single RelativeInfo used to compute results from a drop on a RelativeLayout */ 72 private static final RelativeInfo sRelativeInfo = new RelativeInfo(); 73 /** A temporary array of 2 {@link UiElementEditPart} to avoid allocations. */ 74 private static final UiElementEditPart sTempTwoParts[] = new UiElementEditPart[2]; 75 76 77 private DropFeedback() { 78 } 79 80 81 //----- Package methods called by users of this helper class ----- 82 83 84 /** 85 * This method is used by {@link ElementCreateCommand#execute()} when a new item 86 * needs to be "dropped" in the current XML document. It creates the new item using 87 * the given descriptor as a child of the given parent part. 88 * 89 * @param parentPart The parent part. 90 * @param descriptor The descriptor for the new XML element. 91 * @param where The drop location (in parent coordinates) 92 * @param actions The helper that actually modifies the XML model. 93 */ 94 static void addElementToXml(UiElementEditPart parentPart, 95 ElementDescriptor descriptor, Point where, 96 UiEditorActions actions) { 97 98 String layoutXmlName = getXmlLocalName(parentPart); 99 RelativeInfo info = null; 100 UiElementEditPart sibling = null; 101 102 // TODO consider merge like a vertical layout 103 // TODO consider TableLayout like a linear 104 if (LayoutConstants.LINEAR_LAYOUT.equals(layoutXmlName)) { 105 sibling = findLinearTarget(parentPart, where)[1]; 106 107 } else if (LayoutConstants.RELATIVE_LAYOUT.equals(layoutXmlName)) { 108 info = findRelativeTarget(parentPart, where, sRelativeInfo); 109 if (info != null) { 110 sibling = info.targetParts[info.anchorIndex]; 111 sibling = getNextUiSibling(sibling); 112 } 113 } 114 115 if (actions != null) { 116 UiElementNode uiSibling = sibling != null ? sibling.getUiNode() : null; 117 UiElementNode uiParent = parentPart.getUiNode(); 118 UiElementNode uiNode = actions.addElement(uiParent, uiSibling, descriptor, 119 false /*updateLayout*/); 120 121 if (LayoutConstants.ABSOLUTE_LAYOUT.equals(layoutXmlName)) { 122 adjustAbsoluteAttributes(uiNode, where); 123 } else if (LayoutConstants.RELATIVE_LAYOUT.equals(layoutXmlName)) { 124 adustRelativeAttributes(uiNode, info); 125 } 126 } 127 } 128 129 /** 130 * This method is used by {@link UiLayoutEditPart#showDropTarget(Point)} to compute 131 * highlight information when a drop target is moved over a valid drop area. 132 * <p/> 133 * Since there are no "out" parameters in Java, all the information is returned 134 * via the {@link HighlightInfo} structure passed as parameter. 135 * 136 * @param parentPart The parent part, always a layout. 137 * @param highlightInfo A structure where result is stored to perform highlight. 138 * @param where The target drop point, in parent's coordinates 139 * @return The {@link HighlightInfo} structured passed as a parameter, for convenience. 140 */ 141 static HighlightInfo computeDropFeedback(UiLayoutEditPart parentPart, 142 HighlightInfo highlightInfo, 143 Point where) { 144 String layoutType = getXmlLocalName(parentPart); 145 146 if (LayoutConstants.ABSOLUTE_LAYOUT.equals(layoutType)) { 147 highlightInfo.anchorPoint = where; 148 149 } else if (LayoutConstants.LINEAR_LAYOUT.equals(layoutType)) { 150 boolean isVertical = isVertical(parentPart); 151 152 highlightInfo.childParts = findLinearTarget(parentPart, where); 153 computeLinearLine(parentPart, isVertical, highlightInfo); 154 155 } else if (LayoutConstants.RELATIVE_LAYOUT.equals(layoutType)) { 156 157 RelativeInfo info = findRelativeTarget(parentPart, where, sRelativeInfo); 158 if (info != null) { 159 highlightInfo.childParts = sRelativeInfo.targetParts; 160 computeRelativeLine(parentPart, info, highlightInfo); 161 } 162 } 163 164 return highlightInfo; 165 } 166 167 168 //----- Misc utilities ----- 169 170 /** 171 * Returns the next UI sibling of this part, i.e. the element which is just after in 172 * the UI/XML order in the same parent. Returns null if there's no such part. 173 * <p/> 174 * Note: by "UI sibling" here we mean the sibling in the UiNode hierarchy. By design the 175 * UiNode model has the <em>exact</em> same order as the XML model. This has nothing to do 176 * with the "user interface" order that you see on the rendered Android layouts (e.g. for 177 * LinearLayout they are the same but for AbsoluteLayout or RelativeLayout the UI/XML model 178 * order can be vastly different from the user interface order.) 179 */ 180 private static UiElementEditPart getNextUiSibling(UiElementEditPart part) { 181 if (part != null) { 182 UiElementNode uiNode = part.getUiNode(); 183 if (uiNode != null) { 184 uiNode = uiNode.getUiNextSibling(); 185 } 186 if (uiNode != null) { 187 for (Object childPart : part.getParent().getChildren()) { 188 if (childPart instanceof UiElementEditPart && 189 ((UiElementEditPart) childPart).getUiNode() == uiNode) { 190 return (UiElementEditPart) childPart; 191 } 192 } 193 } 194 } 195 return null; 196 } 197 198 /** 199 * Returns the XML local name of the ui node associated with this edit part or null. 200 */ 201 private static String getXmlLocalName(UiElementEditPart editPart) { 202 UiElementNode uiNode = editPart.getUiNode(); 203 if (uiNode != null) { 204 ElementDescriptor desc = uiNode.getDescriptor(); 205 if (desc != null) { 206 return desc.getXmlLocalName(); 207 } 208 } 209 return null; 210 } 211 212 /** 213 * Adjusts the attributes of a new node dropped in an AbsoluteLayout. 214 * 215 * @param uiNode The new node being dropped. 216 * @param where The drop location (in parent coordinates) 217 */ 218 private static void adjustAbsoluteAttributes(final UiElementNode uiNode, final Point where) { 219 if (where == null) { 220 return; 221 } 222 uiNode.getEditor().editXmlModel(new Runnable() { 223 public void run() { 224 uiNode.setAttributeValue( 225 LayoutConstants.ATTR_LAYOUT_X, 226 SdkConstants.NS_RESOURCES, 227 String.format(LayoutConstants.VALUE_N_DIP, where.x), 228 false /* override */); 229 uiNode.setAttributeValue( 230 LayoutConstants.ATTR_LAYOUT_Y, 231 SdkConstants.NS_RESOURCES, 232 String.format(LayoutConstants.VALUE_N_DIP, where.y), 233 false /* override */); 234 235 uiNode.commitDirtyAttributesToXml(); 236 } 237 }); 238 } 239 240 /** 241 * Adjusts the attributes of a new node dropped in a RelativeLayout: 242 * <ul> 243 * <li> anchor part: the one the user selected (or the closest) and to which the new one 244 * will "attach". The anchor part can be null, either because the layout is currently 245 * empty or the user is attaching to an existing empty border. 246 * <li> direction: the direction from the anchor part to the drop point. That's also the 247 * direction from the anchor part to the new part. 248 * <li> the new node; it is created either after the anchor for right or top directions 249 * or before the anchor for left or bottom directions. This means the new part can 250 * reference the id of the anchor part. 251 * </ul> 252 * 253 * Several cases: 254 * <ul> 255 * <li> set: layout_above/below/toLeftOf/toRightOf to point to the anchor. 256 * <li> copy: layout_centerHorizontal for top/bottom directions 257 * <li> copy: layout_centerVertical for left/right directions. 258 * <li> copy: layout_above/below/toLeftOf/toRightOf for the orthogonal direction 259 * (i.e. top/bottom or left/right.) 260 * </ul> 261 * 262 * @param uiNode The new node being dropped. 263 * @param info The context computed by {@link #findRelativeTarget(UiElementEditPart, Point, RelativeInfo)}. 264 */ 265 private static void adustRelativeAttributes(final UiElementNode uiNode, RelativeInfo info) { 266 if (uiNode == null || info == null) { 267 return; 268 } 269 270 final UiElementEditPart anchorPart = info.targetParts[info.anchorIndex]; // can be null 271 final int direction = info.direction; 272 273 uiNode.getEditor().editXmlModel(new Runnable() { 274 public void run() { 275 HashMap<String, String> map = new HashMap<String, String>(); 276 277 UiElementNode anchorUiNode = anchorPart != null ? anchorPart.getUiNode() : null; 278 String anchorId = anchorUiNode != null 279 ? anchorUiNode.getAttributeValue(LayoutConstants.ATTR_ID) 280 : null; 281 282 if (anchorId == null) { 283 anchorId = DescriptorsUtils.getFreeWidgetId(anchorUiNode); 284 anchorUiNode.setAttributeValue( 285 LayoutConstants.ATTR_ID, 286 SdkConstants.NS_RESOURCES, 287 anchorId, 288 true /*override*/); 289 } 290 291 if (anchorId != null) { 292 switch(direction) { 293 case TOP: 294 map.put(LayoutConstants.ATTR_LAYOUT_ABOVE, anchorId); 295 break; 296 case BOTTOM: 297 map.put(LayoutConstants.ATTR_LAYOUT_BELOW, anchorId); 298 break; 299 case LEFT: 300 map.put(LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF, anchorId); 301 break; 302 case RIGHT: 303 map.put(LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF, anchorId); 304 break; 305 } 306 307 switch(direction) { 308 case TOP: 309 case BOTTOM: 310 map.put(LayoutConstants.ATTR_LAYOUT_CENTER_HORIZONTAL, 311 anchorUiNode.getAttributeValue( 312 LayoutConstants.ATTR_LAYOUT_CENTER_HORIZONTAL)); 313 314 map.put(LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF, 315 anchorUiNode.getAttributeValue( 316 LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF)); 317 map.put(LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF, 318 anchorUiNode.getAttributeValue( 319 LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF)); 320 break; 321 case LEFT: 322 case RIGHT: 323 map.put(LayoutConstants.ATTR_LAYOUT_CENTER_VERTICAL, 324 anchorUiNode.getAttributeValue( 325 LayoutConstants.ATTR_LAYOUT_CENTER_VERTICAL)); 326 map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_BASELINE, 327 anchorUiNode.getAttributeValue( 328 LayoutConstants.ATTR_LAYOUT_ALIGN_BASELINE)); 329 330 map.put(LayoutConstants.ATTR_LAYOUT_ABOVE, 331 anchorUiNode.getAttributeValue(LayoutConstants.ATTR_LAYOUT_ABOVE)); 332 map.put(LayoutConstants.ATTR_LAYOUT_BELOW, 333 anchorUiNode.getAttributeValue(LayoutConstants.ATTR_LAYOUT_BELOW)); 334 break; 335 } 336 } else { 337 // We don't have an anchor node. Assume we're targeting a border and align 338 // to the parent. 339 switch(direction) { 340 case TOP: 341 map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP, 342 LayoutConstants.VALUE_TRUE); 343 break; 344 case BOTTOM: 345 map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, 346 LayoutConstants.VALUE_TRUE); 347 break; 348 case LEFT: 349 map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT, 350 LayoutConstants.VALUE_TRUE); 351 break; 352 case RIGHT: 353 map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT, 354 LayoutConstants.VALUE_TRUE); 355 break; 356 } 357 } 358 359 for (Entry<String, String> entry : map.entrySet()) { 360 uiNode.setAttributeValue( 361 entry.getKey(), 362 SdkConstants.NS_RESOURCES, 363 entry.getValue(), 364 true /* override */); 365 } 366 uiNode.commitDirtyAttributesToXml(); 367 } 368 }); 369 } 370 371 372 //----- LinearLayout -------- 373 374 /** 375 * For a given parent edit part that MUST represent a LinearLayout, finds the 376 * element before which the location points. 377 * <p/> 378 * This computes the edit part that corresponds to what will be the "next sibling" of the new 379 * element. 380 * <p/> 381 * It returns null if it can't be determined, in which case the element will be added at the 382 * end of the parent child list. 383 * 384 * @return The edit parts that correspond to what will be the "prev" and "next sibling" of the 385 * new element. The previous sibling can be null if adding before the first element. 386 * The next sibling can be null if adding after the last element. 387 */ 388 private static UiElementEditPart[] findLinearTarget(UiElementEditPart parent, Point point) { 389 // default orientation is horizontal 390 boolean isVertical = isVertical(parent); 391 392 int target = isVertical ? point.y : point.x; 393 394 UiElementEditPart prev = null; 395 UiElementEditPart next = null; 396 397 for (Object child : parent.getChildren()) { 398 if (child instanceof UiElementEditPart) { 399 UiElementEditPart childPart = (UiElementEditPart) child; 400 Point p = childPart.getBounds().getCenter(); 401 int middle = isVertical ? p.y : p.x; 402 if (target < middle) { 403 next = childPart; 404 break; 405 } 406 prev = childPart; 407 } 408 } 409 410 sTempTwoParts[0] = prev; 411 sTempTwoParts[1] = next; 412 return sTempTwoParts; 413 } 414 415 /** 416 * Computes the highlight line between two parts. 417 * <p/> 418 * The two parts are listed in HighlightInfo.childParts[2]. Any of the parts 419 * can be null. 420 * The result is stored in HighlightInfo. 421 * <p/> 422 * Caller must clear the HighlightInfo as appropriate before this call. 423 * 424 * @param parentPart The parent part, always a layout. 425 * @param isVertical True for vertical parts, thus computing an horizontal line. 426 * @param highlightInfo The in-out highlight info. 427 */ 428 private static void computeLinearLine(UiLayoutEditPart parentPart, 429 boolean isVertical, HighlightInfo highlightInfo) { 430 Rectangle r = parentPart.getBounds(); 431 432 if (isVertical) { 433 Point p = null; 434 UiElementEditPart part = highlightInfo.childParts[0]; 435 if (part != null) { 436 p = part.getBounds().getBottom(); 437 } else { 438 part = highlightInfo.childParts[1]; 439 if (part != null) { 440 p = part.getBounds().getTop(); 441 } 442 } 443 if (p != null) { 444 // horizontal line with middle anchor point 445 highlightInfo.tempPoints[0].setLocation(0, p.y); 446 highlightInfo.tempPoints[1].setLocation(r.width, p.y); 447 highlightInfo.linePoints = highlightInfo.tempPoints; 448 highlightInfo.anchorPoint = p.setLocation(r.width / 2, p.y); 449 } 450 } else { 451 Point p = null; 452 UiElementEditPart part = highlightInfo.childParts[0]; 453 if (part != null) { 454 p = part.getBounds().getRight(); 455 } else { 456 part = highlightInfo.childParts[1]; 457 if (part != null) { 458 p = part.getBounds().getLeft(); 459 } 460 } 461 if (p != null) { 462 // vertical line with middle anchor point 463 highlightInfo.tempPoints[0].setLocation(p.x, 0); 464 highlightInfo.tempPoints[1].setLocation(p.x, r.height); 465 highlightInfo.linePoints = highlightInfo.tempPoints; 466 highlightInfo.anchorPoint = p.setLocation(p.x, r.height / 2); 467 } 468 } 469 } 470 471 /** 472 * Returns true if the linear layout is marked as vertical. 473 * 474 * @param parent The a layout part that must be a LinearLayout 475 * @return True if the linear layout has a vertical orientation attribute. 476 */ 477 private static boolean isVertical(UiElementEditPart parent) { 478 String orientation = parent.getStringAttr("orientation"); //$NON-NLS-1$ 479 boolean isVertical = "vertical".equals(orientation) || //$NON-NLS-1$ 480 "1".equals(orientation); //$NON-NLS-1$ 481 return isVertical; 482 } 483 484 485 //----- RelativeLayout -------- 486 487 /** 488 * Finds the "target" relative layout item for the drop operation & feedback. 489 * <p/> 490 * If the drop point is exactly on a current item, simply returns the side the drop will occur 491 * compared to the center of that element. For the actual XML, we'll need to insert *after* 492 * that element to make sure that referenced are defined in the right order. 493 * In that case the result contains two elements, the second one always being on the right or 494 * bottom side of the first one. When insert in XML, we want to insert right before that 495 * second element or at the end of the child list if the second element is null. 496 * <p/> 497 * If the drop point is not exactly on a current element, find the closest in each 498 * direction and align with the two closest of these. 499 * 500 * @return null if we fail to find anything (such as there are currently no items to compare 501 * with); otherwise fills the {@link RelativeInfo} and return it. 502 */ 503 private static RelativeInfo findRelativeTarget(UiElementEditPart parent, 504 Point point, 505 RelativeInfo outInfo) { 506 507 for (int i = 0; i < 4; i++) { 508 sTempMinDists[i] = Integer.MAX_VALUE; 509 sTempClosests[i] = null; 510 } 511 512 513 for (Object child : parent.getChildren()) { 514 if (child instanceof UiElementEditPart) { 515 UiElementEditPart childPart = (UiElementEditPart) child; 516 Rectangle r = childPart.getBounds(); 517 if (r.contains(point)) { 518 519 float rx = ((float)(point.x - r.x) / (float)r.width ) - 0.5f; 520 float ry = ((float)(point.y - r.y) / (float)r.height) - 0.5f; 521 522 /* TOP 523 * \ / 524 * \ / 525 * L X R 526 * / \ 527 * / \ 528 * BOT 529 */ 530 531 int index = 0; 532 if (Math.abs(rx) >= Math.abs(ry)) { 533 if (rx < 0) { 534 outInfo.direction = LEFT; 535 index = 1; 536 } else { 537 outInfo.direction = RIGHT; 538 } 539 } else { 540 if (ry < 0) { 541 outInfo.direction = TOP; 542 index = 1; 543 } else { 544 outInfo.direction = BOTTOM; 545 } 546 } 547 548 outInfo.anchorIndex = index; 549 outInfo.targetParts[index] = childPart; 550 outInfo.targetParts[1 - index] = findClosestPart(childPart, 551 outInfo.direction); 552 553 return outInfo; 554 } 555 556 computeClosest(point, childPart, sTempClosests, sTempMinDists, TOP); 557 computeClosest(point, childPart, sTempClosests, sTempMinDists, LEFT); 558 computeClosest(point, childPart, sTempClosests, sTempMinDists, BOTTOM); 559 computeClosest(point, childPart, sTempClosests, sTempMinDists, RIGHT); 560 } 561 } 562 563 UiElementEditPart closest = null; 564 int minDist = Integer.MAX_VALUE; 565 int minDir = -1; 566 567 for (int i = 0; i <= MAX_DIR; i++) { 568 if (sTempClosests[i] != null && sTempMinDists[i] < minDist) { 569 closest = sTempClosests[i]; 570 minDist = sTempMinDists[i]; 571 minDir = i; 572 } 573 } 574 575 if (closest != null) { 576 int index = 0; 577 switch(minDir) { 578 case TOP: 579 case LEFT: 580 index = 0; 581 break; 582 case BOTTOM: 583 case RIGHT: 584 index = 1; 585 break; 586 } 587 outInfo.anchorIndex = index; 588 outInfo.targetParts[index] = closest; 589 outInfo.targetParts[1 - index] = findClosestPart(closest, sOppositeDirection[minDir]); 590 outInfo.direction = sOppositeDirection[minDir]; 591 return outInfo; 592 } 593 594 return null; 595 } 596 597 /** 598 * Computes the highlight line for a drop on a RelativeLayout. 599 * <p/> 600 * The line is always placed on the side of the anchor part indicated by the 601 * direction. The direction always point from the anchor part to the drop point. 602 * <p/> 603 * If there's no anchor part, use the other one with a reversed direction. 604 * <p/> 605 * On output, this updates the {@link HighlightInfo}. 606 */ 607 private static void computeRelativeLine(UiLayoutEditPart parentPart, 608 RelativeInfo relInfo, 609 HighlightInfo highlightInfo) { 610 611 UiElementEditPart[] parts = relInfo.targetParts; 612 int dir = relInfo.direction; 613 int index = relInfo.anchorIndex; 614 UiElementEditPart part = parts[index]; 615 616 if (part == null) { 617 dir = sOppositeDirection[dir]; 618 part = parts[1 - index]; 619 } 620 if (part == null) { 621 // give up if both parts are null 622 return; 623 } 624 625 Rectangle r = part.getBounds(); 626 Point p = null; 627 switch(dir) { 628 case TOP: 629 p = r.getTop(); 630 break; 631 case BOTTOM: 632 p = r.getBottom(); 633 break; 634 case LEFT: 635 p = r.getLeft(); 636 break; 637 case RIGHT: 638 p = r.getRight(); 639 break; 640 } 641 642 highlightInfo.anchorPoint = p; 643 644 r = parentPart.getBounds(); 645 switch(dir) { 646 case TOP: 647 case BOTTOM: 648 // horizontal line with middle anchor point 649 highlightInfo.tempPoints[0].setLocation(0, p.y); 650 highlightInfo.tempPoints[1].setLocation(r.width, p.y); 651 highlightInfo.linePoints = highlightInfo.tempPoints; 652 highlightInfo.anchorPoint = p; 653 break; 654 case LEFT: 655 case RIGHT: 656 // vertical line with middle anchor point 657 highlightInfo.tempPoints[0].setLocation(p.x, 0); 658 highlightInfo.tempPoints[1].setLocation(p.x, r.height); 659 highlightInfo.linePoints = highlightInfo.tempPoints; 660 highlightInfo.anchorPoint = p; 661 break; 662 } 663 } 664 665 /** 666 * Given a certain reference point (drop point), computes the distance to the given 667 * part in the given direction. For example if direction is top, only accepts parts which 668 * bottom is above the reference point, computes their distance and then updates the 669 * current minimal distances and current closest parts arrays accordingly. 670 */ 671 private static void computeClosest(Point refPoint, 672 UiElementEditPart compareToPart, 673 UiElementEditPart[] currClosests, 674 int[] currMinDists, 675 int direction) { 676 Rectangle r = compareToPart.getBounds(); 677 678 Point p = null; 679 boolean usable = false; 680 681 switch(direction) { 682 case TOP: 683 p = r.getBottom(); 684 usable = p.y <= refPoint.y; 685 break; 686 case BOTTOM: 687 p = r.getTop(); 688 usable = p.y >= refPoint.y; 689 break; 690 case LEFT: 691 p = r.getRight(); 692 usable = p.x <= refPoint.x; 693 break; 694 case RIGHT: 695 p = r.getLeft(); 696 usable = p.x >= refPoint.x; 697 break; 698 } 699 700 if (usable) { 701 int d = p.getDistance2(refPoint); 702 if (d < currMinDists[direction]) { 703 currMinDists[direction] = d; 704 currClosests[direction] = compareToPart; 705 } 706 } 707 } 708 709 /** 710 * Given a reference parts, finds the closest part in the parent in the given direction. 711 * For example if direction is top, finds the closest sibling part which is above the 712 * reference part and non-overlapping (they can touch.) 713 */ 714 private static UiElementEditPart findClosestPart(UiElementEditPart referencePart, 715 int direction) { 716 if (referencePart == null || referencePart.getParent() == null) { 717 return null; 718 } 719 720 Rectangle r = referencePart.getBounds(); 721 Point ref = null; 722 switch(direction) { 723 case TOP: 724 ref = r.getTop(); 725 break; 726 case BOTTOM: 727 ref = r.getBottom(); 728 break; 729 case LEFT: 730 ref = r.getLeft(); 731 break; 732 case RIGHT: 733 ref = r.getRight(); 734 break; 735 } 736 737 int minDist = Integer.MAX_VALUE; 738 UiElementEditPart closestPart = null; 739 740 for (Object childPart : referencePart.getParent().getChildren()) { 741 if (childPart != referencePart && childPart instanceof UiElementEditPart) { 742 r = ((UiElementEditPart) childPart).getBounds(); 743 Point p = null; 744 boolean usable = false; 745 746 switch(direction) { 747 case TOP: 748 p = r.getBottom(); 749 usable = p.y <= ref.y; 750 break; 751 case BOTTOM: 752 p = r.getTop(); 753 usable = p.y >= ref.y; 754 break; 755 case LEFT: 756 p = r.getRight(); 757 usable = p.x <= ref.x; 758 break; 759 case RIGHT: 760 p = r.getLeft(); 761 usable = p.x >= ref.x; 762 break; 763 } 764 765 if (usable) { 766 int d = p.getDistance2(ref); 767 if (d < minDist) { 768 minDist = d; 769 closestPart = (UiElementEditPart) childPart; 770 } 771 } 772 } 773 } 774 775 return closestPart; 776 } 777 778 } 779