Home | History | Annotate | Download | only in parts
      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