Home | History | Annotate | Download | only in relative
      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.relative;
     17 
     18 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_BOTTOM;
     19 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_LEFT;
     20 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_RIGHT;
     21 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_TOP;
     22 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX;
     23 import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX;
     24 import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
     25 
     26 import com.android.ide.common.api.DrawingStyle;
     27 import com.android.ide.common.api.DropFeedback;
     28 import com.android.ide.common.api.IFeedbackPainter;
     29 import com.android.ide.common.api.IGraphics;
     30 import com.android.ide.common.api.INode;
     31 import com.android.ide.common.api.Point;
     32 import com.android.ide.common.api.Rect;
     33 import com.android.ide.common.api.SegmentType;
     34 import com.android.ide.common.layout.relative.DependencyGraph.Constraint;
     35 
     36 import java.util.ArrayList;
     37 import java.util.HashSet;
     38 import java.util.List;
     39 import java.util.Set;
     40 
     41 /**
     42  * The {@link GuidelinePainter} is responsible for painting guidelines during an operation
     43  * which uses a {@link GuidelineHandler} such as a resize operation.
     44  */
     45 public final class GuidelinePainter implements IFeedbackPainter {
     46     // ---- Implements IFeedbackPainter ----
     47     @Override
     48     public void paint(IGraphics gc, INode node, DropFeedback feedback) {
     49         GuidelineHandler state = (GuidelineHandler) feedback.userData;
     50 
     51         for (INode dragged : state.mDraggedNodes) {
     52             gc.useStyle(DrawingStyle.DRAGGED);
     53             Rect bounds = dragged.getBounds();
     54             if (bounds.isValid()) {
     55                 gc.fillRect(bounds);
     56             }
     57         }
     58 
     59         Set<INode> horizontalDeps = state.mHorizontalDeps;
     60         Set<INode> verticalDeps = state.mVerticalDeps;
     61         Set<INode> deps = new HashSet<INode>(horizontalDeps.size() + verticalDeps.size());
     62         deps.addAll(horizontalDeps);
     63         deps.addAll(verticalDeps);
     64         if (deps.size() > 0) {
     65             gc.useStyle(DrawingStyle.DEPENDENCY);
     66             for (INode n : deps) {
     67                 // Don't highlight the selected nodes themselves
     68                 if (state.mDraggedNodes.contains(n)) {
     69                     continue;
     70                 }
     71                 Rect bounds = n.getBounds();
     72                 gc.fillRect(bounds);
     73             }
     74         }
     75 
     76         if (state.mBounds != null) {
     77             if (state instanceof MoveHandler) {
     78                 gc.useStyle(DrawingStyle.DROP_PREVIEW);
     79             } else {
     80                 // Resizing
     81                 if (state.haveSuggestions()) {
     82                     gc.useStyle(DrawingStyle.RESIZE_PREVIEW);
     83                 } else {
     84                     gc.useStyle(DrawingStyle.RESIZE_FAIL);
     85                 }
     86             }
     87             gc.drawRect(state.mBounds);
     88 
     89             // Draw baseline preview too
     90             if (feedback.dragBaseline != -1) {
     91                 int y = state.mBounds.y + feedback.dragBaseline;
     92                 gc.drawLine(state.mBounds.x, y, state.mBounds.x2(), y);
     93             }
     94         }
     95 
     96         List<String> strings = new ArrayList<String>();
     97 
     98         showMatch(gc, state.mCurrentLeftMatch, state, strings,
     99                 state.mLeftMargin, ATTR_LAYOUT_MARGIN_LEFT);
    100         showMatch(gc, state.mCurrentRightMatch, state, strings,
    101                 state.mRightMargin, ATTR_LAYOUT_MARGIN_RIGHT);
    102         showMatch(gc, state.mCurrentTopMatch, state, strings,
    103                 state.mTopMargin, ATTR_LAYOUT_MARGIN_TOP);
    104         showMatch(gc, state.mCurrentBottomMatch, state, strings,
    105                 state.mBottomMargin, ATTR_LAYOUT_MARGIN_BOTTOM);
    106 
    107         if (strings.size() > 0) {
    108             // Update the drag tooltip
    109             StringBuilder sb = new StringBuilder(200);
    110             for (String s : strings) {
    111                 if (sb.length() > 0) {
    112                     sb.append('\n');
    113                 }
    114                 sb.append(s);
    115             }
    116             feedback.tooltip = sb.toString();
    117 
    118             // Set the tooltip orientation to ensure that it does not interfere with
    119             // the constraint arrows
    120             if (state.mCurrentLeftMatch != null) {
    121                 feedback.tooltipX = SegmentType.RIGHT;
    122             } else if (state.mCurrentRightMatch != null) {
    123                 feedback.tooltipX = SegmentType.LEFT;
    124             }
    125             if (state.mCurrentTopMatch != null) {
    126                 feedback.tooltipY = SegmentType.BOTTOM;
    127             } else if (state.mCurrentBottomMatch != null) {
    128                 feedback.tooltipY = SegmentType.TOP;
    129             }
    130         } else {
    131             feedback.tooltip = null;
    132         }
    133 
    134         if (state.mHorizontalCycle != null) {
    135             paintCycle(gc, state, state.mHorizontalCycle);
    136         }
    137         if (state.mVerticalCycle != null) {
    138             paintCycle(gc, state, state.mVerticalCycle);
    139         }
    140     }
    141 
    142     /** Paints a particular match constraint */
    143     private void showMatch(IGraphics gc, Match m, GuidelineHandler state, List<String> strings,
    144             int margin, String marginAttribute) {
    145         if (m == null) {
    146             return;
    147         }
    148         ConstraintPainter.paintConstraint(gc, state.mBounds, m);
    149 
    150         // Display the constraint. Remove the @id/ and @+id/ prefixes to make the text
    151         // shorter and easier to read. This doesn't use stripPrefix() because the id is
    152         // usually not a prefix of the value (for example, 'layout_alignBottom=@+id/foo').
    153         String constraint = m.getConstraint(false /* generateId */);
    154         String description = constraint.replace(NEW_ID_PREFIX, "").replace(ID_PREFIX, "");
    155         if (description.startsWith(ATTR_LAYOUT_PREFIX)) {
    156             description = description.substring(ATTR_LAYOUT_PREFIX.length());
    157         }
    158         if (margin > 0) {
    159             int dp = state.getRulesEngine().pxToDp(margin);
    160             description = String.format("%1$s, margin=%2$d dp", description, dp);
    161         }
    162         strings.add(description);
    163     }
    164 
    165     /** Paints a constraint cycle */
    166     void paintCycle(IGraphics gc, GuidelineHandler state, List<Constraint> cycle) {
    167         gc.useStyle(DrawingStyle.CYCLE);
    168         assert cycle.size() > 0;
    169 
    170         INode from = cycle.get(0).from.node;
    171         Rect fromBounds = from.getBounds();
    172         if (state.mDraggedNodes.contains(from)) {
    173             fromBounds = state.mBounds;
    174         }
    175         Point fromCenter = fromBounds.center();
    176         INode to = null;
    177 
    178         List<Point> points = new ArrayList<Point>();
    179         points.add(fromCenter);
    180 
    181         for (Constraint constraint : cycle) {
    182             assert constraint.from.node == from;
    183             to = constraint.to.node;
    184             assert from != null && to != null;
    185 
    186             Point toCenter = to.getBounds().center();
    187             points.add(toCenter);
    188 
    189             // Also go through the dragged node bounds
    190             boolean isDragged = state.mDraggedNodes.contains(to);
    191             if (isDragged) {
    192                 toCenter = state.mBounds.center();
    193                 points.add(toCenter);
    194             }
    195 
    196             from = to;
    197             fromCenter = toCenter;
    198         }
    199 
    200         points.add(fromCenter);
    201         points.add(points.get(0));
    202 
    203         for (int i = 1, n = points.size(); i < n; i++) {
    204             gc.drawLine(points.get(i-1), points.get(i));
    205         }
    206     }
    207 }