Home | History | Annotate | Download | only in relative
      1 /*
      2  * Copyright (C) 2011 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 package com.android.ide.common.layout.relative;
     17 
     18 import static com.android.ide.common.api.MarginType.NO_MARGIN;
     19 import static com.android.ide.common.api.SegmentType.BASELINE;
     20 import static com.android.ide.common.api.SegmentType.BOTTOM;
     21 import static com.android.ide.common.api.SegmentType.CENTER_HORIZONTAL;
     22 import static com.android.ide.common.api.SegmentType.CENTER_VERTICAL;
     23 import static com.android.ide.common.api.SegmentType.LEFT;
     24 import static com.android.ide.common.api.SegmentType.RIGHT;
     25 import static com.android.ide.common.api.SegmentType.TOP;
     26 import static com.android.SdkConstants.ATTR_ID;
     27 
     28 import static java.lang.Math.abs;
     29 
     30 import com.android.SdkConstants;
     31 import static com.android.SdkConstants.ANDROID_URI;
     32 import com.android.ide.common.api.DropFeedback;
     33 import com.android.ide.common.api.IClientRulesEngine;
     34 import com.android.ide.common.api.INode;
     35 import com.android.ide.common.api.Rect;
     36 import com.android.ide.common.api.Segment;
     37 import com.android.ide.common.api.SegmentType;
     38 import com.android.ide.common.layout.BaseLayoutRule;
     39 
     40 import java.util.Collections;
     41 import java.util.Set;
     42 
     43 /**
     44  * A {@link ResizeHandler} is a {@link GuidelineHandler} which handles resizing of individual
     45  * edges in a RelativeLayout.
     46  */
     47 public class ResizeHandler extends GuidelineHandler {
     48     private final SegmentType mHorizontalEdgeType;
     49     private final SegmentType mVerticalEdgeType;
     50 
     51     /**
     52      * Creates a new {@link ResizeHandler}
     53      *
     54      * @param layout the layout containing the resized node
     55      * @param resized the node being resized
     56      * @param rulesEngine the applicable {@link IClientRulesEngine}
     57      * @param horizontalEdgeType the type of horizontal edge being resized, or null
     58      * @param verticalEdgeType the type of vertical edge being resized, or null
     59      */
     60     public ResizeHandler(INode layout, INode resized,
     61             IClientRulesEngine rulesEngine,
     62             SegmentType horizontalEdgeType, SegmentType verticalEdgeType) {
     63         super(layout, rulesEngine);
     64 
     65         assert horizontalEdgeType != null || verticalEdgeType != null;
     66         assert horizontalEdgeType != BASELINE && verticalEdgeType != BASELINE;
     67         assert horizontalEdgeType != CENTER_HORIZONTAL && verticalEdgeType != CENTER_HORIZONTAL;
     68         assert horizontalEdgeType != CENTER_VERTICAL && verticalEdgeType != CENTER_VERTICAL;
     69 
     70         mHorizontalEdgeType = horizontalEdgeType;
     71         mVerticalEdgeType = verticalEdgeType;
     72 
     73         Set<INode> nodes = Collections.singleton(resized);
     74         mDraggedNodes = nodes;
     75 
     76         mHorizontalDeps = mDependencyGraph.dependsOn(nodes, false /* vertical */);
     77         mVerticalDeps = mDependencyGraph.dependsOn(nodes, true /* vertical */);
     78 
     79         if (horizontalEdgeType != null) {
     80             if (horizontalEdgeType == TOP) {
     81                 mMoveTop = true;
     82             } else if (horizontalEdgeType == BOTTOM) {
     83                 mMoveBottom = true;
     84             }
     85         }
     86         if (verticalEdgeType != null) {
     87             if (verticalEdgeType == LEFT) {
     88                 mMoveLeft = true;
     89             } else if (verticalEdgeType == RIGHT) {
     90                 mMoveRight = true;
     91             }
     92         }
     93 
     94         for (INode child : layout.getChildren()) {
     95             if (child != resized) {
     96                 String id = child.getStringAttr(ANDROID_URI, ATTR_ID);
     97                 addBounds(child, id,
     98                         !mHorizontalDeps.contains(child),
     99                         !mVerticalDeps.contains(child));
    100             }
    101         }
    102 
    103         addBounds(layout, layout.getStringAttr(ANDROID_URI, ATTR_ID), true, true);
    104     }
    105 
    106     @Override
    107     protected void snapVertical(Segment vEdge, int x, Rect newBounds) {
    108         int maxDistance = BaseLayoutRule.getMaxMatchDistance();
    109         if (vEdge.edgeType == LEFT) {
    110             int margin = mSnap ? 0 : abs(newBounds.x - x);
    111             if (margin > maxDistance) {
    112                 mLeftMargin = margin;
    113             } else {
    114                 newBounds.w += newBounds.x - x;
    115                 newBounds.x = x;
    116             }
    117         } else if (vEdge.edgeType == RIGHT) {
    118             int margin = mSnap ? 0 : abs(newBounds.x - (x - newBounds.w));
    119             if (margin > maxDistance) {
    120                 mRightMargin = margin;
    121             } else {
    122                 newBounds.w = x - newBounds.x;
    123             }
    124         } else {
    125             assert false : vEdge;
    126         }
    127     }
    128 
    129     @Override
    130     protected void snapHorizontal(Segment hEdge, int y, Rect newBounds) {
    131         int maxDistance = BaseLayoutRule.getMaxMatchDistance();
    132         if (hEdge.edgeType == TOP) {
    133             int margin = mSnap ? 0 : abs(newBounds.y - y);
    134             if (margin > maxDistance) {
    135                 mTopMargin = margin;
    136             } else {
    137                 newBounds.h += newBounds.y - y;
    138                 newBounds.y = y;
    139             }
    140         } else if (hEdge.edgeType == BOTTOM) {
    141             int margin = mSnap ? 0 : abs(newBounds.y - (y - newBounds.h));
    142             if (margin > maxDistance) {
    143                 mBottomMargin = margin;
    144             } else {
    145                 newBounds.h = y - newBounds.y;
    146             }
    147         } else {
    148             assert false : hEdge;
    149         }
    150     }
    151 
    152     @Override
    153     protected boolean isEdgeTypeCompatible(SegmentType edge, SegmentType dragged, int delta) {
    154         boolean compatible = super.isEdgeTypeCompatible(edge, dragged, delta);
    155 
    156         // When resizing and not snapping (e.g. using margins to pick a specific pixel
    157         // width) we cannot use -negative- margins to jump back to a closer edge; we
    158         // must always use positive margins, so mark closer edges that result in a negative
    159         // margin as not compatible.
    160         if (compatible && !mSnap) {
    161             switch (dragged) {
    162                 case LEFT:
    163                 case TOP:
    164                     return delta <= 0;
    165                 default:
    166                     return delta >= 0;
    167             }
    168         }
    169 
    170         return compatible;
    171     }
    172 
    173     /**
    174      * Updates the handler for the given mouse resize
    175      *
    176      * @param feedback the feedback handler
    177      * @param child the node being resized
    178      * @param newBounds the new bounds of the resize rectangle
    179      * @param modifierMask the keyboard modifiers pressed during the drag
    180      */
    181     public void updateResize(DropFeedback feedback, INode child, Rect newBounds,
    182             int modifierMask) {
    183         mSnap = (modifierMask & DropFeedback.MODIFIER2) == 0;
    184         mBounds = newBounds;
    185         clearSuggestions();
    186 
    187         Rect b = newBounds;
    188         Segment hEdge = null;
    189         Segment vEdge = null;
    190         String childId = child.getStringAttr(ANDROID_URI, ATTR_ID);
    191 
    192         // TODO: MarginType=NO_MARGIN may not be right. Consider resizing a widget
    193         //   that has margins and how that should be handled.
    194 
    195         if (mHorizontalEdgeType == TOP) {
    196             hEdge = new Segment(b.y, b.x, b.x2(), child, childId, mHorizontalEdgeType, NO_MARGIN);
    197         } else if (mHorizontalEdgeType == BOTTOM) {
    198             hEdge = new Segment(b.y2(), b.x, b.x2(), child, childId, mHorizontalEdgeType,
    199                     NO_MARGIN);
    200         } else {
    201             assert mHorizontalEdgeType == null;
    202         }
    203 
    204         if (mVerticalEdgeType == LEFT) {
    205             vEdge = new Segment(b.x, b.y, b.y2(), child, childId, mVerticalEdgeType, NO_MARGIN);
    206         } else if (mVerticalEdgeType == RIGHT) {
    207             vEdge = new Segment(b.x2(), b.y, b.y2(), child, childId, mVerticalEdgeType, NO_MARGIN);
    208         } else {
    209             assert mVerticalEdgeType == null;
    210         }
    211 
    212         mTopMargin = mBottomMargin = mLeftMargin = mRightMargin = 0;
    213 
    214         if (hEdge != null && mHorizontalEdges.size() > 0) {
    215             // Compute horizontal matches
    216             mHorizontalSuggestions = findClosest(hEdge, mHorizontalEdges);
    217 
    218             Match match = pickBestMatch(mHorizontalSuggestions);
    219             if (match != null
    220                     && (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) {
    221                 if (mHorizontalDeps.contains(match.edge.node)) {
    222                     match.cycle = true;
    223                 }
    224 
    225                 snapHorizontal(hEdge, match.edge.at, newBounds);
    226 
    227                 if (hEdge.edgeType == TOP) {
    228                     mCurrentTopMatch = match;
    229                 } else if (hEdge.edgeType == BOTTOM) {
    230                     mCurrentBottomMatch = match;
    231                 } else {
    232                     assert hEdge.edgeType == CENTER_HORIZONTAL
    233                             || hEdge.edgeType == BASELINE : hEdge;
    234                     mCurrentTopMatch = match;
    235                 }
    236             }
    237         }
    238 
    239         if (vEdge != null && mVerticalEdges.size() > 0) {
    240             mVerticalSuggestions = findClosest(vEdge, mVerticalEdges);
    241 
    242             Match match = pickBestMatch(mVerticalSuggestions);
    243             if (match != null
    244                     && (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) {
    245                 if (mVerticalDeps.contains(match.edge.node)) {
    246                     match.cycle = true;
    247                 }
    248 
    249                 // Snap
    250                 snapVertical(vEdge, match.edge.at, newBounds);
    251 
    252                 if (vEdge.edgeType == LEFT) {
    253                     mCurrentLeftMatch = match;
    254                 } else if (vEdge.edgeType == RIGHT) {
    255                     mCurrentRightMatch = match;
    256                 } else {
    257                     assert vEdge.edgeType == CENTER_VERTICAL;
    258                     mCurrentLeftMatch = match;
    259                 }
    260             }
    261         }
    262 
    263         checkCycles(feedback);
    264     }
    265 }
    266