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 }