1 /* 2 * Copyright (C) 2010 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 17 package com.android.ide.common.layout; 18 19 import static com.android.SdkConstants.ANDROID_URI; 20 import static com.android.SdkConstants.ATTR_LAYOUT_X; 21 import static com.android.SdkConstants.ATTR_LAYOUT_Y; 22 import static com.android.SdkConstants.VALUE_N_DP; 23 24 import com.android.annotations.NonNull; 25 import com.android.annotations.Nullable; 26 import com.android.ide.common.api.DrawingStyle; 27 import com.android.ide.common.api.DropFeedback; 28 import com.android.ide.common.api.IDragElement; 29 import com.android.ide.common.api.IFeedbackPainter; 30 import com.android.ide.common.api.IGraphics; 31 import com.android.ide.common.api.INode; 32 import com.android.ide.common.api.INodeHandler; 33 import com.android.ide.common.api.IViewRule; 34 import com.android.ide.common.api.Point; 35 import com.android.ide.common.api.Rect; 36 import com.android.ide.common.api.SegmentType; 37 import com.android.utils.Pair; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Map; 42 43 /** 44 * An {@link IViewRule} for android.widget.AbsoluteLayout and all its derived 45 * classes. 46 */ 47 public class AbsoluteLayoutRule extends BaseLayoutRule { 48 49 @Override getSelectionHint(@onNull INode parentNode, @NonNull INode childNode)50 public List<String> getSelectionHint(@NonNull INode parentNode, @NonNull INode childNode) { 51 List<String> infos = new ArrayList<String>(2); 52 infos.add("AbsoluteLayout is deprecated."); 53 infos.add("Use other layouts instead."); 54 return infos; 55 } 56 57 // ==== Drag'n'drop support ==== 58 // The AbsoluteLayout accepts any drag'n'drop anywhere on its surface. 59 60 @Override onDropEnter(@onNull INode targetNode, @Nullable Object targetView, final @Nullable IDragElement[] elements)61 public DropFeedback onDropEnter(@NonNull INode targetNode, @Nullable Object targetView, 62 final @Nullable IDragElement[] elements) { 63 64 if (elements.length == 0) { 65 return null; 66 } 67 68 DropFeedback df = new DropFeedback(null, new IFeedbackPainter() { 69 @Override 70 public void paint(@NonNull IGraphics gc, @NonNull INode node, 71 @NonNull DropFeedback feedback) { 72 // Paint callback for the AbsoluteLayout. 73 // This is called by the canvas when a draw is needed. 74 drawFeedback(gc, node, elements, feedback); 75 } 76 }); 77 df.errorMessage = "AbsoluteLayout is deprecated."; 78 return df; 79 } 80 drawFeedback( IGraphics gc, INode targetNode, IDragElement[] elements, DropFeedback feedback)81 void drawFeedback( 82 IGraphics gc, 83 INode targetNode, 84 IDragElement[] elements, 85 DropFeedback feedback) { 86 Rect b = targetNode.getBounds(); 87 if (!b.isValid()) { 88 return; 89 } 90 91 // Highlight the receiver 92 gc.useStyle(DrawingStyle.DROP_RECIPIENT); 93 gc.drawRect(b); 94 95 // Get the drop point 96 Point p = (Point) feedback.userData; 97 98 if (p == null) { 99 return; 100 } 101 102 int x = p.x; 103 int y = p.y; 104 105 Rect be = elements[0].getBounds(); 106 107 if (be.isValid()) { 108 // At least the first element has a bound. Draw rectangles 109 // for all dropped elements with valid bounds, offset at 110 // the drop point. 111 int offsetX = x - be.x + (feedback.dragBounds != null ? feedback.dragBounds.x : 0); 112 int offsetY = y - be.y + (feedback.dragBounds != null ? feedback.dragBounds.y : 0); 113 gc.useStyle(DrawingStyle.DROP_PREVIEW); 114 for (IDragElement element : elements) { 115 drawElement(gc, element, offsetX, offsetY); 116 } 117 } else { 118 // We don't have bounds for new elements. In this case 119 // just draw cross hairs to the drop point. 120 gc.useStyle(DrawingStyle.GUIDELINE); 121 gc.drawLine(x, b.y, x, b.y + b.h); 122 gc.drawLine(b.x, y, b.x + b.w, y); 123 124 // Use preview lines to indicate the bottom quadrant as well (to 125 // indicate that you are looking at the top left position of the 126 // drop, not the center for example) 127 gc.useStyle(DrawingStyle.DROP_PREVIEW); 128 gc.drawLine(x, y, b.x + b.w, y); 129 gc.drawLine(x, y, x, b.y + b.h); 130 } 131 } 132 133 @Override onDropMove(@onNull INode targetNode, @NonNull IDragElement[] elements, @Nullable DropFeedback feedback, @NonNull Point p)134 public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements, 135 @Nullable DropFeedback feedback, @NonNull Point p) { 136 // Update the data used by the DropFeedback.paintCallback above. 137 feedback.userData = p; 138 feedback.requestPaint = true; 139 140 return feedback; 141 } 142 143 @Override onDropLeave(@onNull INode targetNode, @NonNull IDragElement[] elements, @Nullable DropFeedback feedback)144 public void onDropLeave(@NonNull INode targetNode, @NonNull IDragElement[] elements, 145 @Nullable DropFeedback feedback) { 146 // Nothing to do. 147 } 148 149 @Override onDropped(final @NonNull INode targetNode, final @NonNull IDragElement[] elements, final @Nullable DropFeedback feedback, final @NonNull Point p)150 public void onDropped(final @NonNull INode targetNode, final @NonNull IDragElement[] elements, 151 final @Nullable DropFeedback feedback, final @NonNull Point p) { 152 153 final Rect b = targetNode.getBounds(); 154 if (!b.isValid()) { 155 return; 156 } 157 158 // Collect IDs from dropped elements and remap them to new IDs 159 // if this is a copy or from a different canvas. 160 final Map<String, Pair<String, String>> idMap = getDropIdMap(targetNode, elements, 161 feedback.isCopy || !feedback.sameCanvas); 162 163 targetNode.editXml("Add elements to AbsoluteLayout", new INodeHandler() { 164 @Override 165 public void handle(@NonNull INode node) { 166 boolean first = true; 167 Point offset = null; 168 169 // Now write the new elements. 170 for (IDragElement element : elements) { 171 String fqcn = element.getFqcn(); 172 Rect be = element.getBounds(); 173 174 INode newChild = targetNode.appendChild(fqcn); 175 176 // Copy all the attributes, modifying them as needed. 177 addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER); 178 179 int deltaX = (feedback.dragBounds != null ? feedback.dragBounds.x : 0); 180 int deltaY = (feedback.dragBounds != null ? feedback.dragBounds.y : 0); 181 182 int x = p.x - b.x + deltaX; 183 int y = p.y - b.y + deltaY; 184 185 if (first) { 186 first = false; 187 if (be.isValid()) { 188 offset = new Point(x - be.x, y - be.y); 189 } 190 } else if (offset != null && be.isValid()) { 191 x = offset.x + be.x; 192 y = offset.y + be.y; 193 } else { 194 x += 10; 195 y += be.isValid() ? be.h : 10; 196 } 197 198 double scale = feedback.dipScale; 199 if (scale != 1.0) { 200 x *= scale; 201 y *= scale; 202 } 203 204 newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_X, 205 String.format(VALUE_N_DP, x)); 206 newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_Y, 207 String.format(VALUE_N_DP, y)); 208 209 addInnerElements(newChild, element, idMap); 210 } 211 } 212 }); 213 } 214 215 /** 216 * {@inheritDoc} 217 * <p> 218 * Overridden in this layout in order to let the top left coordinate be affected by 219 * the resize operation too. In other words, dragging the top left corner to resize a 220 * widget will not only change the size of the widget, it will also move it (though in 221 * this case, the bottom right corner will stay fixed). 222 */ 223 @Override setNewSizeBounds(ResizeState resizeState, INode node, INode layout, Rect previousBounds, Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge)224 protected void setNewSizeBounds(ResizeState resizeState, INode node, INode layout, 225 Rect previousBounds, Rect newBounds, SegmentType horizontalEdge, 226 SegmentType verticalEdge) { 227 super.setNewSizeBounds(resizeState, node, layout, previousBounds, newBounds, 228 horizontalEdge, verticalEdge); 229 if (verticalEdge != null && newBounds.x != previousBounds.x) { 230 node.setAttribute(ANDROID_URI, ATTR_LAYOUT_X, 231 String.format(VALUE_N_DP, 232 mRulesEngine.pxToDp(newBounds.x - node.getParent().getBounds().x))); 233 } 234 if (horizontalEdge != null && newBounds.y != previousBounds.y) { 235 node.setAttribute(ANDROID_URI, ATTR_LAYOUT_Y, 236 String.format(VALUE_N_DP, 237 mRulesEngine.pxToDp(newBounds.y - node.getParent().getBounds().y))); 238 } 239 } 240 241 @Override getResizeUpdateMessage(ResizeState resizeState, INode child, INode parent, Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge)242 protected String getResizeUpdateMessage(ResizeState resizeState, INode child, INode parent, 243 Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) { 244 Rect parentBounds = parent.getBounds(); 245 if (horizontalEdge == SegmentType.BOTTOM && verticalEdge == SegmentType.RIGHT) { 246 return super.getResizeUpdateMessage(resizeState, child, parent, newBounds, 247 horizontalEdge, verticalEdge); 248 } 249 return String.format("x=%d, y=%d\nwidth=%s, height=%s", 250 mRulesEngine.pxToDp(newBounds.x - parentBounds.x), 251 mRulesEngine.pxToDp(newBounds.y - parentBounds.y), 252 resizeState.getWidthAttribute(), resizeState.getHeightAttribute()); 253 } 254 } 255