• 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.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.INode;
35 import com.android.ide.common.api.Rect;
36 import com.android.ide.common.api.Segment;
37 import com.android.ide.common.api.SegmentType;
38 import com.android.ide.common.layout.BaseLayoutRule;
39 
40 import java.util.Collections;
41 import java.util.Set;
42 
43 /**
44  * A {@link ResizeHandler} is a {@link GuidelineHandler} which handles resizing of individual
45  * edges in a RelativeLayout.
46  */
47 public class ResizeHandler extends GuidelineHandler {
48     private final SegmentType mHorizontalEdgeType;
49     private final SegmentType mVerticalEdgeType;
50 
51     /**
52      * Creates a new {@link ResizeHandler}
53      *
54      * @param layout the layout containing the resized node
55      * @param resized the node being resized
56      * @param rulesEngine the applicable {@link IClientRulesEngine}
57      * @param horizontalEdgeType the type of horizontal edge being resized, or null
58      * @param verticalEdgeType the type of vertical edge being resized, or null
59      */
ResizeHandler(INode layout, INode resized, IClientRulesEngine rulesEngine, SegmentType horizontalEdgeType, SegmentType verticalEdgeType)60     public ResizeHandler(INode layout, INode resized,
61             IClientRulesEngine rulesEngine,
62             SegmentType horizontalEdgeType, SegmentType verticalEdgeType) {
63         super(layout, rulesEngine);
64 
65         assert horizontalEdgeType != null || verticalEdgeType != null;
66         assert horizontalEdgeType != BASELINE && verticalEdgeType != BASELINE;
67         assert horizontalEdgeType != CENTER_HORIZONTAL && verticalEdgeType != CENTER_HORIZONTAL;
68         assert horizontalEdgeType != CENTER_VERTICAL && verticalEdgeType != CENTER_VERTICAL;
69 
70         mHorizontalEdgeType = horizontalEdgeType;
71         mVerticalEdgeType = verticalEdgeType;
72 
73         Set<INode> nodes = Collections.singleton(resized);
74         mDraggedNodes = nodes;
75 
76         mHorizontalDeps = mDependencyGraph.dependsOn(nodes, false /* vertical */);
77         mVerticalDeps = mDependencyGraph.dependsOn(nodes, true /* vertical */);
78 
79         if (horizontalEdgeType != null) {
80             if (horizontalEdgeType == TOP) {
81                 mMoveTop = true;
82             } else if (horizontalEdgeType == BOTTOM) {
83                 mMoveBottom = true;
84             }
85         }
86         if (verticalEdgeType != null) {
87             if (verticalEdgeType == LEFT) {
88                 mMoveLeft = true;
89             } else if (verticalEdgeType == RIGHT) {
90                 mMoveRight = true;
91             }
92         }
93 
94         for (INode child : layout.getChildren()) {
95             if (child != resized) {
96                 String id = child.getStringAttr(ANDROID_URI, ATTR_ID);
97                 addBounds(child, id,
98                         !mHorizontalDeps.contains(child),
99                         !mVerticalDeps.contains(child));
100             }
101         }
102 
103         addBounds(layout, layout.getStringAttr(ANDROID_URI, ATTR_ID), true, true);
104     }
105 
106     @Override
snapVertical(Segment vEdge, int x, Rect newBounds)107     protected void snapVertical(Segment vEdge, int x, Rect newBounds) {
108         int maxDistance = BaseLayoutRule.getMaxMatchDistance();
109         if (vEdge.edgeType == LEFT) {
110             int margin = mSnap ? 0 : abs(newBounds.x - x);
111             if (margin > maxDistance) {
112                 mLeftMargin = margin;
113             } else {
114                 newBounds.w += newBounds.x - x;
115                 newBounds.x = x;
116             }
117         } else if (vEdge.edgeType == RIGHT) {
118             int margin = mSnap ? 0 : abs(newBounds.x - (x - newBounds.w));
119             if (margin > maxDistance) {
120                 mRightMargin = margin;
121             } else {
122                 newBounds.w = x - newBounds.x;
123             }
124         } else {
125             assert false : vEdge;
126         }
127     }
128 
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.h += newBounds.y - y;
138                 newBounds.y = y;
139             }
140         } else if (hEdge.edgeType == BOTTOM) {
141             int margin = mSnap ? 0 : abs(newBounds.y - (y - newBounds.h));
142             if (margin > maxDistance) {
143                 mBottomMargin = margin;
144             } else {
145                 newBounds.h = y - newBounds.y;
146             }
147         } else {
148             assert false : hEdge;
149         }
150     }
151 
152     @Override
isEdgeTypeCompatible(SegmentType edge, SegmentType dragged, int delta)153     protected boolean isEdgeTypeCompatible(SegmentType edge, SegmentType dragged, int delta) {
154         boolean compatible = super.isEdgeTypeCompatible(edge, dragged, delta);
155 
156         // When resizing and not snapping (e.g. using margins to pick a specific pixel
157         // width) we cannot use -negative- margins to jump back to a closer edge; we
158         // must always use positive margins, so mark closer edges that result in a negative
159         // margin as not compatible.
160         if (compatible && !mSnap) {
161             switch (dragged) {
162                 case LEFT:
163                 case TOP:
164                     return delta <= 0;
165                 default:
166                     return delta >= 0;
167             }
168         }
169 
170         return compatible;
171     }
172 
173     /**
174      * Updates the handler for the given mouse resize
175      *
176      * @param feedback the feedback handler
177      * @param child the node being resized
178      * @param newBounds the new bounds of the resize rectangle
179      * @param modifierMask the keyboard modifiers pressed during the drag
180      */
updateResize(DropFeedback feedback, INode child, Rect newBounds, int modifierMask)181     public void updateResize(DropFeedback feedback, INode child, Rect newBounds,
182             int modifierMask) {
183         mSnap = (modifierMask & DropFeedback.MODIFIER2) == 0;
184         mBounds = newBounds;
185         clearSuggestions();
186 
187         Rect b = newBounds;
188         Segment hEdge = null;
189         Segment vEdge = null;
190         String childId = child.getStringAttr(ANDROID_URI, ATTR_ID);
191 
192         // TODO: MarginType=NO_MARGIN may not be right. Consider resizing a widget
193         //   that has margins and how that should be handled.
194 
195         if (mHorizontalEdgeType == TOP) {
196             hEdge = new Segment(b.y, b.x, b.x2(), child, childId, mHorizontalEdgeType, NO_MARGIN);
197         } else if (mHorizontalEdgeType == BOTTOM) {
198             hEdge = new Segment(b.y2(), b.x, b.x2(), child, childId, mHorizontalEdgeType,
199                     NO_MARGIN);
200         } else {
201             assert mHorizontalEdgeType == null;
202         }
203 
204         if (mVerticalEdgeType == LEFT) {
205             vEdge = new Segment(b.x, b.y, b.y2(), child, childId, mVerticalEdgeType, NO_MARGIN);
206         } else if (mVerticalEdgeType == RIGHT) {
207             vEdge = new Segment(b.x2(), b.y, b.y2(), child, childId, mVerticalEdgeType, NO_MARGIN);
208         } else {
209             assert mVerticalEdgeType == null;
210         }
211 
212         mTopMargin = mBottomMargin = mLeftMargin = mRightMargin = 0;
213 
214         if (hEdge != null && mHorizontalEdges.size() > 0) {
215             // Compute horizontal matches
216             mHorizontalSuggestions = findClosest(hEdge, mHorizontalEdges);
217 
218             Match match = pickBestMatch(mHorizontalSuggestions);
219             if (match != null
220                     && (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) {
221                 if (mHorizontalDeps.contains(match.edge.node)) {
222                     match.cycle = true;
223                 }
224 
225                 snapHorizontal(hEdge, match.edge.at, newBounds);
226 
227                 if (hEdge.edgeType == TOP) {
228                     mCurrentTopMatch = match;
229                 } else if (hEdge.edgeType == BOTTOM) {
230                     mCurrentBottomMatch = match;
231                 } else {
232                     assert hEdge.edgeType == CENTER_HORIZONTAL
233                             || hEdge.edgeType == BASELINE : hEdge;
234                     mCurrentTopMatch = match;
235                 }
236             }
237         }
238 
239         if (vEdge != null && mVerticalEdges.size() > 0) {
240             mVerticalSuggestions = findClosest(vEdge, mVerticalEdges);
241 
242             Match match = pickBestMatch(mVerticalSuggestions);
243             if (match != null
244                     && (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) {
245                 if (mVerticalDeps.contains(match.edge.node)) {
246                     match.cycle = true;
247                 }
248 
249                 // Snap
250                 snapVertical(vEdge, match.edge.at, newBounds);
251 
252                 if (vEdge.edgeType == LEFT) {
253                     mCurrentLeftMatch = match;
254                 } else if (vEdge.edgeType == RIGHT) {
255                     mCurrentRightMatch = match;
256                 } else {
257                     assert vEdge.edgeType == CENTER_VERTICAL;
258                     mCurrentLeftMatch = match;
259                 }
260             }
261         }
262 
263         checkCycles(feedback);
264     }
265 }
266