diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java | 878 |
1 files changed, 0 insertions, 878 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java deleted file mode 100644 index df2c8f473..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java +++ /dev/null @@ -1,878 +0,0 @@ -/* - * 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_ID; -import static com.android.SdkConstants.ATTR_LAYOUT_ABOVE; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BASELINE; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BOTTOM; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_TOP; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING; -import static com.android.SdkConstants.ATTR_LAYOUT_BELOW; -import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_HORIZONTAL; -import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_IN_PARENT; -import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_VERTICAL; -import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN; -import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN_SPAN; -import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY; -import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_BOTTOM; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_TOP; -import static com.android.SdkConstants.ATTR_LAYOUT_ROW; -import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_LAYOUT_X; -import static com.android.SdkConstants.ATTR_LAYOUT_Y; -import static com.android.SdkConstants.VALUE_FILL_PARENT; -import static com.android.SdkConstants.VALUE_MATCH_PARENT; -import static com.android.SdkConstants.VALUE_WRAP_CONTENT; - -import com.android.SdkConstants; -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.IAttributeInfo; -import com.android.ide.common.api.IClientRulesEngine; -import com.android.ide.common.api.IDragElement; -import com.android.ide.common.api.IDragElement.IDragAttribute; -import com.android.ide.common.api.IFeedbackPainter; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.IMenuCallback; -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.MarginType; -import com.android.ide.common.api.Point; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.RuleAction; -import com.android.ide.common.api.RuleAction.ChoiceProvider; -import com.android.ide.common.api.Segment; -import com.android.ide.common.api.SegmentType; -import com.android.utils.Pair; - -import java.net.URL; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * A {@link IViewRule} for all layouts. - */ -public class BaseLayoutRule extends BaseViewRule { - private static final String ACTION_FILL_WIDTH = "_fillW"; //$NON-NLS-1$ - private static final String ACTION_FILL_HEIGHT = "_fillH"; //$NON-NLS-1$ - private static final String ACTION_MARGIN = "_margin"; //$NON-NLS-1$ - private static final URL ICON_MARGINS = - BaseLayoutRule.class.getResource("margins.png"); //$NON-NLS-1$ - private static final URL ICON_GRAVITY = - BaseLayoutRule.class.getResource("gravity.png"); //$NON-NLS-1$ - private static final URL ICON_FILL_WIDTH = - BaseLayoutRule.class.getResource("fillwidth.png"); //$NON-NLS-1$ - private static final URL ICON_FILL_HEIGHT = - BaseLayoutRule.class.getResource("fillheight.png"); //$NON-NLS-1$ - - // ==== Layout Actions support ==== - - // The Margin layout parameters are available for LinearLayout, FrameLayout, RelativeLayout, - // and their subclasses. - protected final RuleAction createMarginAction(final INode parentNode, - final List<? extends INode> children) { - - final List<? extends INode> targets = children == null || children.size() == 0 ? - Collections.singletonList(parentNode) - : children; - final INode first = targets.get(0); - - IMenuCallback actionCallback = new IMenuCallback() { - @Override - public void action(@NonNull RuleAction action, - @NonNull List<? extends INode> selectedNodes, - final @Nullable String valueId, - final @Nullable Boolean newValue) { - parentNode.editXml("Change Margins", new INodeHandler() { - @Override - public void handle(@NonNull INode n) { - String uri = ANDROID_URI; - String all = first.getStringAttr(uri, ATTR_LAYOUT_MARGIN); - String left = first.getStringAttr(uri, ATTR_LAYOUT_MARGIN_LEFT); - String right = first.getStringAttr(uri, ATTR_LAYOUT_MARGIN_RIGHT); - String top = first.getStringAttr(uri, ATTR_LAYOUT_MARGIN_TOP); - String bottom = first.getStringAttr(uri, ATTR_LAYOUT_MARGIN_BOTTOM); - String[] margins = mRulesEngine.displayMarginInput(all, left, - right, top, bottom); - if (margins != null) { - assert margins.length == 5; - for (INode child : targets) { - child.setAttribute(uri, ATTR_LAYOUT_MARGIN, margins[0]); - child.setAttribute(uri, ATTR_LAYOUT_MARGIN_LEFT, margins[1]); - child.setAttribute(uri, ATTR_LAYOUT_MARGIN_RIGHT, margins[2]); - child.setAttribute(uri, ATTR_LAYOUT_MARGIN_TOP, margins[3]); - child.setAttribute(uri, ATTR_LAYOUT_MARGIN_BOTTOM, margins[4]); - } - } - } - }); - } - }; - - return RuleAction.createAction(ACTION_MARGIN, "Change Margins...", actionCallback, - ICON_MARGINS, 40, false); - } - - // Both LinearLayout and RelativeLayout have a gravity (but RelativeLayout applies it - // to the parent whereas for LinearLayout it's on the children) - protected final RuleAction createGravityAction(final List<? extends INode> targets, final - String attributeName) { - if (targets != null && targets.size() > 0) { - final INode first = targets.get(0); - ChoiceProvider provider = new ChoiceProvider() { - @Override - public void addChoices(@NonNull List<String> titles, @NonNull List<URL> iconUrls, - @NonNull List<String> ids) { - IAttributeInfo info = first.getAttributeInfo(ANDROID_URI, attributeName); - if (info != null) { - // Generate list of possible gravity value constants - assert info.getFormats().contains(IAttributeInfo.Format.FLAG); - for (String name : info.getFlagValues()) { - titles.add(getAttributeDisplayName(name)); - ids.add(name); - } - } - } - }; - - return RuleAction.createChoices("_gravity", "Change Gravity", //$NON-NLS-1$ - new PropertyCallback(targets, "Change Gravity", ANDROID_URI, - attributeName), - provider, - first.getStringAttr(ANDROID_URI, attributeName), ICON_GRAVITY, - 43, false); - } - - return null; - } - - @Override - public void addLayoutActions( - @NonNull List<RuleAction> actions, - final @NonNull INode parentNode, - final @NonNull List<? extends INode> children) { - super.addLayoutActions(actions, parentNode, children); - - final List<? extends INode> targets = children == null || children.size() == 0 ? - Collections.singletonList(parentNode) - : children; - final INode first = targets.get(0); - - // Shared action callback - IMenuCallback actionCallback = new IMenuCallback() { - @Override - public void action( - @NonNull RuleAction action, - @NonNull List<? extends INode> selectedNodes, - final @Nullable String valueId, - final @Nullable Boolean newValue) { - final String actionId = action.getId(); - final String undoLabel; - if (actionId.equals(ACTION_FILL_WIDTH)) { - undoLabel = "Change Width Fill"; - } else if (actionId.equals(ACTION_FILL_HEIGHT)) { - undoLabel = "Change Height Fill"; - } else { - return; - } - parentNode.editXml(undoLabel, new INodeHandler() { - @Override - public void handle(@NonNull INode n) { - String attribute = actionId.equals(ACTION_FILL_WIDTH) - ? ATTR_LAYOUT_WIDTH : ATTR_LAYOUT_HEIGHT; - String value; - if (newValue) { - if (supportsMatchParent()) { - value = VALUE_MATCH_PARENT; - } else { - value = VALUE_FILL_PARENT; - } - } else { - value = VALUE_WRAP_CONTENT; - } - for (INode child : targets) { - child.setAttribute(ANDROID_URI, attribute, value); - } - } - }); - } - }; - - actions.add(RuleAction.createToggle(ACTION_FILL_WIDTH, "Toggle Fill Width", - isFilled(first, ATTR_LAYOUT_WIDTH), actionCallback, ICON_FILL_WIDTH, 10, false)); - actions.add(RuleAction.createToggle(ACTION_FILL_HEIGHT, "Toggle Fill Height", - isFilled(first, ATTR_LAYOUT_HEIGHT), actionCallback, ICON_FILL_HEIGHT, 20, false)); - } - - // ==== Paste support ==== - - /** - * The default behavior for pasting in a layout is to simulate a drop in the - * top-left corner of the view. - * <p/> - * Note that we explicitly do not call super() here -- the BaseViewRule.onPaste handler - * will call onPasteBeforeChild() instead. - * <p/> - * Derived layouts should override this behavior if not appropriate. - */ - @Override - public void onPaste(@NonNull INode targetNode, @Nullable Object targetView, - @NonNull IDragElement[] elements) { - DropFeedback feedback = onDropEnter(targetNode, targetView, elements); - if (feedback != null) { - Point p = targetNode.getBounds().getTopLeft(); - feedback = onDropMove(targetNode, elements, feedback, p); - if (feedback != null) { - onDropLeave(targetNode, elements, feedback); - onDropped(targetNode, elements, feedback, p); - } - } - } - - /** - * The default behavior for pasting in a layout with a specific child target - * is to simulate a drop right above the top left of the given child target. - * <p/> - * This method is invoked by BaseView when onPaste() is called -- - * views don't generally accept children and instead use the target node as - * a hint to paste "before" it. - * - * @param parentNode the parent node we're pasting into - * @param parentView the view object for the parent layout, or null - * @param targetNode the first selected node - * @param elements the elements being pasted - */ - public void onPasteBeforeChild(INode parentNode, Object parentView, INode targetNode, - IDragElement[] elements) { - DropFeedback feedback = onDropEnter(parentNode, parentView, elements); - if (feedback != null) { - Point parentP = parentNode.getBounds().getTopLeft(); - Point targetP = targetNode.getBounds().getTopLeft(); - if (parentP.y < targetP.y) { - targetP.y -= 1; - } - - feedback = onDropMove(parentNode, elements, feedback, targetP); - if (feedback != null) { - onDropLeave(parentNode, elements, feedback); - onDropped(parentNode, elements, feedback, targetP); - } - } - } - - // ==== Utility methods used by derived layouts ==== - - /** - * Draws the bounds of the given elements and all its children elements in the canvas - * with the specified offset. - * - * @param gc the graphics context - * @param element the element to be drawn - * @param offsetX a horizontal delta to add to the current bounds of the element when - * drawing it - * @param offsetY a vertical delta to add to the current bounds of the element when - * drawing it - */ - public void drawElement(IGraphics gc, IDragElement element, int offsetX, int offsetY) { - Rect b = element.getBounds(); - if (b.isValid()) { - gc.drawRect(b.x + offsetX, b.y + offsetY, b.x + offsetX + b.w, b.y + offsetY + b.h); - } - - for (IDragElement inner : element.getInnerElements()) { - drawElement(gc, inner, offsetX, offsetY); - } - } - - /** - * Collect all the "android:id" IDs from the dropped elements. When moving - * objects within the same canvas, that's all there is to do. However if the - * objects are moved to a different canvas or are copied then set - * createNewIds to true to find the existing IDs under targetNode and create - * a map with new non-conflicting unique IDs as needed. Returns a map String - * old-id => tuple (String new-id, String fqcn) where fqcn is the FQCN of - * the element. - */ - protected static Map<String, Pair<String, String>> getDropIdMap(INode targetNode, - IDragElement[] elements, boolean createNewIds) { - Map<String, Pair<String, String>> idMap = new HashMap<String, Pair<String, String>>(); - - if (createNewIds) { - collectIds(idMap, elements); - // Need to remap ids if necessary - idMap = remapIds(targetNode, idMap); - } - - return idMap; - } - - /** - * Fills idMap with a map String id => tuple (String id, String fqcn) where - * fqcn is the FQCN of the element (in case we want to generate new IDs - * based on the element type.) - * - * @see #getDropIdMap - */ - protected static Map<String, Pair<String, String>> collectIds( - Map<String, Pair<String, String>> idMap, - IDragElement[] elements) { - for (IDragElement element : elements) { - IDragAttribute attr = element.getAttribute(ANDROID_URI, ATTR_ID); - if (attr != null) { - String id = attr.getValue(); - if (id != null && id.length() > 0) { - idMap.put(id, Pair.of(id, element.getFqcn())); - } - } - - collectIds(idMap, element.getInnerElements()); - } - - return idMap; - } - - /** - * Used by #getDropIdMap to find new IDs in case of conflict. - */ - protected static Map<String, Pair<String, String>> remapIds(INode node, - Map<String, Pair<String, String>> idMap) { - // Visit the document to get a list of existing ids - Set<String> existingIdSet = new HashSet<String>(); - collectExistingIds(node.getRoot(), existingIdSet); - - Map<String, Pair<String, String>> new_map = new HashMap<String, Pair<String, String>>(); - for (Map.Entry<String, Pair<String, String>> entry : idMap.entrySet()) { - String key = entry.getKey(); - Pair<String, String> value = entry.getValue(); - - String id = normalizeId(key); - - if (!existingIdSet.contains(id)) { - // Not a conflict. Use as-is. - new_map.put(key, value); - if (!key.equals(id)) { - new_map.put(id, value); - } - } else { - // There is a conflict. Get a new id. - String new_id = findNewId(value.getSecond(), existingIdSet); - value = Pair.of(new_id, value.getSecond()); - new_map.put(id, value); - new_map.put(id.replaceFirst("@\\+", "@"), value); //$NON-NLS-1$ //$NON-NLS-2$ - } - } - - return new_map; - } - - /** - * Used by #remapIds to find a new ID for a conflicting element. - */ - protected static String findNewId(String fqcn, Set<String> existingIdSet) { - // Get the last component of the FQCN (e.g. "android.view.Button" => - // "Button") - String name = fqcn.substring(fqcn.lastIndexOf('.') + 1); - - for (int i = 1; i < 1000000; i++) { - String id = String.format("@+id/%s%02d", name, i); //$NON-NLS-1$ - if (!existingIdSet.contains(id)) { - existingIdSet.add(id); - return id; - } - } - - // We'll never reach here. - return null; - } - - /** - * Used by #getDropIdMap to find existing IDs recursively. - */ - protected static void collectExistingIds(INode root, Set<String> existingIdSet) { - if (root == null) { - return; - } - - String id = root.getStringAttr(ANDROID_URI, ATTR_ID); - if (id != null) { - id = normalizeId(id); - - if (!existingIdSet.contains(id)) { - existingIdSet.add(id); - } - } - - for (INode child : root.getChildren()) { - collectExistingIds(child, existingIdSet); - } - } - - /** - * Transforms @id/name into @+id/name to treat both forms the same way. - */ - protected static String normalizeId(String id) { - if (id.indexOf("@+") == -1) { //$NON-NLS-1$ - id = id.replaceFirst("@", "@+"); //$NON-NLS-1$ //$NON-NLS-2$ - } - return id; - } - - /** - * For use by {@link BaseLayoutRule#addAttributes} A filter should return a - * valid replacement string. - */ - protected static interface AttributeFilter { - String replace(String attributeUri, String attributeName, String attributeValue); - } - - private static final String[] EXCLUDED_ATTRIBUTES = new String[] { - // Common - ATTR_LAYOUT_GRAVITY, - - // from AbsoluteLayout - ATTR_LAYOUT_X, - ATTR_LAYOUT_Y, - - // from RelativeLayout - ATTR_LAYOUT_ABOVE, - ATTR_LAYOUT_BELOW, - ATTR_LAYOUT_TO_LEFT_OF, - ATTR_LAYOUT_TO_RIGHT_OF, - ATTR_LAYOUT_ALIGN_BASELINE, - ATTR_LAYOUT_ALIGN_TOP, - ATTR_LAYOUT_ALIGN_BOTTOM, - ATTR_LAYOUT_ALIGN_LEFT, - ATTR_LAYOUT_ALIGN_RIGHT, - ATTR_LAYOUT_ALIGN_PARENT_TOP, - ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, - ATTR_LAYOUT_ALIGN_PARENT_LEFT, - ATTR_LAYOUT_ALIGN_PARENT_RIGHT, - ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING, - ATTR_LAYOUT_CENTER_HORIZONTAL, - ATTR_LAYOUT_CENTER_IN_PARENT, - ATTR_LAYOUT_CENTER_VERTICAL, - - // From GridLayout - ATTR_LAYOUT_ROW, - ATTR_LAYOUT_ROW_SPAN, - ATTR_LAYOUT_COLUMN, - ATTR_LAYOUT_COLUMN_SPAN - }; - - /** - * Default attribute filter used by the various layouts to filter out some properties - * we don't want to offer. - */ - public static final AttributeFilter DEFAULT_ATTR_FILTER = new AttributeFilter() { - Set<String> mExcludes; - - @Override - public String replace(String uri, String name, String value) { - if (!ANDROID_URI.equals(uri)) { - return value; - } - - if (mExcludes == null) { - mExcludes = new HashSet<String>(EXCLUDED_ATTRIBUTES.length); - mExcludes.addAll(Arrays.asList(EXCLUDED_ATTRIBUTES)); - } - - return mExcludes.contains(name) ? null : value; - } - }; - - /** - * Copies all the attributes from oldElement to newNode. Uses the idMap to - * transform the value of all attributes of Format.REFERENCE. If filter is - * non-null, it's a filter that can rewrite the attribute string. - */ - protected static void addAttributes(INode newNode, IDragElement oldElement, - Map<String, Pair<String, String>> idMap, AttributeFilter filter) { - - for (IDragAttribute attr : oldElement.getAttributes()) { - String uri = attr.getUri(); - String name = attr.getName(); - String value = attr.getValue(); - - IAttributeInfo attrInfo = newNode.getAttributeInfo(uri, name); - if (attrInfo != null) { - if (attrInfo.getFormats().contains(IAttributeInfo.Format.REFERENCE)) { - if (idMap.containsKey(value)) { - value = idMap.get(value).getFirst(); - } - } - } - - if (filter != null) { - value = filter.replace(uri, name, value); - } - if (value != null && value.length() > 0) { - newNode.setAttribute(uri, name, value); - } - } - } - - /** - * Adds all the children elements of oldElement to newNode, recursively. - * Attributes are adjusted by calling addAttributes with idMap as necessary, - * with no closure filter. - */ - protected static void addInnerElements(INode newNode, IDragElement oldElement, - Map<String, Pair<String, String>> idMap) { - - for (IDragElement element : oldElement.getInnerElements()) { - String fqcn = element.getFqcn(); - INode childNode = newNode.appendChild(fqcn); - - addAttributes(childNode, element, idMap, null /* filter */); - addInnerElements(childNode, element, idMap); - } - } - - /** - * Insert the given elements into the given node at the given position - * - * @param targetNode the node to insert into - * @param elements the elements to insert - * @param createNewIds if true, generate new ids when there is a conflict - * @param initialInsertPos index among targetnode's children which to insert the - * children - */ - public static void insertAt(final INode targetNode, final IDragElement[] elements, - final boolean createNewIds, final int initialInsertPos) { - - // 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, - createNewIds); - - targetNode.editXml("Insert Elements", new INodeHandler() { - - @Override - public void handle(@NonNull INode node) { - // Now write the new elements. - int insertPos = initialInsertPos; - for (IDragElement element : elements) { - String fqcn = element.getFqcn(); - - INode newChild = targetNode.insertChildAt(fqcn, insertPos); - - // insertPos==-1 means to insert at the end. Otherwise - // increment the insertion position. - if (insertPos >= 0) { - insertPos++; - } - - // Copy all the attributes, modifying them as needed. - addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER); - addInnerElements(newChild, element, idMap); - } - } - }); - } - - // ---- Resizing ---- - - /** Creates a new {@link ResizeState} object to track resize state */ - protected ResizeState createResizeState(INode layout, Object layoutView, INode node) { - return new ResizeState(this, layout, layoutView, node); - } - - @Override - public DropFeedback onResizeBegin(@NonNull INode child, @NonNull INode parent, - @Nullable SegmentType horizontalEdge, @Nullable SegmentType verticalEdge, - @Nullable Object childView, @Nullable Object parentView) { - ResizeState state = createResizeState(parent, parentView, child); - state.horizontalEdgeType = horizontalEdge; - state.verticalEdgeType = verticalEdge; - - // Compute preferred (wrap_content) size such that we can offer guidelines to - // snap to the preferred size - Map<INode, Rect> sizes = mRulesEngine.measureChildren(parent, - new IClientRulesEngine.AttributeFilter() { - @Override - public String getAttribute(@NonNull INode node, @Nullable String namespace, - @NonNull String localName) { - // Change attributes to wrap_content - if (ATTR_LAYOUT_WIDTH.equals(localName) - && SdkConstants.NS_RESOURCES.equals(namespace)) { - return VALUE_WRAP_CONTENT; - } - if (ATTR_LAYOUT_HEIGHT.equals(localName) - && SdkConstants.NS_RESOURCES.equals(namespace)) { - return VALUE_WRAP_CONTENT; - } - - return null; - } - }); - if (sizes != null) { - state.wrapBounds = sizes.get(child); - } - - return new DropFeedback(state, new IFeedbackPainter() { - @Override - public void paint(@NonNull IGraphics gc, @NonNull INode node, - @NonNull DropFeedback feedback) { - ResizeState resizeState = (ResizeState) feedback.userData; - if (resizeState != null && resizeState.bounds != null) { - paintResizeFeedback(gc, node, resizeState); - } - } - }); - } - - protected void paintResizeFeedback(IGraphics gc, INode node, ResizeState resizeState) { - gc.useStyle(DrawingStyle.RESIZE_PREVIEW); - Rect b = resizeState.bounds; - gc.drawRect(b); - - if (resizeState.horizontalFillSegment != null) { - gc.useStyle(DrawingStyle.GUIDELINE); - Segment s = resizeState.horizontalFillSegment; - gc.drawLine(s.from, s.at, s.to, s.at); - } - if (resizeState.verticalFillSegment != null) { - gc.useStyle(DrawingStyle.GUIDELINE); - Segment s = resizeState.verticalFillSegment; - gc.drawLine(s.at, s.from, s.at, s.to); - } - - if (resizeState.wrapBounds != null) { - gc.useStyle(DrawingStyle.GUIDELINE); - int wrapWidth = resizeState.wrapBounds.w; - int wrapHeight = resizeState.wrapBounds.h; - - // Show the "wrap_content" guideline. - // If we are showing both the wrap_width and wrap_height lines - // then we show at most the rectangle formed by the two lines; - // otherwise we show the entire width of the line - if (resizeState.horizontalEdgeType != null) { - int y = -1; - switch (resizeState.horizontalEdgeType) { - case TOP: - y = b.y + b.h - wrapHeight; - break; - case BOTTOM: - y = b.y + wrapHeight; - break; - default: assert false : resizeState.horizontalEdgeType; - } - if (resizeState.verticalEdgeType != null) { - switch (resizeState.verticalEdgeType) { - case LEFT: - gc.drawLine(b.x + b.w - wrapWidth, y, b.x + b.w, y); - break; - case RIGHT: - gc.drawLine(b.x, y, b.x + wrapWidth, y); - break; - default: assert false : resizeState.verticalEdgeType; - } - } else { - gc.drawLine(b.x, y, b.x + b.w, y); - } - } - if (resizeState.verticalEdgeType != null) { - int x = -1; - switch (resizeState.verticalEdgeType) { - case LEFT: - x = b.x + b.w - wrapWidth; - break; - case RIGHT: - x = b.x + wrapWidth; - break; - default: assert false : resizeState.verticalEdgeType; - } - if (resizeState.horizontalEdgeType != null) { - switch (resizeState.horizontalEdgeType) { - case TOP: - gc.drawLine(x, b.y + b.h - wrapHeight, x, b.y + b.h); - break; - case BOTTOM: - gc.drawLine(x, b.y, x, b.y + wrapHeight); - break; - default: assert false : resizeState.horizontalEdgeType; - } - } else { - gc.drawLine(x, b.y, x, b.y + b.h); - } - } - } - } - - /** - * Returns the maximum number of pixels will be considered a "match" when snapping - * resize or move positions to edges or other constraints - * - * @return the maximum number of pixels to consider for snapping - */ - public static final int getMaxMatchDistance() { - // TODO - make constant once we're happy with the feel - return 20; - } - - @Override - public void onResizeUpdate(@Nullable DropFeedback feedback, @NonNull INode child, - @NonNull INode parent, @NonNull Rect newBounds, int modifierMask) { - ResizeState state = (ResizeState) feedback.userData; - state.bounds = newBounds; - state.modifierMask = modifierMask; - - // Match on wrap bounds - state.wrapWidth = state.wrapHeight = false; - if (state.wrapBounds != null) { - Rect b = state.wrapBounds; - int maxMatchDistance = getMaxMatchDistance(); - if (state.horizontalEdgeType != null) { - if (Math.abs(newBounds.h - b.h) < maxMatchDistance) { - state.wrapHeight = true; - if (state.horizontalEdgeType == SegmentType.TOP) { - newBounds.y += newBounds.h - b.h; - } - newBounds.h = b.h; - } - } - if (state.verticalEdgeType != null) { - if (Math.abs(newBounds.w - b.w) < maxMatchDistance) { - state.wrapWidth = true; - if (state.verticalEdgeType == SegmentType.LEFT) { - newBounds.x += newBounds.w - b.w; - } - newBounds.w = b.w; - } - } - } - - // Match on fill bounds - state.horizontalFillSegment = null; - state.fillHeight = false; - if (state.horizontalEdgeType == SegmentType.BOTTOM && !state.wrapHeight) { - Rect parentBounds = parent.getBounds(); - state.horizontalFillSegment = new Segment(parentBounds.y2(), newBounds.x, - newBounds.x2(), - null /*node*/, null /*id*/, SegmentType.BOTTOM, MarginType.NO_MARGIN); - if (Math.abs(newBounds.y2() - parentBounds.y2()) < getMaxMatchDistance()) { - state.fillHeight = true; - newBounds.h = parentBounds.y2() - newBounds.y; - } - } - state.verticalFillSegment = null; - state.fillWidth = false; - if (state.verticalEdgeType == SegmentType.RIGHT && !state.wrapWidth) { - Rect parentBounds = parent.getBounds(); - state.verticalFillSegment = new Segment(parentBounds.x2(), newBounds.y, - newBounds.y2(), - null /*node*/, null /*id*/, SegmentType.RIGHT, MarginType.NO_MARGIN); - if (Math.abs(newBounds.x2() - parentBounds.x2()) < getMaxMatchDistance()) { - state.fillWidth = true; - newBounds.w = parentBounds.x2() - newBounds.x; - } - } - - feedback.tooltip = getResizeUpdateMessage(state, child, parent, - newBounds, state.horizontalEdgeType, state.verticalEdgeType); - } - - @Override - public void onResizeEnd(@Nullable DropFeedback feedback, @NonNull INode child, - final @NonNull INode parent, final @NonNull Rect newBounds) { - final Rect oldBounds = child.getBounds(); - if (oldBounds.w != newBounds.w || oldBounds.h != newBounds.h) { - final ResizeState state = (ResizeState) feedback.userData; - child.editXml("Resize", new INodeHandler() { - @Override - public void handle(@NonNull INode n) { - setNewSizeBounds(state, n, parent, oldBounds, newBounds, - state.horizontalEdgeType, state.verticalEdgeType); - } - }); - } - } - - /** - * Returns the message to display to the user during the resize operation - * - * @param resizeState the current resize state - * @param child the child node being resized - * @param parent the parent of the resized node - * @param newBounds the new bounds to resize the child to, in pixels - * @param horizontalEdge the horizontal edge being resized - * @param verticalEdge the vertical edge being resized - * @return the message to display for the current resize bounds - */ - protected String getResizeUpdateMessage(ResizeState resizeState, INode child, INode parent, - Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) { - String width = resizeState.getWidthAttribute(); - String height = resizeState.getHeightAttribute(); - - if (horizontalEdge == null) { - return width; - } else if (verticalEdge == null) { - return height; - } else { - // U+00D7: Unicode for multiplication sign - return String.format("%s \u00D7 %s", width, height); - } - } - - /** - * Performs the edit on the node to complete a resizing operation. The actual edit - * part is pulled out such that subclasses can change/add to the edits and be part of - * the same undo event - * - * @param resizeState the current resize state - * @param node the child node being resized - * @param layout the parent of the resized node - * @param newBounds the new bounds to resize the child to, in pixels - * @param horizontalEdge the horizontal edge being resized - * @param verticalEdge the vertical edge being resized - */ - protected void setNewSizeBounds(ResizeState resizeState, INode node, INode layout, - Rect oldBounds, Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) { - if (verticalEdge != null - && (newBounds.w != oldBounds.w || resizeState.wrapWidth || resizeState.fillWidth)) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, resizeState.getWidthAttribute()); - } - if (horizontalEdge != null - && (newBounds.h != oldBounds.h || resizeState.wrapHeight || resizeState.fillHeight)) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, resizeState.getHeightAttribute()); - } - } -} |