blob: 2fe74768f4089952eb63ed34ebc6a2b630d06243 [file] [log] [blame]
/*
* 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));
}
}
}