aboutsummaryrefslogtreecommitdiffstats
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2010-11-08 11:21:24 -0800
committerTor Norbye <tnorbye@google.com>2010-11-09 16:32:28 -0800
commit23258d5ae21d164049eef14eebb2ef28a43a7b40 (patch)
tree998d756150724474885f12cbe8b680d7f677f0eb /eclipse/plugins/com.android.ide.eclipse.adt/src/com
parente00a18ff06a4b0f2862e541fe61f10f3bf6b6b2d (diff)
downloadsdk-23258d5ae21d164049eef14eebb2ef28a43a7b40.zip
sdk-23258d5ae21d164049eef14eebb2ef28a43a7b40.tar.gz
sdk-23258d5ae21d164049eef14eebb2ef28a43a7b40.tar.bz2
Add transient visibility mode for empty containers
When you add a new container, such as a LinearLayout, it is usually invisible. The reason for this is that an empty layout has 0 width and 0 height. There are two existing features in the layout editor to deal with this: (1) Outline mode, which renders rectangles around all views, and (2) Padding mode, which adds 10 pixels of padding to all views. In combination, these two modes will create a rectangle for empty layouts making them visible and user-manipulatable -- you can for example select or drop into them. This changeset attempts to make dealing with these types of containers easier and more discoverable. It adds a new temporary mode where empty containers (and only empty containers) are automatically padded and have their outlines painted. And more importantly, this is only done in some limited scenarios: When you drag into, or drag within, the layout canvas. As soon as you finish the drag, empty containers disappear again. Unlike padding mode, we don't enlarge the design surface itself, since this mode comes and goes easily and frequently. In addition to this, there is special handling for selection. If you select a zero-sized element (which for example is automatically done when you drop a new layout, and which can also be done by clicking in the outline), then the element is also revealed similar to the show-empty mode, but in this case only the selected item and not any other invisible containers are shown. Change-Id: Ibf3ec6a080a50a8f0f55919c3d3e6c4d2890468d
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java65
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java47
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/EmptyViewsOverlay.java93
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java7
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java9
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java101
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java47
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java142
11 files changed, 509 insertions, 26 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java
index 335e565..0938afa 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java
@@ -91,11 +91,17 @@ public enum DrawingStyle {
HELP,
/**
- * The style used to raw illegal/error/invalid markers
+ * The style used to draw illegal/error/invalid markers
*/
INVALID,
/**
+ * The style used to draw empty containers of zero bounds (which are padded
+ * a bit to make them visible during a drag or selection).
+ */
+ EMPTY,
+
+ /**
* A style used for unspecified purposes; can be used by a client to have
* yet another color that is domain specific; using this color constant
* rather than your own hardcoded value means that you will be guaranteed to
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
index 180c329..ed8a205 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
@@ -278,6 +278,13 @@ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput,
}
/**
+ * Tells the graphical editor to recompute its layout.
+ */
+ public void recomputeLayout() {
+ mGraphicalEditor.recomputeLayout();
+ }
+
+ /**
* Returns the custom IContentOutlinePage or IPropertySheetPage when asked for it.
*/
@SuppressWarnings("unchecked")
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
index 5f93387..1e04c6b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
@@ -37,6 +37,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -49,7 +50,7 @@ import java.util.regex.Pattern;
*/
public final class UiElementPullParser extends BasePullParser {
private final static String ATTR_PADDING = "padding"; //$NON-NLS-1$
- private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)"); //$NON-NLS-1$
+ private final static Pattern FLOAT_PATTERN = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)"); //$NON-NLS-1$
private final int[] sIntOut = new int[1];
@@ -61,16 +62,52 @@ public final class UiElementPullParser extends BasePullParser {
private List<ElementDescriptor> mLayoutDescriptors;
private final int mDensityValue;
private final float mXdpi;
- private final String mDefaultPaddingValue;
- public UiElementPullParser(UiElementNode top, boolean explodeRendering, int densityValue,
- float xdpi, IProject project) {
+ /**
+ * Number of pixels to pad views with in exploded-rendering mode.
+ */
+ private static final String DEFAULT_PADDING_VALUE =
+ ExplodedRenderingHelper.PADDING_VALUE + "px"; //$NON-NLS-1$
+
+ /**
+ * Number of pixels to pad exploded individual views with. (This is HALF the width of the
+ * rectangle since padding is repeated on both sides of the empty content.)
+ */
+ private static final String FIXED_PADDING_VALUE = "20px"; //$NON-NLS-1$
+
+ /**
+ * Set of nodes that we want to auto-pad using {@link #FIXED_PADDING_VALUE} as the padding
+ * attribute value. Can be null, which is the case when we don't want to perform any
+ * <b>individual</b> node exploding.
+ */
+ private final Set<UiElementNode> mExplodeNodes;
+
+ /**
+ * Constructs a new {@link UiElementPullParser}, a parser dedicated to the special case of
+ * parsing a layout resource files, and handling "exploded rendering" - adding padding on views
+ * to make them easier to see and operate on.
+ *
+ * @param top The {@link UiElementNode} for the root node.
+ * @param explodeRendering When true, add padding to <b>all</b> nodes in the hierarchy. This
+ * will add rather than replace padding of a node.
+ * @param explodeNodes A set of individual nodes that should be assigned a fixed amount of
+ * padding ({@link #FIXED_PADDING_VALUE}). This is intended for use with nodes that
+ * (without padding) would be invisible. This parameter can be null, in which case
+ * nodes are not individually exploded (but they may all be exploded with the
+ * explodeRendering parameter.
+ * @param densityValue the density factor for the screen.
+ * @param xdpi the screen actual dpi in X
+ * @param project Project containing this layout.
+ */
+ public UiElementPullParser(UiElementNode top, boolean explodeRendering,
+ Set<UiElementNode> explodeNodes,
+ int densityValue, float xdpi, IProject project) {
super();
mRoot = top;
mExplodedRendering = explodeRendering;
+ mExplodeNodes = explodeNodes;
mDensityValue = densityValue;
mXdpi = xdpi;
- mDefaultPaddingValue = ExplodedRenderingHelper.PADDING_VALUE + "px"; //$NON-NLS-1$
if (mExplodedRendering) {
// get the layout descriptor
IAndroidTarget target = Sdk.getCurrent().getTarget(project);
@@ -255,7 +292,7 @@ public final class UiElementPullParser extends BasePullParser {
public String getAttributeValue(int i) {
if (mZeroAttributeIsPadding) {
if (i == 0) {
- return mDefaultPaddingValue;
+ return DEFAULT_PADDING_VALUE;
} else {
i--;
}
@@ -279,9 +316,17 @@ public final class UiElementPullParser extends BasePullParser {
* This is the main method used by the LayoutInflater to query for attributes.
*/
public String getAttributeValue(String namespace, String localName) {
+ if (mExplodeNodes != null && ATTR_PADDING.equals(localName) &&
+ SdkConstants.NS_RESOURCES.equals(namespace)) {
+ UiElementNode node = getCurrentNode();
+ if (node != null && mExplodeNodes.contains(node)) {
+ return FIXED_PADDING_VALUE;
+ }
+ }
+
if (mZeroAttributeIsPadding && ATTR_PADDING.equals(localName) &&
SdkConstants.NS_RESOURCES.equals(namespace)) {
- return mDefaultPaddingValue;
+ return DEFAULT_PADDING_VALUE;
}
// get the current uiNode
@@ -457,14 +502,14 @@ public final class UiElementPullParser extends BasePullParser {
*/
private boolean stringToPixel(String s) {
// remove the space before and after
- s.trim();
+ s = s.trim();
int len = s.length();
if (len <= 0) {
return false;
}
- // check that there's no non ascii characters.
+ // check that there's no non ASCII characters.
char[] buf = s.toCharArray();
for (int i = 0 ; i < len ; i++) {
if (buf[i] > 255) {
@@ -478,7 +523,7 @@ public final class UiElementPullParser extends BasePullParser {
}
// now look for the string that is after the float...
- Matcher m = sFloatPattern.matcher(s);
+ Matcher m = FLOAT_PATTERN.matcher(s);
if (m.matches()) {
String f_str = m.group(1);
String end = m.group(2);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
index 9714b6c..7485b6d 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.common.api.Rect;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
@@ -64,6 +65,13 @@ public class CanvasViewInfo implements IPropertySource {
private final ArrayList<CanvasViewInfo> mChildren = new ArrayList<CanvasViewInfo>();
/**
+ * Is this view info an individually exploded view? This is the case for views
+ * that were specially inflated by the {@link UiElementPullParser} and assigned
+ * fixed padding because they were invisible and somebody requested visibility.
+ */
+ private boolean mExploded;
+
+ /**
* Constructs a {@link CanvasViewInfo} hierarchy based on a given {@link ILayoutViewInfo}
* hierarchy. This call is recursive and builds a full tree.
*
@@ -279,6 +287,45 @@ public class CanvasViewInfo implements IPropertySource {
}
/**
+ * Returns true if this {@link CanvasViewInfo} represents an invisible parent - in
+ * other words, a view that can have children, and that has zero bounds making it
+ * effectively invisible. (We don't actually look for -0- bounds, but
+ * bounds smaller than SELECTION_MIN_SIZE.)
+ *
+ * @return True if this is an invisible parent.
+ */
+ public boolean isInvisibleParent() {
+ if (mAbsRect.width < SELECTION_MIN_SIZE || mAbsRect.height < SELECTION_MIN_SIZE) {
+ return mUiViewKey != null && mUiViewKey.getDescriptor().hasChildren();
+ }
+
+ return false;
+ }
+
+ /**
+ * Is this {@link CanvasViewInfo} a view that has had its padding inflated in order to
+ * make it visible during selection or dragging? Note that this is NOT considered to
+ * be the case in the explode-all-views mode where all nodes have their padding
+ * increased; it's only used for views that individually exploded because they were
+ * requested visible and they returned true for {@link #isInvisibleParent()}.
+ *
+ * @return True if this is an exploded node.
+ */
+ public boolean isExploded() {
+ return mExploded;
+ }
+
+ /**
+ * Mark this {@link CanvasViewInfo} as having been exploded or not. See the
+ * {@link #isExploded()} method for details on what this property means.
+ *
+ * @param exploded New value of the exploded property to mark this info with.
+ */
+ /* package */ void setExploded(boolean exploded) {
+ this.mExploded = exploded;
+ }
+
+ /**
* Returns the info represented as a {@link SimpleElement}.
*
* @return A {@link SimpleElement} wrapping this info.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/EmptyViewsOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/EmptyViewsOverlay.java
new file mode 100644
index 0000000..946c381
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/EmptyViewsOverlay.java
@@ -0,0 +1,93 @@
+/*
+ * 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.eclipse.adt.internal.editors.layout.gle2;
+
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Rectangle;
+
+/**
+ * The {@link EmptyViewsOverlay} paints bounding rectangles for any of the empty and
+ * invisible container views in the scene.
+ */
+public class EmptyViewsOverlay extends Overlay {
+ /** The {@link ViewHierarchy} containing visible view information. */
+ private final ViewHierarchy mViewHierarchy;
+
+ /** Border color to paint the bounding boxes with. */
+ private Color mBorderColor;
+
+ /** Vertical scaling & scrollbar information. */
+ private ScaleInfo mVScale;
+
+ /** Horizontal scaling & scrollbar information. */
+ private ScaleInfo mHScale;
+
+ /**
+ * Constructs a new {@link EmptyViewsOverlay} linked to the given view hierarchy.
+ *
+ * @param viewHierarchy The {@link ViewHierarchy} to render.
+ * @param hScale The {@link ScaleInfo} to use to transfer horizontal layout
+ * coordinates to screen coordinates.
+ * @param vScale The {@link ScaleInfo} to use to transfer vertical layout coordinates
+ * to screen coordinates.
+ */
+ public EmptyViewsOverlay(ViewHierarchy viewHierarchy, ScaleInfo hScale, ScaleInfo vScale) {
+ super();
+ this.mViewHierarchy = viewHierarchy;
+ this.mHScale = hScale;
+ this.mVScale = vScale;
+ }
+
+ @Override
+ public void create(Device device) {
+ mBorderColor = new Color(device, SwtDrawingStyle.EMPTY.getStrokeColor());
+ }
+
+ @Override
+ public void dispose() {
+ if (mBorderColor != null) {
+ mBorderColor.dispose();
+ mBorderColor = null;
+ }
+ }
+
+ @Override
+ public void paint(GC gc) {
+ gc.setForeground(mBorderColor);
+ gc.setLineDash(null);
+ gc.setLineStyle(SwtDrawingStyle.EMPTY.getLineStyle());
+ int oldAlpha = gc.getAlpha();
+ gc.setAlpha(SwtDrawingStyle.EMPTY.getStrokeAlpha());
+ gc.setLineWidth(SwtDrawingStyle.EMPTY.getLineWidth());
+
+ for (CanvasViewInfo info : mViewHierarchy.getInvisibleViews()) {
+ Rectangle r = info.getAbsRect();
+
+ int x = mHScale.translate(r.x);
+ int y = mVScale.translate(r.y);
+ int w = mHScale.scale(r.width);
+ int h = mVScale.scale(r.height);
+
+ // +1: See explanation in equivalent code in {@link OutlineOverlay#paint}
+ gc.drawRectangle(x, y, w + 1, h + 1);
+ }
+
+ gc.setAlpha(oldAlpha);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
index 97a21ee..9da3599 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
@@ -370,6 +370,8 @@ public class GestureManager {
* The cursor has entered the drop target boundaries. {@inheritDoc}
*/
public void dragEnter(DropTargetEvent event) {
+ mCanvas.showInvisibleViews(true);
+
if (mCurrentGesture == null) {
Gesture newGesture = mZombieGesture;
if (newGesture == null) {
@@ -406,6 +408,8 @@ public class GestureManager {
finishGesture(ControlPoint.create(mCanvas, event), true);
mZombieGesture = dropGesture;
}
+
+ mCanvas.showInvisibleViews(false);
}
/**
@@ -477,7 +481,6 @@ public class GestureManager {
// element, *change* the selection to match the element under the
// cursor and use that. If nothing can be selected, abort the drag
// operation.
-
List<CanvasSelection> selections = mCanvas.getSelectionManager().getSelections();
mDragSelection.clear();
@@ -559,6 +562,7 @@ public class GestureManager {
e.doit = false;
} else {
// Otherwise, the drag means you are moving something
+ mCanvas.showInvisibleViews(true);
startGesture(ControlPoint.create(mCanvas, e), new MoveGesture(mCanvas), 0);
}
@@ -601,6 +605,7 @@ public class GestureManager {
GlobalCanvasDragInfo.getInstance().stopDrag();
finishGesture(ControlPoint.create(mCanvas, e), e.detail == DND.DROP_NONE);
+ mCanvas.showInvisibleViews(false);
mCanvas.redraw();
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index a0fbac6..b0d5ed9 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -1084,7 +1084,7 @@ public class GraphicalEditorPart extends EditorPart
}
};
- mCanvasViewer.getCanvas().setResult(result);
+ mCanvasViewer.getCanvas().setResult(result, null /*explodeNodes*/);
return;
}
@@ -1209,8 +1209,11 @@ public class GraphicalEditorPart extends EditorPart
float ydpi = mConfigComposite.getYDpi();
boolean isProjectTheme = mConfigComposite.isProjectTheme();
+ LayoutCanvas canvas = getCanvasControl();
+ Set<UiElementNode> explodeNodes = canvas.getNodesToExplode();
+
UiElementPullParser parser = new UiElementPullParser(getModel(),
- mUseExplodeMode, density, xdpi, iProject);
+ mUseExplodeMode, explodeNodes, density, xdpi, iProject);
ILayoutResult result = computeLayout(bridge, parser,
iProject /* projectKey */,
@@ -1223,7 +1226,7 @@ public class GraphicalEditorPart extends EditorPart
// post rendering clean up
bridge.cleanUp();
- mCanvasViewer.getCanvas().setResult(result);
+ canvas.setResult(result, explodeNodes);
// update the UiElementNode with the layout info.
if (result.getSuccess() != ILayoutResult.SUCCESS) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
index c97013b..1c0c798 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
@@ -71,6 +71,10 @@ import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.w3c.dom.Node;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
/**
* Displays the image rendered by the {@link GraphicalEditorPart} and handles
* the interaction with the widgets.
@@ -110,6 +114,9 @@ class LayoutCanvas extends Canvas {
/** When true, always display the outline of all views. */
private boolean mShowOutline;
+ /** When true, display the outline of all empty parent views. */
+ private boolean mShowInvisible;
+
/** Drop target associated with this composite. */
private DropTarget mDropTarget;
@@ -161,6 +168,9 @@ class LayoutCanvas extends Canvas {
/** The overlay which paints the optional outline. */
private OutlineOverlay mOutlineOverlay;
+ /** The overlay which paints outlines around empty children */
+ private EmptyViewsOverlay mEmptyOverlay;
+
/** The overlay which paints the mouse hover. */
private HoverOverlay mHoverOverlay;
@@ -199,7 +209,7 @@ class LayoutCanvas extends Canvas {
mFont = display.getSystemFont();
// --- Set up graphic overlays
- // mOutlineOverlay is initialized lazily
+ // mOutlineOverlay and mEmptyOverlay are initialized lazily
mHoverOverlay = new HoverOverlay(mHScale, mVScale);
mHoverOverlay.create(display);
mSelectionOverlay = new SelectionOverlay();
@@ -300,6 +310,11 @@ class LayoutCanvas extends Canvas {
mOutlineOverlay = null;
}
+ if (mEmptyOverlay != null) {
+ mEmptyOverlay.dispose();
+ mEmptyOverlay = null;
+ }
+
if (mHoverOverlay != null) {
mHoverOverlay.dispose();
mHoverOverlay = null;
@@ -427,12 +442,17 @@ class LayoutCanvas extends Canvas {
* when it is valid.
*
* @param result The new rendering result, either valid or not.
+ * @param explodedNodes The set of individual nodes the layout computer was asked to
+ * explode. Note that these are independent of the explode-all mode where
+ * all views are exploded; this is used only for the mode (
+ * {@link #showInvisibleViews(boolean)}) where individual invisible nodes
+ * are padded during certain interactions.
*/
- /* package */ void setResult(ILayoutResult result) {
+ /* package */ void setResult(ILayoutResult result, Set<UiElementNode> explodedNodes) {
// disable any hover
clearHover();
- mViewHierarchy.setResult(result);
+ mViewHierarchy.setResult(result, explodedNodes);
if (mViewHierarchy.isValid() && result != null) {
Image image = mImageOverlay.setImage(result.getImage());
@@ -532,6 +552,14 @@ class LayoutCanvas extends Canvas {
mOutlineOverlay.paint(gc);
}
+ if (mShowInvisible) {
+ if (mEmptyOverlay == null) {
+ mEmptyOverlay = new EmptyViewsOverlay(mViewHierarchy, mHScale, mVScale);
+ mEmptyOverlay.create(getDisplay());
+ }
+ mEmptyOverlay.paint(gc);
+ }
+
mHoverOverlay.paint(gc);
mSelectionOverlay.paint(mSelectionManager, gc, mGCWrapper, mRulesEngine);
mGestureManager.paint(gc);
@@ -542,6 +570,72 @@ class LayoutCanvas extends Canvas {
}
/**
+ * Shows or hides invisible parent views, which are views which have empty bounds and
+ * no children. The nodes which will be shown are provided by
+ * {@link #getNodesToExplode()}.
+ *
+ * @param show When true, any invisible parent nodes are padded and highlighted
+ * ("exploded"), and when false any formerly exploded nodes are hidden.
+ */
+ /* package */ void showInvisibleViews(boolean show) {
+ if (mShowInvisible == show) {
+ return;
+ }
+
+ // Optimization: Avoid doing work when we don't have invisible parents (on show)
+ // or formerly exploded nodes (on hide).
+ if (show && !mViewHierarchy.hasInvisibleParents()) {
+ return;
+ } else if (!show && !mViewHierarchy.hasExplodedParents()) {
+ return;
+ }
+
+ mShowInvisible = show;
+ mLayoutEditor.recomputeLayout();
+ }
+
+ /**
+ * Returns a set of nodes that should be exploded (forced non-zero padding during render),
+ * or null if no nodes should be exploded. (Note that this is independent of the
+ * explode-all mode, where all nodes are padded -- that facility does not use this
+ * mechanism, which is only intended to be used to expose invisible parent nodes.
+ *
+ * @return The set of invisible parents, or null if no views should be expanded.
+ */
+ public Set<UiElementNode> getNodesToExplode() {
+ if (mShowInvisible) {
+ return mViewHierarchy.getInvisibleNodes();
+ }
+
+ // IF we have selection, and IF we have invisible nodes in the view,
+ // see if any of the selected items are among the invisible nodes, and if so
+ // add them to a lazily constructed set which we pass back for rendering.
+ Set<UiElementNode> result = null;
+ List<CanvasSelection> selections = mSelectionManager.getSelections();
+ if (selections.size() > 0) {
+ List<CanvasViewInfo> invisibleParents = mViewHierarchy.getInvisibleViews();
+ if (invisibleParents.size() > 0) {
+ for (CanvasSelection item : selections) {
+ CanvasViewInfo viewInfo = item.getViewInfo();
+ // O(n^2) here, but both the selection size and especially the
+ // invisibleParents size are expected to be small
+ if (invisibleParents.contains(viewInfo)) {
+ UiViewElementNode node = viewInfo.getUiViewKey();
+ if (node != null) {
+ if (result == null) {
+ result = new HashSet<UiElementNode>();
+ }
+ result.add(node);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
* Clears the hover.
*/
/* package */ void clearHover() {
@@ -889,5 +983,4 @@ class LayoutCanvas extends Canvas {
AdtPlugin.printToConsole("Canvas", String.format(message, params));
}
}
-
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
index affd69a..3db65f1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
@@ -180,6 +180,7 @@ public class SelectionManager implements ISelectionProvider {
}
boolean changed = false;
+ boolean redoLayout = false;
// Create a list of all currently selected view infos
Set<CanvasViewInfo> oldSelected = new HashSet<CanvasViewInfo>();
@@ -206,15 +207,24 @@ public class SelectionManager implements ISelectionProvider {
mSelections.add(createSelection(newVi));
changed = true;
}
+ if (newVi.isInvisibleParent()) {
+ redoLayout = true;
+ }
}
}
// Deselect old selected items that are not in the new one
for (CanvasViewInfo vi : oldSelected) {
+ if (vi.isExploded()) {
+ redoLayout = true;
+ }
deselect(vi);
changed = true;
}
+ if (redoLayout) {
+ mCanvas.getLayoutEditor().recomputeLayout();
+ }
if (changed) {
redraw();
updateMenuActions();
@@ -238,7 +248,6 @@ public class SelectionManager implements ISelectionProvider {
* is a plain select or a toggle, etc.
*/
public void select(MouseEvent e) {
-
boolean isMultiClick = (e.stateMask & SWT.SHIFT) != 0 ||
// On Mac, the Command key is the normal toggle accelerator
((SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) &&
@@ -284,6 +293,10 @@ public class SelectionManager implements ISelectionProvider {
if (vi != null) {
// toggle this selection on-off: remove it if already selected
if (deselect(vi)) {
+ if (vi.isExploded()) {
+ mCanvas.getLayoutEditor().recomputeLayout();
+ }
+
redraw();
return;
}
@@ -334,7 +347,6 @@ public class SelectionManager implements ISelectionProvider {
} else {
// Case where no modifier is pressed: either select or reset the selection.
-
selectSingle(vi);
}
}
@@ -349,6 +361,8 @@ public class SelectionManager implements ISelectionProvider {
// reset alternate selection if any
mAltSelection = null;
+ boolean redoLayout = hasExplodedItems();
+
// reset (multi)selection if any
if (!mSelections.isEmpty()) {
if (mSelections.size() == 1 && mSelections.getFirst().getViewInfo() == vi) {
@@ -360,11 +374,30 @@ public class SelectionManager implements ISelectionProvider {
if (vi != null) {
mSelections.add(createSelection(vi));
+ if (vi.isInvisibleParent()) {
+ redoLayout = true;
+ }
}
fireSelectionChanged();
+
+ if (redoLayout) {
+ mCanvas.getLayoutEditor().recomputeLayout();
+ }
+
redraw();
}
+ /** Returns true if the view hierarchy is showing exploded items. */
+ private boolean hasExplodedItems() {
+ for (CanvasSelection item : mSelections) {
+ if (item.getViewInfo().isExploded()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Selects the given set of {@link CanvasViewInfo}s. This is similar to
* {@link #selectSingle} but allows you to make a multi-selection. Issues a
@@ -377,14 +410,24 @@ public class SelectionManager implements ISelectionProvider {
// reset alternate selection if any
mAltSelection = null;
+ boolean redoLayout = hasExplodedItems();
+
mSelections.clear();
if (viewInfos != null) {
for (CanvasViewInfo viewInfo : viewInfos) {
mSelections.add(createSelection(viewInfo));
+ if (viewInfo.isInvisibleParent()) {
+ redoLayout = true;
+ }
}
}
fireSelectionChanged();
+
+ if (redoLayout) {
+ mCanvas.getLayoutEditor().recomputeLayout();
+ }
+
redraw();
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java
index 36fdad9..2748297 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java
@@ -90,6 +90,11 @@ public enum SwtDrawingStyle {
INVALID(new RGB(0xFF, 0xFF, 0xFF), 255, new RGB(0xFF, 0x00, 0x00), 150, 2, SWT.LINE_SOLID),
/**
+ * The style definition corresponding to {@link DrawingStyle#EMPTY}
+ */
+ EMPTY(new RGB(0xFF, 0xFF, 0x55), 255, new RGB(0xFF, 0xFF, 0x55), 255, 1, SWT.LINE_DASH),
+
+ /**
* The style definition corresponding to {@link DrawingStyle#CUSTOM1}
*/
CUSTOM1(new RGB(0xFF, 0x00, 0xFF), 255, null, 0, 1, SWT.LINE_SOLID),
@@ -183,6 +188,8 @@ public enum SwtDrawingStyle {
/**
* Return the corresponding SwtDrawingStyle for the given
* {@link DrawingStyle}
+ * @param style The style to convert from a {@link DrawingStyle} to a {@link SwtDrawingStyle}.
+ * @return A corresponding {@link SwtDrawingStyle}.
*/
public static SwtDrawingStyle of(DrawingStyle style) {
switch (style) {
@@ -208,6 +215,8 @@ public enum SwtDrawingStyle {
return HELP;
case INVALID:
return INVALID;
+ case EMPTY:
+ return EMPTY;
case CUSTOM1:
return CUSTOM1;
case CUSTOM2:
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
index 24d0caa..8c39662 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
@@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.common.api.INode;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.layoutlib.api.ILayoutResult;
import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
@@ -27,7 +28,10 @@ import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* The view hierarchy class manages a set of view info objects and performs find
@@ -48,7 +52,7 @@ public class ViewHierarchy {
}
/**
- * The CanvasViewInfo root created by the last call to {@link #setResult(ILayoutResult)}
+ * The CanvasViewInfo root created by the last call to {@link #setResult}
* with a valid layout.
* <p/>
* This <em>can</em> be null to indicate we're dealing with an empty document with
@@ -59,7 +63,7 @@ public class ViewHierarchy {
private CanvasViewInfo mLastValidViewInfoRoot;
/**
- * True when the last {@link #setResult(ILayoutResult)} provided a valid {@link ILayoutResult}.
+ * True when the last {@link #setResult} provided a valid {@link ILayoutResult}.
* <p/>
* When false this means the canvas is displaying an out-dated result image & bounds and some
* features should be disabled accordingly such a drag'n'drop.
@@ -70,6 +74,27 @@ public class ViewHierarchy {
private boolean mIsResultValid;
/**
+ * A list of invisible parents (see {@link CanvasViewInfo#isInvisibleParent()} for
+ * details) in the current view hierarchy.
+ */
+ private final List<CanvasViewInfo> mInvisibleParents = new ArrayList<CanvasViewInfo>();
+
+ /**
+ * A read-only view of {@link #mInvisibleParents}; note that this is NOT a copy so it
+ * reflects updates to the underlying {@link #mInvisibleParents} list.
+ */
+ private final List<CanvasViewInfo> mInvisibleParentsReadOnly =
+ Collections.unmodifiableList(mInvisibleParents);
+
+ /**
+ * Flag which records whether or not we have any exploded parent nodes in this
+ * view hierarchy. This is used to track whether or not we need to recompute the
+ * layout when we exit show-all-invisible-parents mode (see
+ * {@link LayoutCanvas#showInvisibleViews}).
+ */
+ private boolean mExplodedParents;
+
+ /**
* Sets the result of the layout rendering. The result object indicates if the layout
* rendering succeeded. If it did, it contains a bitmap and the objects rectangles.
*
@@ -78,9 +103,15 @@ public class ViewHierarchy {
* when it is valid.
*
* @param result The new rendering result, either valid or not.
+ * @param explodedNodes The set of individual nodes the layout computer was asked to
+ * explode. Note that these are independent of the explode-all mode where
+ * all views are exploded; this is used only for the mode (
+ * {@link LayoutCanvas#showInvisibleViews}) where individual invisible
+ * nodes are padded during certain interactions.
*/
- /* package */ void setResult(ILayoutResult result) {
+ /* package */ void setResult(ILayoutResult result, Set<UiElementNode> explodedNodes) {
mIsResultValid = (result != null && result.getSuccess() == ILayoutResult.SUCCESS);
+ mExplodedParents = false;
if (mIsResultValid && result != null) {
ILayoutViewInfo root = result.getRootView();
@@ -92,8 +123,18 @@ public class ViewHierarchy {
updateNodeProxies(mLastValidViewInfoRoot);
+ // Update the data structures related to tracking invisible and exploded nodes.
+ // We need to find the {@link CanvasViewInfo} objects that correspond to
+ // the passed in {@link UiElementNode} keys that were re-rendered, and mark
+ // them as exploded and store them in a list for rendering.
+ mExplodedParents = false;
+ mInvisibleParents.clear();
+ addInvisibleParents(mLastValidViewInfoRoot, explodedNodes);
+
// Update the selection
mCanvas.getSelectionManager().sync(mLastValidViewInfoRoot);
+ } else {
+ mInvisibleParents.clear();
}
}
@@ -122,10 +163,45 @@ public class ViewHierarchy {
}
}
+ /**
+ * Make a pass over the view hierarchy and look for two things:
+ * <ol>
+ * <li>Invisible parents. These are nodes that can hold children and have empty
+ * bounds. These are then added to the {@link #mInvisibleParents} list.
+ * <li>Exploded nodes. These are nodes that were previously marked as invisible, and
+ * subsequently rendered by a recomputed layout. They now no longer have empty bounds,
+ * but should be specially marked via {@link CanvasViewInfo#setExploded} such that we
+ * for example in selection operations can determine if we need to recompute the
+ * layout.
+ * </ol>
+ *
+ * @param vi
+ * @param invisibleNodes
+ */
+ private void addInvisibleParents(CanvasViewInfo vi, Set<UiElementNode> invisibleNodes) {
+ if (vi == null) {
+ return;
+ }
+
+ if (vi.isInvisibleParent()) {
+ mInvisibleParents.add(vi);
+ } else if (invisibleNodes != null) {
+ UiViewElementNode key = vi.getUiViewKey();
+ if (key != null && invisibleNodes.contains(key)) {
+ vi.setExploded(true);
+ mExplodedParents = true;
+ mInvisibleParents.add(vi);
+ }
+ }
+
+ for (CanvasViewInfo child : vi.getChildren()) {
+ addInvisibleParents(child, invisibleNodes);
+ }
+ }
/**
- * Returns true when the last {@link #setResult(ILayoutResult)} provided a valid
+ * Returns true when the last {@link #setResult} provided a valid
* {@link ILayoutResult}.
* <p/>
* When false this means the canvas is displaying an out-dated result image & bounds and some
@@ -147,6 +223,24 @@ public class ViewHierarchy {
return mLastValidViewInfoRoot == null;
}
+ /**
+ * Returns true if we have parents in this hierarchy that are invisible (e.g. because
+ * they have no children and zero layout bounds).
+ *
+ * @return True if we have invisible parents.
+ */
+ public boolean hasInvisibleParents() {
+ return mInvisibleParents.size() > 0;
+ }
+
+ /**
+ * Returns true if we have views that were exploded during rendering
+ * @return True if we have exploded parents
+ */
+ public boolean hasExplodedParents() {
+ return mExplodedParents;
+ }
+
/** Locates and return any views that overlap the given selection rectangle.
* @param topLeft The top left corner of the selection rectangle.
* @param bottomRight The bottom right corner of the selection rectangle.
@@ -411,7 +505,7 @@ public class ViewHierarchy {
}
/**
- * Return the root of the view hierarchy, if any (could be null, for example
+ * Returns the root of the view hierarchy, if any (could be null, for example
* on rendering failure).
*
* @return The current view hierarchy, or null
@@ -420,4 +514,42 @@ public class ViewHierarchy {
return mLastValidViewInfoRoot;
}
+ /**
+ * Returns a collection of views that have zero bounds and that correspond to empty
+ * parents. Note that the views may not actually have zero bounds; in particular, if
+ * they are exploded ({@link CanvasViewInfo#isExploded()}, then they will have the
+ * bounds of a shown invisible node. Therefore, this method returns the views that
+ * would be invisible in a real rendering of the scene.
+ *
+ * @return A collection of empty parent views.
+ */
+ public List<CanvasViewInfo> getInvisibleViews() {
+ return mInvisibleParentsReadOnly;
+ }
+
+ /**
+ * Returns the invisible nodes (the {@link UiElementNode} objects corresponding
+ * to the {@link CanvasViewInfo} objects returned from {@link #getInvisibleViews()}.
+ * We are pulling out the nodes since they preserve their identity across layout
+ * rendering, and in particular we return it as a set such that the layout renderer
+ * can perform quick identity checks when looking up attribute values during the
+ * rendering process.
+ *
+ * @return A set of the invisible nodes.
+ */
+ public Set<UiElementNode> getInvisibleNodes() {
+ if (mInvisibleParents.size() == 0) {
+ return Collections.emptySet();
+ }
+
+ Set<UiElementNode> nodes = new HashSet<UiElementNode>(mInvisibleParents.size());
+ for (CanvasViewInfo info : mInvisibleParents) {
+ UiViewElementNode node = info.getUiViewKey();
+ if (node != null) {
+ nodes.add(node);
+ }
+ }
+
+ return nodes;
+ }
}