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