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 }