blob: 3ec3b5f1a752356279d55973a6f88cb6465ba384 [file] [log] [blame]
/*
* Copyright (C) 2010 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;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_LAYOUT_X;
import static com.android.SdkConstants.ATTR_LAYOUT_Y;
import static com.android.SdkConstants.VALUE_N_DP;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.api.DrawingStyle;
import com.android.ide.common.api.DropFeedback;
import com.android.ide.common.api.IDragElement;
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.INodeHandler;
import com.android.ide.common.api.IViewRule;
import com.android.ide.common.api.Point;
import com.android.ide.common.api.Rect;
import com.android.ide.common.api.SegmentType;
import com.android.utils.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* An {@link IViewRule} for android.widget.AbsoluteLayout and all its derived
* classes.
*/
public class AbsoluteLayoutRule extends BaseLayoutRule {
@Override
public List<String> getSelectionHint(@NonNull INode parentNode, @NonNull INode childNode) {
List<String> infos = new ArrayList<String>(2);
infos.add("AbsoluteLayout is deprecated.");
infos.add("Use other layouts instead.");
return infos;
}
// ==== Drag'n'drop support ====
// The AbsoluteLayout accepts any drag'n'drop anywhere on its surface.
@Override
public DropFeedback onDropEnter(@NonNull INode targetNode, @Nullable Object targetView,
final @Nullable IDragElement[] elements) {
if (elements.length == 0) {
return null;
}
DropFeedback df = new DropFeedback(null, new IFeedbackPainter() {
@Override
public void paint(@NonNull IGraphics gc, @NonNull INode node,
@NonNull DropFeedback feedback) {
// Paint callback for the AbsoluteLayout.
// This is called by the canvas when a draw is needed.
drawFeedback(gc, node, elements, feedback);
}
});
df.errorMessage = "AbsoluteLayout is deprecated.";
return df;
}
void drawFeedback(
IGraphics gc,
INode targetNode,
IDragElement[] elements,
DropFeedback feedback) {
Rect b = targetNode.getBounds();
if (!b.isValid()) {
return;
}
// Highlight the receiver
gc.useStyle(DrawingStyle.DROP_RECIPIENT);
gc.drawRect(b);
// Get the drop point
Point p = (Point) feedback.userData;
if (p == null) {
return;
}
int x = p.x;
int y = p.y;
Rect be = elements[0].getBounds();
if (be.isValid()) {
// At least the first element has a bound. Draw rectangles
// for all dropped elements with valid bounds, offset at
// the drop point.
int offsetX = x - be.x + (feedback.dragBounds != null ? feedback.dragBounds.x : 0);
int offsetY = y - be.y + (feedback.dragBounds != null ? feedback.dragBounds.y : 0);
gc.useStyle(DrawingStyle.DROP_PREVIEW);
for (IDragElement element : elements) {
drawElement(gc, element, offsetX, offsetY);
}
} else {
// We don't have bounds for new elements. In this case
// just draw cross hairs to the drop point.
gc.useStyle(DrawingStyle.GUIDELINE);
gc.drawLine(x, b.y, x, b.y + b.h);
gc.drawLine(b.x, y, b.x + b.w, y);
// Use preview lines to indicate the bottom quadrant as well (to
// indicate that you are looking at the top left position of the
// drop, not the center for example)
gc.useStyle(DrawingStyle.DROP_PREVIEW);
gc.drawLine(x, y, b.x + b.w, y);
gc.drawLine(x, y, x, b.y + b.h);
}
}
@Override
public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements,
@Nullable DropFeedback feedback, @NonNull Point p) {
// Update the data used by the DropFeedback.paintCallback above.
feedback.userData = p;
feedback.requestPaint = true;
return feedback;
}
@Override
public void onDropLeave(@NonNull INode targetNode, @NonNull IDragElement[] elements,
@Nullable DropFeedback feedback) {
// Nothing to do.
}
@Override
public void onDropped(final @NonNull INode targetNode, final @NonNull IDragElement[] elements,
final @Nullable DropFeedback feedback, final @NonNull Point p) {
final Rect b = targetNode.getBounds();
if (!b.isValid()) {
return;
}
// Collect IDs from dropped elements and remap them to new IDs
// if this is a copy or from a different canvas.
final Map<String, Pair<String, String>> idMap = getDropIdMap(targetNode, elements,
feedback.isCopy || !feedback.sameCanvas);
targetNode.editXml("Add elements to AbsoluteLayout", new INodeHandler() {
@Override
public void handle(@NonNull INode node) {
boolean first = true;
Point offset = null;
// Now write the new elements.
for (IDragElement element : elements) {
String fqcn = element.getFqcn();
Rect be = element.getBounds();
INode newChild = targetNode.appendChild(fqcn);
// Copy all the attributes, modifying them as needed.
addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER);
int deltaX = (feedback.dragBounds != null ? feedback.dragBounds.x : 0);
int deltaY = (feedback.dragBounds != null ? feedback.dragBounds.y : 0);
int x = p.x - b.x + deltaX;
int y = p.y - b.y + deltaY;
if (first) {
first = false;
if (be.isValid()) {
offset = new Point(x - be.x, y - be.y);
}
} else if (offset != null && be.isValid()) {
x = offset.x + be.x;
y = offset.y + be.y;
} else {
x += 10;
y += be.isValid() ? be.h : 10;
}
double scale = feedback.dipScale;
if (scale != 1.0) {
x *= scale;
y *= scale;
}
newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_X,
String.format(VALUE_N_DP, x));
newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_Y,
String.format(VALUE_N_DP, y));
addInnerElements(newChild, element, idMap);
}
}
});
}
/**
* {@inheritDoc}
* <p>
* Overridden in this layout in order to let the top left coordinate be affected by
* the resize operation too. In other words, dragging the top left corner to resize a
* widget will not only change the size of the widget, it will also move it (though in
* this case, the bottom right corner will stay fixed).
*/
@Override
protected void setNewSizeBounds(ResizeState resizeState, INode node, INode layout,
Rect previousBounds, Rect newBounds, SegmentType horizontalEdge,
SegmentType verticalEdge) {
super.setNewSizeBounds(resizeState, node, layout, previousBounds, newBounds,
horizontalEdge, verticalEdge);
if (verticalEdge != null && newBounds.x != previousBounds.x) {
node.setAttribute(ANDROID_URI, ATTR_LAYOUT_X,
String.format(VALUE_N_DP,
mRulesEngine.pxToDp(newBounds.x - node.getParent().getBounds().x)));
}
if (horizontalEdge != null && newBounds.y != previousBounds.y) {
node.setAttribute(ANDROID_URI, ATTR_LAYOUT_Y,
String.format(VALUE_N_DP,
mRulesEngine.pxToDp(newBounds.y - node.getParent().getBounds().y)));
}
}
@Override
protected String getResizeUpdateMessage(ResizeState resizeState, INode child, INode parent,
Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) {
Rect parentBounds = parent.getBounds();
if (horizontalEdge == SegmentType.BOTTOM && verticalEdge == SegmentType.RIGHT) {
return super.getResizeUpdateMessage(resizeState, child, parent, newBounds,
horizontalEdge, verticalEdge);
}
return String.format("x=%d, y=%d\nwidth=%s, height=%s",
mRulesEngine.pxToDp(newBounds.x - parentBounds.x),
mRulesEngine.pxToDp(newBounds.y - parentBounds.y),
resizeState.getWidthAttribute(), resizeState.getHeightAttribute());
}
}