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