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 @Override 130 public void paint(IGraphics gc, INode node, DropFeedback feedback) { 131 Rect b = node.getBounds(); 132 if (!b.isValid()) { 133 return; 134 } 135 136 // Highlight the receiver 137 gc.useStyle(DrawingStyle.DROP_RECIPIENT); 138 gc.drawRect(b); 139 GridDropHandler data = (GridDropHandler) feedback.userData; 140 141 if (!GridLayoutRule.sGridMode) { 142 paintFreeFormDropFeedback(gc, node, feedback, b, data); 143 } else { 144 paintGridModeDropFeedback(gc, b, data); 145 } 146 } 147 148 /** 149 * Paints the drag feedback for a free-form mode drag 150 */ 151 private void paintFreeFormDropFeedback(IGraphics gc, INode node, DropFeedback feedback, 152 Rect b, GridDropHandler data) { 153 GridModel grid = data.getGrid(); 154 if (GridLayoutRule.sSnapToGrid) { 155 GridLayoutPainter.paintGrid(node, gc); 156 } 157 GridLayoutPainter.paintStructure(DrawingStyle.GRID, node, gc, grid); 158 159 GridMatch rowMatch = data.getRowMatch(); 160 GridMatch columnMatch = data.getColumnMatch(); 161 162 if (rowMatch == null || columnMatch == null) { 163 return; 164 } 165 166 IDragElement first = mElements[0]; 167 Rect dragBounds = first.getBounds(); 168 int offsetX = 0; 169 int offsetY = 0; 170 if (rowMatch.type == SegmentType.BOTTOM) { 171 offsetY -= dragBounds.h; 172 } else if (rowMatch.type == SegmentType.BASELINE) { 173 offsetY -= feedback.dragBaseline; 174 } 175 if (columnMatch.type == SegmentType.RIGHT) { 176 offsetX -= dragBounds.w; 177 } else if (columnMatch.type == SegmentType.CENTER_HORIZONTAL) { 178 offsetX -= dragBounds.w / 2; 179 } 180 181 // Draw guidelines for matches 182 int y = rowMatch.matchedLine; 183 int x = columnMatch.matchedLine; 184 Rect bounds = first.getBounds(); 185 186 // Draw margin 187 if (rowMatch.margin != UNDEFINED && rowMatch.margin > 0) { 188 gc.useStyle(DrawingStyle.DISTANCE); 189 int centerX = bounds.w / 2 + offsetX + x; 190 int y1; 191 int y2; 192 if (rowMatch.type == SegmentType.TOP) { 193 y1 = offsetY + y - 1; 194 y2 = rowMatch.matchedLine - rowMatch.margin; 195 } else { 196 assert rowMatch.type == SegmentType.BOTTOM; 197 y1 = bounds.h + offsetY + y - 1; 198 y2 = rowMatch.matchedLine + rowMatch.margin; 199 } 200 gc.drawLine(b.x, y1, b.x2(), y1); 201 gc.drawLine(b.x, y2, b.x2(), y2); 202 gc.drawString(Integer.toString(rowMatch.margin), 203 centerX - 3, y1 + (y2 - y1 - 16) / 2); 204 } else { 205 gc.useStyle(rowMatch.margin == 0 ? DrawingStyle.DISTANCE 206 : rowMatch.createCell ? DrawingStyle.GUIDELINE_DASHED 207 : DrawingStyle.GUIDELINE); 208 gc.drawLine(b.x, y, b.x2(), y ); 209 } 210 211 if (columnMatch.margin != UNDEFINED && columnMatch.margin > 0) { 212 gc.useStyle(DrawingStyle.DISTANCE); 213 int centerY = bounds.h / 2 + offsetY + y; 214 int x1; 215 int x2; 216 if (columnMatch.type == SegmentType.LEFT) { 217 x1 = offsetX + x - 1; 218 x2 = columnMatch.matchedLine - columnMatch.margin; 219 } else { 220 assert columnMatch.type == SegmentType.RIGHT; 221 x1 = bounds.w + offsetX + x - 1; 222 x2 = columnMatch.matchedLine + columnMatch.margin; 223 } 224 gc.drawLine(x1, b.y, x1, b.y2()); 225 gc.drawLine(x2, b.y, x2, b.y2()); 226 gc.drawString(Integer.toString(columnMatch.margin), 227 x1 + (x2 - x1 - 16) / 2, centerY - 3); 228 } else { 229 gc.useStyle(columnMatch.margin == 0 ? DrawingStyle.DISTANCE 230 : columnMatch.createCell ? DrawingStyle.GUIDELINE_DASHED 231 : DrawingStyle.GUIDELINE); 232 gc.drawLine(x, b.y, x, b.y2()); 233 } 234 235 // Draw preview rectangle of the first dragged element 236 gc.useStyle(DrawingStyle.DROP_PREVIEW); 237 mRule.drawElement(gc, first, x + offsetX - bounds.x, y + offsetY - bounds.y); 238 239 // Preview baseline as well 240 if (feedback.dragBaseline != -1) { 241 int x1 = dragBounds.x + x + offsetX - bounds.x; 242 int y1 = dragBounds.y + y + offsetY - bounds.y + feedback.dragBaseline; 243 gc.drawLine(x1, y1, x1 + dragBounds.w, y1); 244 } 245 } 246 247 /** 248 * Paints the drag feedback for a grid-mode drag 249 */ 250 private void paintGridModeDropFeedback(IGraphics gc, Rect b, GridDropHandler data) { 251 int radius = mRule.getNewCellSize(); 252 GridModel grid = data.getGrid(); 253 254 gc.useStyle(DrawingStyle.GUIDELINE); 255 // Paint grid 256 for (int row = 1; row < grid.actualRowCount; row++) { 257 int y = grid.getRowY(row); 258 gc.drawLine(b.x, y - radius, b.x2(), y - radius); 259 gc.drawLine(b.x, y + radius, b.x2(), y + radius); 260 261 } 262 for (int column = 1; column < grid.actualColumnCount; column++) { 263 int x = grid.getColumnX(column); 264 gc.drawLine(x - radius, b.y, x - radius, b.y2()); 265 gc.drawLine(x + radius, b.y, x + radius, b.y2()); 266 } 267 gc.drawRect(b.x, b.y, b.x2(), b.y2()); 268 gc.drawRect(b.x + 2 * radius, b.y + 2 * radius, 269 b.x2() - 2 * radius, b.y2() - 2 * radius); 270 271 int column = data.getColumnMatch().cellIndex; 272 int row = data.getRowMatch().cellIndex; 273 boolean createColumn = data.getColumnMatch().createCell; 274 boolean createRow = data.getRowMatch().createCell; 275 276 Rect cellBounds = grid.getCellBounds(row, column, 1, 1); 277 278 IDragElement first = mElements[0]; 279 Rect dragBounds = first.getBounds(); 280 int offsetX = cellBounds.x - dragBounds.x; 281 int offsetY = cellBounds.y - dragBounds.y; 282 283 gc.useStyle(DrawingStyle.DROP_ZONE_ACTIVE); 284 if (createColumn) { 285 gc.fillRect(new Rect(cellBounds.x - radius, 286 cellBounds.y + (createRow ? -radius : radius), 287 2 * radius + 1, cellBounds.h - (createRow ? 0 : 2 * radius))); 288 offsetX -= radius + dragBounds.w / 2; 289 } 290 if (createRow) { 291 gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y - radius, 292 cellBounds.w - 2 * radius, 2 * radius + 1)); 293 offsetY -= radius + dragBounds.h / 2; 294 } else if (!createColumn) { 295 // Choose this cell 296 gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y + radius, 297 cellBounds.w - 2 * radius, cellBounds.h - 2 * radius)); 298 } 299 300 gc.useStyle(DrawingStyle.DROP_PREVIEW); 301 mRule.drawElement(gc, first, offsetX, offsetY); 302 } 303 } 304 305 /** 306 * Paints the structure (the row and column boundaries) of the given 307 * GridLayout 308 * 309 * @param view the instance of the GridLayout whose structure should be 310 * painted 311 * @param style the drawing style to use for the cell boundaries 312 * @param layout the layout element 313 * @param gc the graphics context 314 * @return true if the structure was successfully inferred from the view and 315 * painted 316 */ 317 public static boolean paintStructure(Object view, DrawingStyle style, INode layout, 318 IGraphics gc) { 319 Pair<int[],int[]> cellBounds = GridModel.getAxisBounds(view); 320 if (cellBounds != null) { 321 int[] xs = cellBounds.getFirst(); 322 int[] ys = cellBounds.getSecond(); 323 Rect b = layout.getBounds(); 324 gc.useStyle(style); 325 for (int row = 0; row < ys.length; row++) { 326 int y = ys[row] + b.y; 327 gc.drawLine(b.x, y, b.x2(), y); 328 } 329 for (int column = 0; column < xs.length; column++) { 330 int x = xs[column] + b.x; 331 gc.drawLine(x, b.y, x, b.y2()); 332 } 333 334 return true; 335 } else { 336 return false; 337 } 338 } 339 } 340