• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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