/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.eclipse.org/org/documents/epl-v10.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ide.common.layout.relative; import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_BOTTOM; import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT; import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT; import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_TOP; import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; import static com.android.SdkConstants.ID_PREFIX; import static com.android.SdkConstants.NEW_ID_PREFIX; import com.android.annotations.NonNull; import com.android.ide.common.api.DrawingStyle; import com.android.ide.common.api.DropFeedback; import com.android.ide.common.api.IFeedbackPainter; import com.android.ide.common.api.IGraphics; import com.android.ide.common.api.INode; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; import com.android.ide.common.api.SegmentType; import com.android.ide.common.layout.relative.DependencyGraph.Constraint; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * The {@link GuidelinePainter} is responsible for painting guidelines during an operation * which uses a {@link GuidelineHandler} such as a resize operation. */ public final class GuidelinePainter implements IFeedbackPainter { // ---- Implements IFeedbackPainter ---- @Override public void paint(@NonNull IGraphics gc, @NonNull INode node, @NonNull DropFeedback feedback) { GuidelineHandler state = (GuidelineHandler) feedback.userData; for (INode dragged : state.mDraggedNodes) { gc.useStyle(DrawingStyle.DRAGGED); Rect bounds = dragged.getBounds(); if (bounds.isValid()) { gc.fillRect(bounds); } } Set<INode> horizontalDeps = state.mHorizontalDeps; Set<INode> verticalDeps = state.mVerticalDeps; Set<INode> deps = new HashSet<INode>(horizontalDeps.size() + verticalDeps.size()); deps.addAll(horizontalDeps); deps.addAll(verticalDeps); if (deps.size() > 0) { gc.useStyle(DrawingStyle.DEPENDENCY); for (INode n : deps) { // Don't highlight the selected nodes themselves if (state.mDraggedNodes.contains(n)) { continue; } Rect bounds = n.getBounds(); gc.fillRect(bounds); } } if (state.mBounds != null) { if (state instanceof MoveHandler) { gc.useStyle(DrawingStyle.DROP_PREVIEW); } else { // Resizing if (state.haveSuggestions()) { gc.useStyle(DrawingStyle.RESIZE_PREVIEW); } else { gc.useStyle(DrawingStyle.RESIZE_FAIL); } } gc.drawRect(state.mBounds); // Draw baseline preview too if (feedback.dragBaseline != -1) { int y = state.mBounds.y + feedback.dragBaseline; gc.drawLine(state.mBounds.x, y, state.mBounds.x2(), y); } } List<String> strings = new ArrayList<String>(); showMatch(gc, state.mCurrentLeftMatch, state, strings, state.mLeftMargin, ATTR_LAYOUT_MARGIN_LEFT); showMatch(gc, state.mCurrentRightMatch, state, strings, state.mRightMargin, ATTR_LAYOUT_MARGIN_RIGHT); showMatch(gc, state.mCurrentTopMatch, state, strings, state.mTopMargin, ATTR_LAYOUT_MARGIN_TOP); showMatch(gc, state.mCurrentBottomMatch, state, strings, state.mBottomMargin, ATTR_LAYOUT_MARGIN_BOTTOM); if (strings.size() > 0) { // Update the drag tooltip StringBuilder sb = new StringBuilder(200); for (String s : strings) { if (sb.length() > 0) { sb.append('\n'); } sb.append(s); } feedback.tooltip = sb.toString(); // Set the tooltip orientation to ensure that it does not interfere with // the constraint arrows if (state.mCurrentLeftMatch != null) { feedback.tooltipX = SegmentType.RIGHT; } else if (state.mCurrentRightMatch != null) { feedback.tooltipX = SegmentType.LEFT; } if (state.mCurrentTopMatch != null) { feedback.tooltipY = SegmentType.BOTTOM; } else if (state.mCurrentBottomMatch != null) { feedback.tooltipY = SegmentType.TOP; } } else { feedback.tooltip = null; } if (state.mHorizontalCycle != null) { paintCycle(gc, state, state.mHorizontalCycle); } if (state.mVerticalCycle != null) { paintCycle(gc, state, state.mVerticalCycle); } } /** Paints a particular match constraint */ private void showMatch(IGraphics gc, Match m, GuidelineHandler state, List<String> strings, int margin, String marginAttribute) { if (m == null) { return; } ConstraintPainter.paintConstraint(gc, state.mBounds, m); // Display the constraint. Remove the @id/ and @+id/ prefixes to make the text // shorter and easier to read. This doesn't use stripPrefix() because the id is // usually not a prefix of the value (for example, 'layout_alignBottom=@+id/foo'). String constraint = m.getConstraint(false /* generateId */); String description = constraint.replace(NEW_ID_PREFIX, "").replace(ID_PREFIX, ""); if (description.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { description = description.substring(ATTR_LAYOUT_RESOURCE_PREFIX.length()); } if (margin > 0) { int dp = state.getRulesEngine().pxToDp(margin); description = String.format("%1$s, margin=%2$d dp", description, dp); } strings.add(description); } /** Paints a constraint cycle */ void paintCycle(IGraphics gc, GuidelineHandler state, List<Constraint> cycle) { gc.useStyle(DrawingStyle.CYCLE); assert cycle.size() > 0; INode from = cycle.get(0).from.node; Rect fromBounds = from.getBounds(); if (state.mDraggedNodes.contains(from)) { fromBounds = state.mBounds; } Point fromCenter = fromBounds.center(); INode to = null; List<Point> points = new ArrayList<Point>(); points.add(fromCenter); for (Constraint constraint : cycle) { assert constraint.from.node == from; to = constraint.to.node; assert from != null && to != null; Point toCenter = to.getBounds().center(); points.add(toCenter); // Also go through the dragged node bounds boolean isDragged = state.mDraggedNodes.contains(to); if (isDragged) { toCenter = state.mBounds.center(); points.add(toCenter); } from = to; fromCenter = toCenter; } points.add(fromCenter); points.add(points.get(0)); for (int i = 1, n = points.size(); i < n; i++) { gc.drawLine(points.get(i-1), points.get(i)); } } }