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