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