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