Home | History | Annotate | Download | only in relative
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.ide.common.layout.relative;
     17 
     18 import static com.android.ide.common.api.MarginType.NO_MARGIN;
     19 import static com.android.ide.common.api.SegmentType.BASELINE;
     20 import static com.android.ide.common.api.SegmentType.BOTTOM;
     21 import static com.android.ide.common.api.SegmentType.CENTER_HORIZONTAL;
     22 import static com.android.ide.common.api.SegmentType.CENTER_VERTICAL;
     23 import static com.android.ide.common.api.SegmentType.LEFT;
     24 import static com.android.ide.common.api.SegmentType.RIGHT;
     25 import static com.android.ide.common.api.SegmentType.TOP;
     26 import static com.android.SdkConstants.ATTR_ID;
     27 
     28 import static java.lang.Math.abs;
     29 
     30 import com.android.SdkConstants;
     31 import static com.android.SdkConstants.ANDROID_URI;
     32 import com.android.ide.common.api.DropFeedback;
     33 import com.android.ide.common.api.IClientRulesEngine;
     34 import com.android.ide.common.api.IDragElement;
     35 import com.android.ide.common.api.INode;
     36 import com.android.ide.common.api.Rect;
     37 import com.android.ide.common.api.Segment;
     38 import com.android.ide.common.layout.BaseLayoutRule;
     39 import com.android.ide.common.layout.relative.DependencyGraph.ViewData;
     40 
     41 import java.util.ArrayList;
     42 import java.util.List;
     43 
     44 /**
     45  * A {@link MoveHandler} is a {@link GuidelineHandler} which handles move and drop
     46  * gestures, and offers guideline suggestions and snapping.
     47  * <p>
     48  * Unlike the {@link ResizeHandler}, the {@link MoveHandler} looks for matches for all
     49  * different segment types -- the left edge, the right edge, the baseline, the center
     50  * edges, and so on -- and picks the best among these.
     51  */
     52 public class MoveHandler extends GuidelineHandler {
     53     private int mDraggedBaseline;
     54 
     55     /**
     56      * Creates a new {@link MoveHandler}.
     57      *
     58      * @param layout the layout element the handler is operating on
     59      * @param elements the elements being dragged in the move operation
     60      * @param rulesEngine the corresponding {@link IClientRulesEngine}
     61      */
     62     public MoveHandler(INode layout, IDragElement[] elements, IClientRulesEngine rulesEngine) {
     63         super(layout, rulesEngine);
     64 
     65         // Compute list of nodes being dragged within the layout, if any
     66         List<INode> nodes = new ArrayList<INode>();
     67         for (IDragElement element : elements) {
     68             ViewData view = mDependencyGraph.getView(element);
     69             if (view != null) {
     70                 nodes.add(view.node);
     71             }
     72         }
     73         mDraggedNodes = nodes;
     74 
     75         mHorizontalDeps = mDependencyGraph.dependsOn(nodes, false /* verticalEdge */);
     76         mVerticalDeps = mDependencyGraph.dependsOn(nodes, true /* verticalEdge */);
     77 
     78         for (INode child : layout.getChildren()) {
     79             Rect bc = child.getBounds();
     80             if (bc.isValid()) {
     81                 // First see if this node looks like it's the same as one of the
     82                 // *dragged* bounds
     83                 boolean isDragged = false;
     84                 for (IDragElement element : elements) {
     85                     // This tries to determine if an INode corresponds to an
     86                     // IDragElement, by comparing their bounds.
     87                     if (bc.equals(element.getBounds())) {
     88                         isDragged = true;
     89                     }
     90                 }
     91 
     92                 if (!isDragged) {
     93                     String id = child.getStringAttr(ANDROID_URI, ATTR_ID);
     94                     // It's okay for id to be null; if you apply a constraint
     95                     // to a node with a missing id we will generate the id
     96 
     97                     boolean addHorizontal = !mHorizontalDeps.contains(child);
     98                     boolean addVertical = !mVerticalDeps.contains(child);
     99 
    100                     addBounds(child, id, addHorizontal, addVertical);
    101                     if (addHorizontal) {
    102                         addBaseLine(child, id);
    103                     }
    104                 }
    105             }
    106         }
    107 
    108         String id = layout.getStringAttr(ANDROID_URI, ATTR_ID);
    109         addBounds(layout, id, true, true);
    110         addCenter(layout, id, true, true);
    111     }
    112 
    113     @Override
    114     protected void snapVertical(Segment vEdge, int x, Rect newBounds) {
    115         int maxDistance = BaseLayoutRule.getMaxMatchDistance();
    116         if (vEdge.edgeType == LEFT) {
    117             int margin = !mSnap ? 0 : abs(newBounds.x - x);
    118             if (margin > maxDistance) {
    119                 mLeftMargin = margin;
    120             } else {
    121                 newBounds.x = x;
    122             }
    123         } else if (vEdge.edgeType == RIGHT) {
    124             int margin = !mSnap ? 0 : abs(newBounds.x - (x - newBounds.w));
    125             if (margin > maxDistance) {
    126                 mRightMargin = margin;
    127             } else {
    128                 newBounds.x = x - newBounds.w;
    129             }
    130         } else if (vEdge.edgeType == CENTER_VERTICAL) {
    131             newBounds.x = x - newBounds.w / 2;
    132         } else {
    133             assert false : vEdge;
    134         }
    135     }
    136 
    137     // TODO: Consider unifying this with the snapping logic in ResizeHandler
    138     @Override
    139     protected void snapHorizontal(Segment hEdge, int y, Rect newBounds) {
    140         int maxDistance = BaseLayoutRule.getMaxMatchDistance();
    141         if (hEdge.edgeType == TOP) {
    142             int margin = !mSnap ? 0 : abs(newBounds.y - y);
    143             if (margin > maxDistance) {
    144                 mTopMargin = margin;
    145             } else {
    146                 newBounds.y = y;
    147             }
    148         } else if (hEdge.edgeType == BOTTOM) {
    149             int margin = !mSnap ? 0 : abs(newBounds.y - (y - newBounds.h));
    150             if (margin > maxDistance) {
    151                 mBottomMargin = margin;
    152             } else {
    153                 newBounds.y = y - newBounds.h;
    154             }
    155         } else if (hEdge.edgeType == CENTER_HORIZONTAL) {
    156             int margin = !mSnap ? 0 : abs(newBounds.y - (y - newBounds.h / 2));
    157             if (margin > maxDistance) {
    158                 mTopMargin = margin;
    159                 // or bottomMargin?
    160             } else {
    161                 newBounds.y = y - newBounds.h / 2;
    162             }
    163         } else if (hEdge.edgeType == BASELINE) {
    164                 newBounds.y = y - mDraggedBaseline;
    165         } else {
    166             assert false : hEdge;
    167         }
    168     }
    169 
    170     /**
    171      * Updates the handler for the given mouse move
    172      *
    173      * @param feedback the feedback handler
    174      * @param elements the elements being dragged
    175      * @param offsetX the new mouse X coordinate
    176      * @param offsetY the new mouse Y coordinate
    177      * @param modifierMask the keyboard modifiers pressed during the drag
    178      */
    179     public void updateMove(DropFeedback feedback, IDragElement[] elements,
    180             int offsetX, int offsetY, int modifierMask) {
    181         mSnap = (modifierMask & DropFeedback.MODIFIER2) == 0;
    182 
    183         Rect firstBounds = elements[0].getBounds();
    184         INode firstNode = null;
    185         if (mDraggedNodes != null && mDraggedNodes.size() > 0) {
    186             // TODO - this isn't quite right; this could be a different node than we have
    187             // bounds for!
    188             firstNode = mDraggedNodes.iterator().next();
    189             firstBounds = firstNode.getBounds();
    190         }
    191 
    192         mBounds = new Rect(offsetX, offsetY, firstBounds.w, firstBounds.h);
    193         Rect layoutBounds = layout.getBounds();
    194         if (mBounds.x2() > layoutBounds.x2()) {
    195             mBounds.x -= mBounds.x2() - layoutBounds.x2();
    196         }
    197         if (mBounds.y2() > layoutBounds.y2()) {
    198             mBounds.y -= mBounds.y2() - layoutBounds.y2();
    199         }
    200         if (mBounds.x < layoutBounds.x) {
    201             mBounds.x = layoutBounds.x;
    202         }
    203         if (mBounds.y < layoutBounds.y) {
    204             mBounds.y = layoutBounds.y;
    205         }
    206 
    207         clearSuggestions();
    208 
    209         Rect b = mBounds;
    210         Segment edge = new Segment(b.y, b.x, b.x2(), null, null, TOP, NO_MARGIN);
    211         List<Match> horizontalMatches = findClosest(edge, mHorizontalEdges);
    212         edge = new Segment(b.y2(), b.x, b.x2(), null, null, BOTTOM, NO_MARGIN);
    213         addClosest(edge, mHorizontalEdges, horizontalMatches);
    214 
    215         edge = new Segment(b.x, b.y, b.y2(), null, null, LEFT, NO_MARGIN);
    216         List<Match> verticalMatches = findClosest(edge, mVerticalEdges);
    217         edge = new Segment(b.x2(), b.y, b.y2(), null, null, RIGHT, NO_MARGIN);
    218         addClosest(edge, mVerticalEdges, verticalMatches);
    219 
    220         // Match center
    221         edge = new Segment(b.centerX(), b.y, b.y2(), null, null, CENTER_VERTICAL, NO_MARGIN);
    222         addClosest(edge, mCenterVertEdges, verticalMatches);
    223         edge = new Segment(b.centerY(), b.x, b.x2(), null, null, CENTER_HORIZONTAL, NO_MARGIN);
    224         addClosest(edge, mCenterHorizEdges, horizontalMatches);
    225 
    226         // Match baseline
    227         if (firstNode != null) {
    228             int baseline = firstNode.getBaseline();
    229             if (baseline != -1) {
    230                 mDraggedBaseline = baseline;
    231                 edge = new Segment(b.y + baseline, b.x, b.x2(), firstNode, null, BASELINE,
    232                         NO_MARGIN);
    233                 addClosest(edge, mHorizontalEdges, horizontalMatches);
    234             }
    235         } else {
    236             int baseline = feedback.dragBaseline;
    237             if (baseline != -1) {
    238                 mDraggedBaseline = baseline;
    239                 edge = new Segment(offsetY + baseline, b.x, b.x2(), null, null, BASELINE,
    240                         NO_MARGIN);
    241                 addClosest(edge, mHorizontalEdges, horizontalMatches);
    242             }
    243         }
    244 
    245         mHorizontalSuggestions = horizontalMatches;
    246         mVerticalSuggestions = verticalMatches;
    247         mTopMargin = mBottomMargin = mLeftMargin = mRightMargin = 0;
    248 
    249         Match match = pickBestMatch(mHorizontalSuggestions);
    250         if (match != null) {
    251             if (mHorizontalDeps.contains(match.edge.node)) {
    252                 match.cycle = true;
    253             }
    254 
    255             // Reset top AND bottom bounds regardless of whether both are bound
    256             mMoveTop = true;
    257             mMoveBottom = true;
    258 
    259             // TODO: Consider doing the snap logic on all the possible matches
    260             // BEFORE sorting, in case this affects the best-pick algorithm (since some
    261             // edges snap and others don't).
    262             snapHorizontal(match.with, match.edge.at, mBounds);
    263 
    264             if (match.with.edgeType == TOP) {
    265                 mCurrentTopMatch = match;
    266             } else if (match.with.edgeType == BOTTOM) {
    267                 mCurrentBottomMatch = match;
    268             } else {
    269                 assert match.with.edgeType == CENTER_HORIZONTAL
    270                         || match.with.edgeType == BASELINE : match.with.edgeType;
    271                 mCurrentTopMatch = match;
    272             }
    273         }
    274 
    275         match = pickBestMatch(mVerticalSuggestions);
    276         if (match != null) {
    277             if (mVerticalDeps.contains(match.edge.node)) {
    278                 match.cycle = true;
    279             }
    280 
    281             // Reset left AND right bounds regardless of whether both are bound
    282             mMoveLeft = true;
    283             mMoveRight = true;
    284 
    285             snapVertical(match.with, match.edge.at, mBounds);
    286 
    287             if (match.with.edgeType == LEFT) {
    288                 mCurrentLeftMatch = match;
    289             } else if (match.with.edgeType == RIGHT) {
    290                 mCurrentRightMatch = match;
    291             } else {
    292                 assert match.with.edgeType == CENTER_VERTICAL;
    293                 mCurrentLeftMatch = match;
    294             }
    295         }
    296 
    297         checkCycles(feedback);
    298     }
    299 }
    300