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