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
paint(@onNull IGraphics gc, @NonNull INode node, @NonNull DropFeedback feedback)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 */
showMatch(IGraphics gc, Match m, GuidelineHandler state, List<String> strings, int margin, String marginAttribute)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 */
paintCycle(IGraphics gc, GuidelineHandler state, List<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