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.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      */
     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      */
     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      */
     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      */
    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 */
    124         public DropFeedbackPainter(GridLayoutRule rule, IDragElement[] elements) {
    125             mRule = rule;
    126             mElements = elements;
    127         }
    128 
    129         // Implements IFeedbackPainter
    130         @Override
    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          */
    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          */
    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      */
    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