/* * 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()); } }