1 /* 2 * Copyright (C) 2011 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 package com.android.ide.common.layout.grid; 17 18 import static com.android.ide.common.layout.GridLayoutRule.GRID_SIZE; 19 import static com.android.ide.common.layout.GridLayoutRule.MARGIN_SIZE; 20 import static com.android.ide.common.layout.grid.GridModel.UNDEFINED; 21 22 import com.android.ide.common.api.DrawingStyle; 23 import com.android.ide.common.api.DropFeedback; 24 import com.android.ide.common.api.IDragElement; 25 import com.android.ide.common.api.IFeedbackPainter; 26 import com.android.ide.common.api.IGraphics; 27 import com.android.ide.common.api.INode; 28 import com.android.ide.common.api.Rect; 29 import com.android.ide.common.api.SegmentType; 30 import com.android.ide.common.layout.GridLayoutRule; 31 import com.android.util.Pair; 32 33 /** 34 * Painter which paints feedback during drag, drop and resizing operations, as well as 35 * static selection feedback 36 */ 37 public class GridLayoutPainter { 38 39 /** 40 * Creates a painter for drop feedback 41 * 42 * @param rule the corresponding {@link GridLayoutRule} 43 * @param elements the dragged elements 44 * @return a {@link IFeedbackPainter} which can paint the drop feedback 45 */ 46 public static IFeedbackPainter createDropFeedbackPainter(GridLayoutRule rule, 47 IDragElement[] elements) { 48 return new DropFeedbackPainter(rule, elements); 49 } 50 51 /** 52 * Paints the structure (the grid model) of the given GridLayout. 53 * 54 * @param style the drawing style to use to paint the structure lines 55 * @param layout the grid layout node 56 * @param gc the graphics context to paint into 57 * @param grid the grid model to be visualized 58 */ 59 public static void paintStructure(DrawingStyle style, INode layout, IGraphics gc, 60 GridModel grid) { 61 Rect b = layout.getBounds(); 62 63 gc.useStyle(style); 64 for (int row = 0; row < grid.actualRowCount; row++) { 65 int y = grid.getRowY(row); 66 gc.drawLine(b.x, y, b.x2(), y); 67 } 68 for (int column = 0; column < grid.actualColumnCount; column++) { 69 int x = grid.getColumnX(column); 70 gc.drawLine(x, b.y, x, b.y2()); 71 } 72 } 73 74 /** 75 * Paints a regular grid according to the {@link GridLayoutRule#GRID_SIZE} and 76 * {@link GridLayoutRule#MARGIN_SIZE} dimensions. These are the same lines that 77 * snap-to-grid will align with. 78 * 79 * @param layout the GridLayout node 80 * @param gc the graphics context to paint the grid into 81 */ 82 public static void paintGrid(INode layout, IGraphics gc) { 83 Rect b = layout.getBounds(); 84 85 int oldAlpha = gc.getAlpha(); 86 gc.useStyle(DrawingStyle.GUIDELINE); 87 gc.setAlpha(128); 88 89 int y1 = b.y + MARGIN_SIZE; 90 int y2 = b.y2() - MARGIN_SIZE; 91 for (int y = y1; y < y2; y += GRID_SIZE) { 92 int x1 = b.x + MARGIN_SIZE; 93 int x2 = b.x2() - MARGIN_SIZE; 94 for (int x = x1; x < x2; x += GRID_SIZE) { 95 gc.drawPoint(x, y); 96 } 97 } 98 gc.setAlpha(oldAlpha); 99 } 100 101 /** 102 * Paint resizing feedback (which currently paints the grid model faintly.) 103 * 104 * @param gc the graphics context 105 * @param layout the GridLayout 106 * @param grid the grid model 107 */ 108 public static void paintResizeFeedback(IGraphics gc, INode layout, GridModel grid) { 109 paintStructure(DrawingStyle.GRID, layout, gc, grid); 110 } 111 112 /** 113 * A painter which can paint the drop feedback for elements being dragged into or 114 * within a GridLayout. 115 */ 116 private static class DropFeedbackPainter implements IFeedbackPainter { 117 private final GridLayoutRule mRule; 118 private final IDragElement[] mElements; 119 120 /** Constructs a new {@link GridLayoutPainter} bound to the given {@link GridLayoutRule} 121 * @param rule the corresponding rule 122 * @param elements the elements to draw */ 123 public DropFeedbackPainter(GridLayoutRule rule, IDragElement[] elements) { 124 mRule = rule; 125 mElements = elements; 126 } 127 128 // Implements IFeedbackPainter 129 public void paint(IGraphics gc, INode node, DropFeedback feedback) { 130 Rect b = node.getBounds(); 131 if (!b.isValid()) { 132 return; 133 } 134 135 // Highlight the receiver 136 gc.useStyle(DrawingStyle.DROP_RECIPIENT); 137 gc.drawRect(b); 138 GridDropHandler data = (GridDropHandler) feedback.userData; 139 140 if (!GridLayoutRule.sGridMode) { 141 paintFreeFormDropFeedback(gc, node, feedback, b, data); 142 } else { 143 paintGridModeDropFeedback(gc, b, data); 144 } 145 } 146 147 /** 148 * Paints the drag feedback for a free-form mode drag 149 */ 150 private void paintFreeFormDropFeedback(IGraphics gc, INode node, DropFeedback feedback, 151 Rect b, GridDropHandler data) { 152 GridModel grid = data.getGrid(); 153 if (GridLayoutRule.sSnapToGrid) { 154 GridLayoutPainter.paintGrid(node, gc); 155 } 156 GridLayoutPainter.paintStructure(DrawingStyle.GRID, node, gc, grid); 157 158 GridMatch rowMatch = data.getRowMatch(); 159 GridMatch columnMatch = data.getColumnMatch(); 160 161 if (rowMatch == null || columnMatch == null) { 162 return; 163 } 164 165 IDragElement first = mElements[0]; 166 Rect dragBounds = first.getBounds(); 167 int offsetX = 0; 168 int offsetY = 0; 169 if (rowMatch.type == SegmentType.BOTTOM) { 170 offsetY -= dragBounds.h; 171 } else if (rowMatch.type == SegmentType.BASELINE) { 172 offsetY -= feedback.dragBaseline; 173 } 174 if (columnMatch.type == SegmentType.RIGHT) { 175 offsetX -= dragBounds.w; 176 } else if (columnMatch.type == SegmentType.CENTER_HORIZONTAL) { 177 offsetX -= dragBounds.w / 2; 178 } 179 180 // Draw guidelines for matches 181 int y = rowMatch.matchedLine; 182 int x = columnMatch.matchedLine; 183 Rect bounds = first.getBounds(); 184 185 // Draw margin 186 if (rowMatch.margin != UNDEFINED && rowMatch.margin > 0) { 187 gc.useStyle(DrawingStyle.DISTANCE); 188 int centerX = bounds.w / 2 + offsetX + x; 189 int y1; 190 int y2; 191 if (rowMatch.type == SegmentType.TOP) { 192 y1 = offsetY + y - 1; 193 y2 = rowMatch.matchedLine - rowMatch.margin; 194 } else { 195 assert rowMatch.type == SegmentType.BOTTOM; 196 y1 = bounds.h + offsetY + y - 1; 197 y2 = rowMatch.matchedLine + rowMatch.margin; 198 } 199 gc.drawLine(b.x, y1, b.x2(), y1); 200 gc.drawLine(b.x, y2, b.x2(), y2); 201 gc.drawString(Integer.toString(rowMatch.margin), 202 centerX - 3, y1 + (y2 - y1 - 16) / 2); 203 } else { 204 gc.useStyle(rowMatch.margin == 0 ? DrawingStyle.DISTANCE 205 : rowMatch.createCell ? DrawingStyle.GUIDELINE_DASHED 206 : DrawingStyle.GUIDELINE); 207 gc.drawLine(b.x, y, b.x2(), y ); 208 } 209 210 if (columnMatch.margin != UNDEFINED && columnMatch.margin > 0) { 211 gc.useStyle(DrawingStyle.DISTANCE); 212 int centerY = bounds.h / 2 + offsetY + y; 213 int x1; 214 int x2; 215 if (columnMatch.type == SegmentType.LEFT) { 216 x1 = offsetX + x - 1; 217 x2 = columnMatch.matchedLine - columnMatch.margin; 218 } else { 219 assert columnMatch.type == SegmentType.RIGHT; 220 x1 = bounds.w + offsetX + x - 1; 221 x2 = columnMatch.matchedLine + columnMatch.margin; 222 } 223 gc.drawLine(x1, b.y, x1, b.y2()); 224 gc.drawLine(x2, b.y, x2, b.y2()); 225 gc.drawString(Integer.toString(columnMatch.margin), 226 x1 + (x2 - x1 - 16) / 2, centerY - 3); 227 } else { 228 gc.useStyle(columnMatch.margin == 0 ? DrawingStyle.DISTANCE 229 : columnMatch.createCell ? DrawingStyle.GUIDELINE_DASHED 230 : DrawingStyle.GUIDELINE); 231 gc.drawLine(x, b.y, x, b.y2()); 232 } 233 234 // Draw preview rectangle of the first dragged element 235 gc.useStyle(DrawingStyle.DROP_PREVIEW); 236 mRule.drawElement(gc, first, x + offsetX - bounds.x, y + offsetY - bounds.y); 237 238 // Preview baseline as well 239 if (feedback.dragBaseline != -1) { 240 int x1 = dragBounds.x + x + offsetX - bounds.x; 241 int y1 = dragBounds.y + y + offsetY - bounds.y + feedback.dragBaseline; 242 gc.drawLine(x1, y1, x1 + dragBounds.w, y1); 243 } 244 } 245 246 /** 247 * Paints the drag feedback for a grid-mode drag 248 */ 249 private void paintGridModeDropFeedback(IGraphics gc, Rect b, GridDropHandler data) { 250 int radius = mRule.getNewCellSize(); 251 GridModel grid = data.getGrid(); 252 253 gc.useStyle(DrawingStyle.GUIDELINE); 254 // Paint grid 255 for (int row = 1; row < grid.actualRowCount; row++) { 256 int y = grid.getRowY(row); 257 gc.drawLine(b.x, y - radius, b.x2(), y - radius); 258 gc.drawLine(b.x, y + radius, b.x2(), y + radius); 259 260 } 261 for (int column = 1; column < grid.actualColumnCount; column++) { 262 int x = grid.getColumnX(column); 263 gc.drawLine(x - radius, b.y, x - radius, b.y2()); 264 gc.drawLine(x + radius, b.y, x + radius, b.y2()); 265 } 266 gc.drawRect(b.x, b.y, b.x2(), b.y2()); 267 gc.drawRect(b.x + 2 * radius, b.y + 2 * radius, 268 b.x2() - 2 * radius, b.y2() - 2 * radius); 269 270 int column = data.getColumnMatch().cellIndex; 271 int row = data.getRowMatch().cellIndex; 272 boolean createColumn = data.getColumnMatch().createCell; 273 boolean createRow = data.getRowMatch().createCell; 274 275 Rect cellBounds = grid.getCellBounds(row, column, 1, 1); 276 277 IDragElement first = mElements[0]; 278 Rect dragBounds = first.getBounds(); 279 int offsetX = cellBounds.x - dragBounds.x; 280 int offsetY = cellBounds.y - dragBounds.y; 281 282 gc.useStyle(DrawingStyle.DROP_ZONE_ACTIVE); 283 if (createColumn) { 284 gc.fillRect(new Rect(cellBounds.x - radius, 285 cellBounds.y + (createRow ? -radius : radius), 286 2 * radius + 1, cellBounds.h - (createRow ? 0 : 2 * radius))); 287 offsetX -= radius + dragBounds.w / 2; 288 } 289 if (createRow) { 290 gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y - radius, 291 cellBounds.w - 2 * radius, 2 * radius + 1)); 292 offsetY -= radius + dragBounds.h / 2; 293 } else if (!createColumn) { 294 // Choose this cell 295 gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y + radius, 296 cellBounds.w - 2 * radius, cellBounds.h - 2 * radius)); 297 } 298 299 gc.useStyle(DrawingStyle.DROP_PREVIEW); 300 mRule.drawElement(gc, first, offsetX, offsetY); 301 } 302 } 303 304 /** 305 * Paints the structure (the row and column boundaries) of the given 306 * GridLayout 307 * 308 * @param view the instance of the GridLayout whose structure should be 309 * painted 310 * @param style the drawing style to use for the cell boundaries 311 * @param layout the layout element 312 * @param gc the graphics context 313 * @return true if the structure was successfully inferred from the view and 314 * painted 315 */ 316 public static boolean paintStructure(Object view, DrawingStyle style, INode layout, 317 IGraphics gc) { 318 Pair<int[],int[]> cellBounds = GridModel.getAxisBounds(view); 319 if (cellBounds != null) { 320 int[] xs = cellBounds.getFirst(); 321 int[] ys = cellBounds.getSecond(); 322 Rect b = layout.getBounds(); 323 gc.useStyle(style); 324 for (int row = 0; row < ys.length; row++) { 325 int y = ys[row] + b.y; 326 gc.drawLine(b.x, y, b.x2(), y); 327 } 328 for (int column = 0; column < xs.length; column++) { 329 int x = xs[column] + b.x; 330 gc.drawLine(x, b.y, x, b.y2()); 331 } 332 333 return true; 334 } else { 335 return false; 336 } 337 } 338 } 339