• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MoveHandler(INode layout, IDragElement[] elements, IClientRulesEngine rulesEngine)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
snapVertical(Segment vEdge, int x, Rect newBounds)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
snapHorizontal(Segment hEdge, int y, Rect newBounds)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 
updateMove(DropFeedback feedback, IDragElement[] elements, int offsetX, int offsetY, int modifierMask)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 }