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