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