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 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 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 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 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 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 }