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