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.adt.gscripts; 18 19 /** 20 * An {@link IViewRule} for android.widget.LinearLayout and all its derived classes. 21 */ 22 public class AndroidWidgetLinearLayoutRule extends BaseLayout { 23 24 public static String ATTR_ORIENTATION = "orientation"; 25 public static String VALUE_VERTICAL = "vertical"; 26 27 // ==== Drag'n'drop support ==== 28 29 DropFeedback onDropEnter(INode targetNode, IDragElement[] elements) { 30 31 if (elements.length == 0) { 32 return null; 33 } 34 35 def bn = targetNode.getBounds(); 36 if (!bn.isValid()) { 37 return; 38 } 39 40 boolean isVertical = 41 targetNode.getStringAttr(ANDROID_URI, ATTR_ORIENTATION) == VALUE_VERTICAL; 42 43 // Prepare a list of insertion points: X coords for horizontal, Y for vertical. 44 // Each list is a tuple: 0=pixel coordinate, 1=index of children or -1 for "at end". 45 def indexes = [ ]; 46 47 int last = isVertical ? bn.y : bn.x; 48 int pos = 0; 49 targetNode.getChildren().each { 50 def bc = it.getBounds(); 51 if (bc.isValid()) { 52 // add an insertion point between the last point and the start of this child 53 int v = isVertical ? bc.y : bc.x; 54 v = (last + v) / 2; 55 indexes.add( [v, pos++] ); 56 57 last = isVertical ? (bc.y + bc.h) : (bc.x + bc.w); 58 } 59 } 60 61 int v = isVertical ? (bn.y + bn.h) : (bn.x + bn.w); 62 v = (last + v) / 2; 63 indexes.add( [v, -1] ); 64 65 return new DropFeedback( 66 [ "isVertical": isVertical, // boolean: True if vertical linear layout 67 "indexes": indexes, // list(tuple(0:int, 1:int)): insert points (pixels + index) 68 "currX": null, // int: Current marker X position 69 "currY": null, // int: Current marker Y position 70 "insertPos": -1 // int: Current drop insert index (-1 for "at the end") 71 ], 72 { 73 gc, node, feedback -> 74 // Paint closure for the LinearLayout. 75 // This is called by the canvas when a draw is needed. 76 77 drawFeedback(gc, node, elements, feedback); 78 }); 79 } 80 81 void drawFeedback(IGraphics gc, 82 INode node, 83 IDragElement[] elements, 84 DropFeedback feedback) { 85 Rect b = node.getBounds(); 86 if (!b.isValid()) { 87 return; 88 } 89 90 // Highlight the receiver 91 gc.setForeground(gc.registerColor(0x00FFFF00)); 92 gc.setLineStyle(IGraphics.LineStyle.LINE_SOLID); 93 gc.setLineWidth(2); 94 gc.drawRect(b); 95 96 gc.setLineStyle(IGraphics.LineStyle.LINE_DOT); 97 gc.setLineWidth(1); 98 99 def indexes = feedback.userData.indexes; 100 boolean isVertical = feedback.userData.isVertical; 101 102 indexes.each { 103 int i = it[0]; 104 if (isVertical) { 105 // draw horizontal lines 106 gc.drawLine(b.x, i, b.x + b.w, i); 107 } else { 108 // draw vertical lines 109 gc.drawLine(i, b.y, i, b.y + b.h); 110 } 111 } 112 113 def currX = feedback.userData.currX; 114 def currY = feedback.userData.currY; 115 116 if (currX != null && currY != null) { 117 int x = currX; 118 int y = currY; 119 120 // Draw a mark at the drop point. 121 gc.setLineStyle(IGraphics.LineStyle.LINE_SOLID); 122 gc.setLineWidth(2); 123 124 gc.drawLine(x - 10, y - 10, x + 10, y + 10); 125 gc.drawLine(x + 10, y - 10, x - 10, y + 10); 126 gc.drawOval(x - 10, y - 10, x + 10, y + 10); 127 128 Rect be = elements[0].getBounds(); 129 130 if (be.isValid()) { 131 // At least the first element has a bound. Draw rectangles 132 // for all dropped elements with valid bounds, offset at 133 // the drop point. 134 135 int offsetX = x - be.x; 136 int offsetY = y - be.y; 137 138 // If there's a parent, keep the X/Y coordinate the same relative to the parent. 139 Rect pb = elements[0].getParentBounds(); 140 if (pb.isValid()) { 141 if (isVertical) { 142 offsetX = b.x - pb.x; 143 } else { 144 offsetY = b.y - pb.y; 145 } 146 } 147 148 for (element in elements) { 149 drawElement(gc, element, offsetX, offsetY); 150 } 151 } 152 } 153 } 154 155 DropFeedback onDropMove(INode targetNode, 156 IDragElement[] elements, 157 DropFeedback feedback, 158 Point p) { 159 def data = feedback.userData; 160 161 Rect b = targetNode.getBounds(); 162 if (!b.isValid()) { 163 return feedback; 164 } 165 166 boolean isVertical = data.isVertical; 167 168 int bestDist = Integer.MAX_VALUE; 169 int bestIndex = Integer.MIN_VALUE; 170 int bestPos = null; 171 172 for(index in data.indexes) { 173 int i = index[0]; 174 int pos = index[1]; 175 int dist = (isVertical ? p.y : p.x) - i; 176 if (dist < 0) dist = - dist; 177 if (dist < bestDist) { 178 bestDist = dist; 179 bestIndex = i; 180 bestPos = pos; 181 if (bestDist <= 0) break; 182 } 183 } 184 185 if (bestIndex != Integer.MIN_VALUE) { 186 def old_x = data.currX; 187 def old_y = data.currY; 188 189 if (isVertical) { 190 data.currX = b.x + b.w / 2; 191 data.currY = bestIndex; 192 } else { 193 data.currX = bestIndex; 194 data.currY = b.y + b.h / 2; 195 } 196 197 data.insertPos = bestPos; 198 199 feedback.requestPaint = (old_x != data.currX) || (old_y != data.currY); 200 } 201 202 return feedback; 203 } 204 205 void onDropLeave(INode targetNode, IDragElement[] elements, DropFeedback feedback) { 206 // ignore 207 } 208 209 void onDropped(INode targetNode, 210 IDragElement[] elements, 211 DropFeedback feedback, 212 Point p) { 213 214 int insertPos = feedback.userData.insertPos; 215 216 // Collect IDs from dropped elements and remap them to new IDs 217 // if this is a copy or from a different canvas. 218 def idMap = getDropIdMap(targetNode, elements, feedback.isCopy || !feedback.sameCanvas); 219 220 targetNode.editXml("Add elements to LinearLayout") { 221 222 // Now write the new elements. 223 for (element in elements) { 224 String fqcn = element.getFqcn(); 225 Rect be = element.getBounds(); 226 227 INode newChild = targetNode.insertChildAt(fqcn, insertPos); 228 229 // insertPos==-1 means to insert at the end. Otherwise 230 // increment the insertion position. 231 if (insertPos >= 0) { 232 insertPos++; 233 } 234 235 // Copy all the attributes, modifying them as needed. 236 def attrFilter = getLayoutAttrFilter(); 237 addAttributes(newChild, element, idMap) { 238 uri, name, value -> 239 // TODO need a better way to exclude other layout attributes dynamically 240 if (uri == ANDROID_URI && name in attrFilter) { 241 return false; // don't set these attributes 242 } else { 243 return value; 244 } 245 }; 246 247 addInnerElements(newChild, element, idMap); 248 } 249 } 250 251 252 } 253 } 254