Home | History | Annotate | Download | only in layout
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.common.layout;
     18 
     19 import static com.android.SdkConstants.ANDROID_URI;
     20 import static com.android.SdkConstants.ATTR_LAYOUT_X;
     21 import static com.android.SdkConstants.ATTR_LAYOUT_Y;
     22 import static com.android.SdkConstants.VALUE_N_DP;
     23 
     24 import com.android.annotations.NonNull;
     25 import com.android.annotations.Nullable;
     26 import com.android.ide.common.api.DrawingStyle;
     27 import com.android.ide.common.api.DropFeedback;
     28 import com.android.ide.common.api.IDragElement;
     29 import com.android.ide.common.api.IFeedbackPainter;
     30 import com.android.ide.common.api.IGraphics;
     31 import com.android.ide.common.api.INode;
     32 import com.android.ide.common.api.INodeHandler;
     33 import com.android.ide.common.api.IViewRule;
     34 import com.android.ide.common.api.Point;
     35 import com.android.ide.common.api.Rect;
     36 import com.android.ide.common.api.SegmentType;
     37 import com.android.utils.Pair;
     38 
     39 import java.util.ArrayList;
     40 import java.util.List;
     41 import java.util.Map;
     42 
     43 /**
     44  * An {@link IViewRule} for android.widget.AbsoluteLayout and all its derived
     45  * classes.
     46  */
     47 public class AbsoluteLayoutRule extends BaseLayoutRule {
     48 
     49     @Override
     50     public List<String> getSelectionHint(@NonNull INode parentNode, @NonNull INode childNode) {
     51         List<String> infos = new ArrayList<String>(2);
     52         infos.add("AbsoluteLayout is deprecated.");
     53         infos.add("Use other layouts instead.");
     54         return infos;
     55     }
     56 
     57     // ==== Drag'n'drop support ====
     58     // The AbsoluteLayout accepts any drag'n'drop anywhere on its surface.
     59 
     60     @Override
     61     public DropFeedback onDropEnter(@NonNull INode targetNode, @Nullable Object targetView,
     62             final @Nullable IDragElement[] elements) {
     63 
     64         if (elements.length == 0) {
     65             return null;
     66         }
     67 
     68         DropFeedback df = new DropFeedback(null, new IFeedbackPainter() {
     69             @Override
     70             public void paint(@NonNull IGraphics gc, @NonNull INode node,
     71                     @NonNull DropFeedback feedback) {
     72                 // Paint callback for the AbsoluteLayout.
     73                 // This is called by the canvas when a draw is needed.
     74                 drawFeedback(gc, node, elements, feedback);
     75             }
     76         });
     77         df.errorMessage = "AbsoluteLayout is deprecated.";
     78         return df;
     79     }
     80 
     81     void drawFeedback(
     82             IGraphics gc,
     83             INode targetNode,
     84             IDragElement[] elements,
     85             DropFeedback feedback) {
     86         Rect b = targetNode.getBounds();
     87         if (!b.isValid()) {
     88             return;
     89         }
     90 
     91         // Highlight the receiver
     92         gc.useStyle(DrawingStyle.DROP_RECIPIENT);
     93         gc.drawRect(b);
     94 
     95         // Get the drop point
     96         Point p = (Point) feedback.userData;
     97 
     98         if (p == null) {
     99             return;
    100         }
    101 
    102         int x = p.x;
    103         int y = p.y;
    104 
    105         Rect be = elements[0].getBounds();
    106 
    107         if (be.isValid()) {
    108             // At least the first element has a bound. Draw rectangles
    109             // for all dropped elements with valid bounds, offset at
    110             // the drop point.
    111             int offsetX = x - be.x + (feedback.dragBounds != null ? feedback.dragBounds.x : 0);
    112             int offsetY = y - be.y + (feedback.dragBounds != null ? feedback.dragBounds.y : 0);
    113             gc.useStyle(DrawingStyle.DROP_PREVIEW);
    114             for (IDragElement element : elements) {
    115                 drawElement(gc, element, offsetX, offsetY);
    116             }
    117         } else {
    118             // We don't have bounds for new elements. In this case
    119             // just draw cross hairs to the drop point.
    120             gc.useStyle(DrawingStyle.GUIDELINE);
    121             gc.drawLine(x, b.y, x, b.y + b.h);
    122             gc.drawLine(b.x, y, b.x + b.w, y);
    123 
    124             // Use preview lines to indicate the bottom quadrant as well (to
    125             // indicate that you are looking at the top left position of the
    126             // drop, not the center for example)
    127             gc.useStyle(DrawingStyle.DROP_PREVIEW);
    128             gc.drawLine(x, y, b.x + b.w, y);
    129             gc.drawLine(x, y, x, b.y + b.h);
    130         }
    131     }
    132 
    133     @Override
    134     public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements,
    135             @Nullable DropFeedback feedback, @NonNull Point p) {
    136         // Update the data used by the DropFeedback.paintCallback above.
    137         feedback.userData = p;
    138         feedback.requestPaint = true;
    139 
    140         return feedback;
    141     }
    142 
    143     @Override
    144     public void onDropLeave(@NonNull INode targetNode, @NonNull IDragElement[] elements,
    145             @Nullable DropFeedback feedback) {
    146         // Nothing to do.
    147     }
    148 
    149     @Override
    150     public void onDropped(final @NonNull INode targetNode, final @NonNull IDragElement[] elements,
    151             final @Nullable DropFeedback feedback, final @NonNull Point p) {
    152 
    153         final Rect b = targetNode.getBounds();
    154         if (!b.isValid()) {
    155             return;
    156         }
    157 
    158         // Collect IDs from dropped elements and remap them to new IDs
    159         // if this is a copy or from a different canvas.
    160         final Map<String, Pair<String, String>> idMap = getDropIdMap(targetNode, elements,
    161                 feedback.isCopy || !feedback.sameCanvas);
    162 
    163         targetNode.editXml("Add elements to AbsoluteLayout", new INodeHandler() {
    164             @Override
    165             public void handle(@NonNull INode node) {
    166                 boolean first = true;
    167                 Point offset = null;
    168 
    169                 // Now write the new elements.
    170                 for (IDragElement element : elements) {
    171                     String fqcn = element.getFqcn();
    172                     Rect be = element.getBounds();
    173 
    174                     INode newChild = targetNode.appendChild(fqcn);
    175 
    176                     // Copy all the attributes, modifying them as needed.
    177                     addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER);
    178 
    179                     int deltaX = (feedback.dragBounds != null ? feedback.dragBounds.x : 0);
    180                     int deltaY = (feedback.dragBounds != null ? feedback.dragBounds.y : 0);
    181 
    182                     int x = p.x - b.x + deltaX;
    183                     int y = p.y - b.y + deltaY;
    184 
    185                     if (first) {
    186                         first = false;
    187                         if (be.isValid()) {
    188                             offset = new Point(x - be.x, y - be.y);
    189                         }
    190                     } else if (offset != null && be.isValid()) {
    191                         x = offset.x + be.x;
    192                         y = offset.y + be.y;
    193                     } else {
    194                         x += 10;
    195                         y += be.isValid() ? be.h : 10;
    196                     }
    197 
    198                     double scale = feedback.dipScale;
    199                     if (scale != 1.0) {
    200                         x *= scale;
    201                         y *= scale;
    202                     }
    203 
    204                     newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_X,
    205                             String.format(VALUE_N_DP, x));
    206                     newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_Y,
    207                             String.format(VALUE_N_DP, y));
    208 
    209                     addInnerElements(newChild, element, idMap);
    210                 }
    211             }
    212         });
    213     }
    214 
    215     /**
    216      * {@inheritDoc}
    217      * <p>
    218      * Overridden in this layout in order to let the top left coordinate be affected by
    219      * the resize operation too. In other words, dragging the top left corner to resize a
    220      * widget will not only change the size of the widget, it will also move it (though in
    221      * this case, the bottom right corner will stay fixed).
    222      */
    223     @Override
    224     protected void setNewSizeBounds(ResizeState resizeState, INode node, INode layout,
    225             Rect previousBounds, Rect newBounds, SegmentType horizontalEdge,
    226             SegmentType verticalEdge) {
    227         super.setNewSizeBounds(resizeState, node, layout, previousBounds, newBounds,
    228                 horizontalEdge, verticalEdge);
    229         if (verticalEdge != null && newBounds.x != previousBounds.x) {
    230             node.setAttribute(ANDROID_URI, ATTR_LAYOUT_X,
    231                     String.format(VALUE_N_DP,
    232                             mRulesEngine.pxToDp(newBounds.x - node.getParent().getBounds().x)));
    233         }
    234         if (horizontalEdge != null && newBounds.y != previousBounds.y) {
    235             node.setAttribute(ANDROID_URI, ATTR_LAYOUT_Y,
    236                     String.format(VALUE_N_DP,
    237                             mRulesEngine.pxToDp(newBounds.y - node.getParent().getBounds().y)));
    238         }
    239     }
    240 
    241     @Override
    242     protected String getResizeUpdateMessage(ResizeState resizeState, INode child, INode parent,
    243             Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) {
    244         Rect parentBounds = parent.getBounds();
    245         if (horizontalEdge == SegmentType.BOTTOM && verticalEdge == SegmentType.RIGHT) {
    246             return super.getResizeUpdateMessage(resizeState, child, parent, newBounds,
    247                     horizontalEdge, verticalEdge);
    248         }
    249         return String.format("x=%d, y=%d\nwidth=%s, height=%s",
    250                 mRulesEngine.pxToDp(newBounds.x - parentBounds.x),
    251                 mRulesEngine.pxToDp(newBounds.y - parentBounds.y),
    252                 resizeState.getWidthAttribute(), resizeState.getHeightAttribute());
    253     }
    254 }
    255