Home | History | Annotate | Download | only in grid
      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