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         @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