/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.eclipse.org/org/documents/epl-v10.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ide.common.layout.grid; import static com.android.ide.common.layout.GridLayoutRule.GRID_SIZE; import static com.android.ide.common.layout.GridLayoutRule.MARGIN_SIZE; import static com.android.ide.common.layout.grid.GridModel.UNDEFINED; import com.android.annotations.NonNull; import com.android.ide.common.api.DrawingStyle; import com.android.ide.common.api.DropFeedback; import com.android.ide.common.api.IDragElement; import com.android.ide.common.api.IFeedbackPainter; import com.android.ide.common.api.IGraphics; import com.android.ide.common.api.INode; import com.android.ide.common.api.Rect; import com.android.ide.common.api.SegmentType; import com.android.ide.common.layout.GridLayoutRule; import com.android.utils.Pair; /** * Painter which paints feedback during drag, drop and resizing operations, as well as * static selection feedback */ public class GridLayoutPainter { /** * Creates a painter for drop feedback * * @param rule the corresponding {@link GridLayoutRule} * @param elements the dragged elements * @return a {@link IFeedbackPainter} which can paint the drop feedback */ public static IFeedbackPainter createDropFeedbackPainter(GridLayoutRule rule, IDragElement[] elements) { return new DropFeedbackPainter(rule, elements); } /** * Paints the structure (the grid model) of the given GridLayout. * * @param style the drawing style to use to paint the structure lines * @param layout the grid layout node * @param gc the graphics context to paint into * @param grid the grid model to be visualized */ public static void paintStructure(DrawingStyle style, INode layout, IGraphics gc, GridModel grid) { Rect b = layout.getBounds(); gc.useStyle(style); for (int row = 0; row < grid.actualRowCount; row++) { int y = grid.getRowY(row); gc.drawLine(b.x, y, b.x2(), y); } for (int column = 0; column < grid.actualColumnCount; column++) { int x = grid.getColumnX(column); gc.drawLine(x, b.y, x, b.y2()); } } /** * Paints a regular grid according to the {@link GridLayoutRule#GRID_SIZE} and * {@link GridLayoutRule#MARGIN_SIZE} dimensions. These are the same lines that * snap-to-grid will align with. * * @param layout the GridLayout node * @param gc the graphics context to paint the grid into */ public static void paintGrid(INode layout, IGraphics gc) { Rect b = layout.getBounds(); int oldAlpha = gc.getAlpha(); gc.useStyle(DrawingStyle.GUIDELINE); gc.setAlpha(128); int y1 = b.y + MARGIN_SIZE; int y2 = b.y2() - MARGIN_SIZE; for (int y = y1; y < y2; y += GRID_SIZE) { int x1 = b.x + MARGIN_SIZE; int x2 = b.x2() - MARGIN_SIZE; for (int x = x1; x < x2; x += GRID_SIZE) { gc.drawPoint(x, y); } } gc.setAlpha(oldAlpha); } /** * Paint resizing feedback (which currently paints the grid model faintly.) * * @param gc the graphics context * @param layout the GridLayout * @param grid the grid model */ public static void paintResizeFeedback(IGraphics gc, INode layout, GridModel grid) { paintStructure(DrawingStyle.GRID, layout, gc, grid); } /** * A painter which can paint the drop feedback for elements being dragged into or * within a GridLayout. */ private static class DropFeedbackPainter implements IFeedbackPainter { private final GridLayoutRule mRule; private final IDragElement[] mElements; /** Constructs a new {@link GridLayoutPainter} bound to the given {@link GridLayoutRule} * @param rule the corresponding rule * @param elements the elements to draw */ public DropFeedbackPainter(GridLayoutRule rule, IDragElement[] elements) { mRule = rule; mElements = elements; } // Implements IFeedbackPainter @Override public void paint(@NonNull IGraphics gc, @NonNull INode node, @NonNull DropFeedback feedback) { Rect b = node.getBounds(); if (!b.isValid()) { return; } // Highlight the receiver gc.useStyle(DrawingStyle.DROP_RECIPIENT); gc.drawRect(b); GridDropHandler data = (GridDropHandler) feedback.userData; if (!GridLayoutRule.sGridMode) { paintFreeFormDropFeedback(gc, node, feedback, b, data); } else { paintGridModeDropFeedback(gc, b, data); } } /** * Paints the drag feedback for a free-form mode drag */ private void paintFreeFormDropFeedback(IGraphics gc, INode node, DropFeedback feedback, Rect b, GridDropHandler data) { GridModel grid = data.getGrid(); if (GridLayoutRule.sSnapToGrid) { GridLayoutPainter.paintGrid(node, gc); } GridLayoutPainter.paintStructure(DrawingStyle.GRID, node, gc, grid); GridMatch rowMatch = data.getRowMatch(); GridMatch columnMatch = data.getColumnMatch(); if (rowMatch == null || columnMatch == null) { return; } IDragElement first = mElements[0]; Rect dragBounds = first.getBounds(); int offsetX = 0; int offsetY = 0; if (rowMatch.type == SegmentType.BOTTOM) { offsetY -= dragBounds.h; } else if (rowMatch.type == SegmentType.BASELINE) { offsetY -= feedback.dragBaseline; } if (columnMatch.type == SegmentType.RIGHT) { offsetX -= dragBounds.w; } else if (columnMatch.type == SegmentType.CENTER_HORIZONTAL) { offsetX -= dragBounds.w / 2; } // Draw guidelines for matches int y = rowMatch.matchedLine; int x = columnMatch.matchedLine; Rect bounds = first.getBounds(); // Draw margin if (rowMatch.margin != UNDEFINED && rowMatch.margin > 0) { gc.useStyle(DrawingStyle.DISTANCE); int centerX = bounds.w / 2 + offsetX + x; int y1; int y2; if (rowMatch.type == SegmentType.TOP) { y1 = offsetY + y - 1; y2 = rowMatch.matchedLine - rowMatch.margin; } else { assert rowMatch.type == SegmentType.BOTTOM; y1 = bounds.h + offsetY + y - 1; y2 = rowMatch.matchedLine + rowMatch.margin; } gc.drawLine(b.x, y1, b.x2(), y1); gc.drawLine(b.x, y2, b.x2(), y2); gc.drawString(Integer.toString(rowMatch.margin), centerX - 3, y1 + (y2 - y1 - 16) / 2); } else { gc.useStyle(rowMatch.margin == 0 ? DrawingStyle.DISTANCE : rowMatch.createCell ? DrawingStyle.GUIDELINE_DASHED : DrawingStyle.GUIDELINE); gc.drawLine(b.x, y, b.x2(), y ); } if (columnMatch.margin != UNDEFINED && columnMatch.margin > 0) { gc.useStyle(DrawingStyle.DISTANCE); int centerY = bounds.h / 2 + offsetY + y; int x1; int x2; if (columnMatch.type == SegmentType.LEFT) { x1 = offsetX + x - 1; x2 = columnMatch.matchedLine - columnMatch.margin; } else { assert columnMatch.type == SegmentType.RIGHT; x1 = bounds.w + offsetX + x - 1; x2 = columnMatch.matchedLine + columnMatch.margin; } gc.drawLine(x1, b.y, x1, b.y2()); gc.drawLine(x2, b.y, x2, b.y2()); gc.drawString(Integer.toString(columnMatch.margin), x1 + (x2 - x1 - 16) / 2, centerY - 3); } else { gc.useStyle(columnMatch.margin == 0 ? DrawingStyle.DISTANCE : columnMatch.createCell ? DrawingStyle.GUIDELINE_DASHED : DrawingStyle.GUIDELINE); gc.drawLine(x, b.y, x, b.y2()); } // Draw preview rectangles for all the dragged elements gc.useStyle(DrawingStyle.DROP_PREVIEW); offsetX += x - bounds.x; offsetY += y - bounds.y; for (IDragElement element : mElements) { if (element == first) { mRule.drawElement(gc, first, offsetX, offsetY); // Preview baseline as well if (feedback.dragBaseline != -1) { int x1 = dragBounds.x + offsetX; int y1 = dragBounds.y + offsetY + feedback.dragBaseline; gc.drawLine(x1, y1, x1 + dragBounds.w, y1); } } else { b = element.getBounds(); if (b.isValid()) { gc.drawRect(b.x + offsetX, b.y + offsetY, b.x + offsetX + b.w, b.y + offsetY + b.h); } } } } /** * Paints the drag feedback for a grid-mode drag */ private void paintGridModeDropFeedback(IGraphics gc, Rect b, GridDropHandler data) { int radius = mRule.getNewCellSize(); GridModel grid = data.getGrid(); gc.useStyle(DrawingStyle.GUIDELINE); // Paint grid for (int row = 1; row < grid.actualRowCount; row++) { int y = grid.getRowY(row); gc.drawLine(b.x, y - radius, b.x2(), y - radius); gc.drawLine(b.x, y + radius, b.x2(), y + radius); } for (int column = 1; column < grid.actualColumnCount; column++) { int x = grid.getColumnX(column); gc.drawLine(x - radius, b.y, x - radius, b.y2()); gc.drawLine(x + radius, b.y, x + radius, b.y2()); } gc.drawRect(b.x, b.y, b.x2(), b.y2()); gc.drawRect(b.x + 2 * radius, b.y + 2 * radius, b.x2() - 2 * radius, b.y2() - 2 * radius); int column = data.getColumnMatch().cellIndex; int row = data.getRowMatch().cellIndex; boolean createColumn = data.getColumnMatch().createCell; boolean createRow = data.getRowMatch().createCell; Rect cellBounds = grid.getCellBounds(row, column, 1, 1); IDragElement first = mElements[0]; Rect dragBounds = first.getBounds(); int offsetX = cellBounds.x - dragBounds.x; int offsetY = cellBounds.y - dragBounds.y; gc.useStyle(DrawingStyle.DROP_ZONE_ACTIVE); if (createColumn) { gc.fillRect(new Rect(cellBounds.x - radius, cellBounds.y + (createRow ? -radius : radius), 2 * radius + 1, cellBounds.h - (createRow ? 0 : 2 * radius))); offsetX -= radius + dragBounds.w / 2; } if (createRow) { gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y - radius, cellBounds.w - 2 * radius, 2 * radius + 1)); offsetY -= radius + dragBounds.h / 2; } else if (!createColumn) { // Choose this cell gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y + radius, cellBounds.w - 2 * radius, cellBounds.h - 2 * radius)); } gc.useStyle(DrawingStyle.DROP_PREVIEW); mRule.drawElement(gc, first, offsetX, offsetY); } } /** * Paints the structure (the row and column boundaries) of the given * GridLayout * * @param view the instance of the GridLayout whose structure should be * painted * @param style the drawing style to use for the cell boundaries * @param layout the layout element * @param gc the graphics context * @return true if the structure was successfully inferred from the view and * painted */ public static boolean paintStructure(Object view, DrawingStyle style, INode layout, IGraphics gc) { Pair<int[],int[]> cellBounds = GridModel.getAxisBounds(view); if (cellBounds != null) { int[] xs = cellBounds.getFirst(); int[] ys = cellBounds.getSecond(); Rect b = layout.getBounds(); gc.useStyle(style); for (int row = 0; row < ys.length; row++) { int y = ys[row] + b.y; gc.drawLine(b.x, y, b.x2(), y); } for (int column = 0; column < xs.length; column++) { int x = xs[column] + b.x; gc.drawLine(x, b.y, x, b.y2()); } return true; } else { return false; } } }