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_GRAVITY;
21 import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
22 import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
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.IViewMetadata;
34 import com.android.ide.common.api.IViewMetadata.FillPreference;
35 import com.android.ide.common.api.IViewRule;
36 import com.android.ide.common.api.InsertType;
37 import com.android.ide.common.api.Point;
38 import com.android.ide.common.api.Rect;
39 import com.android.ide.common.api.RuleAction;
40 import com.android.utils.Pair;
41 
42 import java.util.List;
43 import java.util.Map;
44 
45 /**
46  * An {@link IViewRule} for android.widget.FrameLayout and all its derived
47  * classes.
48  */
49 public class FrameLayoutRule extends BaseLayoutRule {
50 
51     // ==== Drag'n'drop support ====
52     // The FrameLayout accepts any drag'n'drop anywhere on its surface.
53 
54     @Override
onDropEnter(@onNull INode targetNode, @Nullable Object targetView, final @Nullable IDragElement[] elements)55     public DropFeedback onDropEnter(@NonNull INode targetNode, @Nullable Object targetView,
56             final @Nullable IDragElement[] elements) {
57         if (elements.length == 0) {
58             return null;
59         }
60 
61         return new DropFeedback(null, new IFeedbackPainter() {
62             @Override
63             public void paint(@NonNull IGraphics gc, @NonNull INode node,
64                     @NonNull DropFeedback feedback) {
65                 drawFeedback(gc, node, elements, feedback);
66             }
67         });
68     }
69 
70     protected void drawFeedback(
71             IGraphics gc,
72             INode targetNode,
73             IDragElement[] elements,
74             DropFeedback feedback) {
75         Rect b = targetNode.getBounds();
76         if (!b.isValid()) {
77             return;
78         }
79 
80         gc.useStyle(DrawingStyle.DROP_RECIPIENT);
81         gc.drawRect(b);
82 
83         // Get the drop point
84         Point p = (Point) feedback.userData;
85 
86         if (p == null) {
87             return;
88         }
89 
90         Rect be = elements[0].getBounds();
91 
92         gc.useStyle(DrawingStyle.DROP_PREVIEW);
93         if (be.isValid()) {
94             // At least the first element has a bound. Draw rectangles
95             // for all dropped elements with valid bounds, offset at
96             // (0,0)
97             for (IDragElement it : elements) {
98                 Rect currBounds = it.getBounds();
99                 if (currBounds.isValid()) {
100                     int offsetX = b.x - currBounds.x;
101                     int offsetY = b.y - currBounds.y;
102                     drawElement(gc, it, offsetX, offsetY);
103                 }
104             }
105         } else {
106             // We don't have bounds for new elements. In this case
107             // just draw insert lines indicating the top left corner where
108             // the item will be placed
109 
110             // +1: Place lines fully within the view (the stroke width is 2) to
111             // make
112             // it even more visually obvious
113             gc.drawLine(b.x + 1, b.y, b.x + 1, b.y + b.h);
114             gc.drawLine(b.x, b.y + 1, b.x + b.w, b.y + 1);
115         }
116     }
117 
118     @Override
119     public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements,
120             @Nullable DropFeedback feedback, @NonNull Point p) {
121         feedback.userData = p;
122         feedback.requestPaint = true;
123         return feedback;
124     }
125 
126     @Override
127     public void onDropLeave(@NonNull INode targetNode, @NonNull IDragElement[] elements,
128             @Nullable DropFeedback feedback) {
129         // ignore
130     }
131 
132     @Override
133     public void onDropped(final @NonNull INode targetNode, final @NonNull IDragElement[] elements,
134             final @Nullable DropFeedback feedback, final @NonNull Point p) {
135         Rect b = targetNode.getBounds();
136         if (!b.isValid()) {
137             return;
138         }
139 
140         // Collect IDs from dropped elements and remap them to new IDs
141         // if this is a copy or from a different canvas.
142         final Map<String, Pair<String, String>> idMap = getDropIdMap(targetNode, elements,
143                 feedback.isCopy || !feedback.sameCanvas);
144 
145         targetNode.editXml("Add elements to FrameLayout", new INodeHandler() {
146 
147             @Override
148             public void handle(@NonNull INode node) {
149 
150                 // Now write the new elements.
151                 for (IDragElement element : elements) {
152                     String fqcn = element.getFqcn();
153 
154                     INode newChild = targetNode.appendChild(fqcn);
155 
156                     // Copy all the attributes, modifying them as needed.
157                     addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER);
158 
159                     addInnerElements(newChild, element, idMap);
160                 }
161             }
162         });
163     }
164 
165     @Override
166     public void addLayoutActions(
167             @NonNull List<RuleAction> actions,
168             final @NonNull INode parentNode,
169             final @NonNull List<? extends INode> children) {
170         super.addLayoutActions(actions, parentNode, children);
171         actions.add(RuleAction.createSeparator(25));
172         actions.add(createMarginAction(parentNode, children));
173         if (children != null && children.size() > 0) {
174             actions.add(createGravityAction(children, ATTR_LAYOUT_GRAVITY));
175         }
176     }
177 
178     @Override
179     public void onChildInserted(@NonNull INode node, @NonNull INode parent,
180             @NonNull InsertType insertType) {
181         // Look at the fill preferences and fill embedded layouts etc
182         String fqcn = node.getFqcn();
183         IViewMetadata metadata = mRulesEngine.getMetadata(fqcn);
184         if (metadata != null) {
185             FillPreference fill = metadata.getFillPreference();
186             String fillParent = getFillParentValueName();
187             if (fill.fillHorizontally(true)) {
188                 node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent);
189             }
190             if (fill.fillVertically(false)) {
191                 node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, fillParent);
192             }
193         }
194     }
195 }
196