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