DropFeedbackpublic class DropFeedback extends Object Utility methods used when dealing with dropping EditPart on the GLE.
This class uses some temporary static storage to avoid excessive allocations during
drop operations. It is expected to only be invoked from the main UI thread with no
concurrent access. |
Fields Summary |
---|
private static final int | TOP | private static final int | LEFT | private static final int | BOTTOM | private static final int | RIGHT | private static final int | MAX_DIR | private static final int[] | sOppositeDirection | private static final UiElementEditPart[] | sTempClosests | private static final int[] | sTempMinDists | private static final RelativeInfo | sRelativeInfoThe single RelativeInfo used to compute results from a drop on a RelativeLayout | private static final UiElementEditPart[] | sTempTwoPartsA temporary array of 2 {@link UiElementEditPart} to avoid allocations. |
Constructors Summary |
---|
private DropFeedback()
|
Methods Summary |
---|
static void | addElementToXml(UiElementEditPart parentPart, com.android.ide.eclipse.editors.descriptors.ElementDescriptor descriptor, org.eclipse.draw2d.geometry.Point where, com.android.ide.eclipse.editors.layout.LayoutEditor.UiEditorActions actions)This method is used by {@link ElementCreateCommand#execute()} when a new item
needs to be "dropped" in the current XML document. It creates the new item using
the given descriptor as a child of the given parent part.
String layoutXmlName = getXmlLocalName(parentPart);
RelativeInfo info = null;
UiElementEditPart sibling = null;
// TODO consider merge like a vertical layout
// TODO consider TableLayout like a linear
if (LayoutConstants.LINEAR_LAYOUT.equals(layoutXmlName)) {
sibling = findLinearTarget(parentPart, where)[1];
} else if (LayoutConstants.RELATIVE_LAYOUT.equals(layoutXmlName)) {
info = findRelativeTarget(parentPart, where, sRelativeInfo);
if (info != null) {
sibling = info.targetParts[info.anchorIndex];
sibling = getNextUiSibling(sibling);
}
}
if (actions != null) {
UiElementNode uiSibling = sibling != null ? sibling.getUiNode() : null;
UiElementNode uiParent = parentPart.getUiNode();
UiElementNode uiNode = actions.addElement(uiParent, uiSibling, descriptor,
false /*updateLayout*/);
if (LayoutConstants.ABSOLUTE_LAYOUT.equals(layoutXmlName)) {
adjustAbsoluteAttributes(uiNode, where);
} else if (LayoutConstants.RELATIVE_LAYOUT.equals(layoutXmlName)) {
adustRelativeAttributes(uiNode, info);
}
}
| private static void | adjustAbsoluteAttributes(com.android.ide.eclipse.editors.uimodel.UiElementNode uiNode, org.eclipse.draw2d.geometry.Point where)Adjusts the attributes of a new node dropped in an AbsoluteLayout.
if (where == null) {
return;
}
uiNode.getEditor().editXmlModel(new Runnable() {
public void run() {
uiNode.setAttributeValue(LayoutConstants.ATTR_LAYOUT_X,
String.format(LayoutConstants.VALUE_N_DIP, where.x),
false /* override */);
uiNode.setAttributeValue(LayoutConstants.ATTR_LAYOUT_Y,
String.format(LayoutConstants.VALUE_N_DIP, where.y),
false /* override */);
uiNode.commitDirtyAttributesToXml();
}
});
| private static void | adustRelativeAttributes(com.android.ide.eclipse.editors.uimodel.UiElementNode uiNode, com.android.ide.eclipse.editors.layout.parts.DropFeedback$RelativeInfo info)Adjusts the attributes of a new node dropped in a RelativeLayout:
- anchor part: the one the user selected (or the closest) and to which the new one
will "attach". The anchor part can be null, either because the layout is currently
empty or the user is attaching to an existing empty border.
- direction: the direction from the anchor part to the drop point. That's also the
direction from the anchor part to the new part.
- the new node; it is created either after the anchor for right or top directions
or before the anchor for left or bottom directions. This means the new part can
reference the id of the anchor part.
Several cases:
- set: layout_above/below/toLeftOf/toRightOf to point to the anchor.
- copy: layout_centerHorizontal for top/bottom directions
- copy: layout_centerVertical for left/right directions.
- copy: layout_above/below/toLeftOf/toRightOf for the orthogonal direction
(i.e. top/bottom or left/right.)
if (uiNode == null || info == null) {
return;
}
final UiElementEditPart anchorPart = info.targetParts[info.anchorIndex]; // can be null
final int direction = info.direction;
uiNode.getEditor().editXmlModel(new Runnable() {
public void run() {
HashMap<String, String> map = new HashMap<String, String>();
UiElementNode anchorUiNode = anchorPart != null ? anchorPart.getUiNode() : null;
String anchorId = anchorUiNode != null
? anchorUiNode.getAttributeValue("id") //$NON-NLS-1$
: null;
if (anchorId == null) {
anchorId = DescriptorsUtils.getFreeWidgetId(anchorUiNode);
anchorUiNode.setAttributeValue("id", anchorId, true /*override*/); //$NON-NLS-1$
}
if (anchorId != null) {
switch(direction) {
case TOP:
map.put(LayoutConstants.ATTR_LAYOUT_ABOVE, anchorId);
break;
case BOTTOM:
map.put(LayoutConstants.ATTR_LAYOUT_BELOW, anchorId);
break;
case LEFT:
map.put(LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF, anchorId);
break;
case RIGHT:
map.put(LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF, anchorId);
break;
}
switch(direction) {
case TOP:
case BOTTOM:
map.put(LayoutConstants.ATTR_LAYOUT_CENTER_HORIZONTAL,
anchorUiNode.getAttributeValue(
LayoutConstants.ATTR_LAYOUT_CENTER_HORIZONTAL));
map.put(LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF,
anchorUiNode.getAttributeValue(
LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF));
map.put(LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF,
anchorUiNode.getAttributeValue(
LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF));
break;
case LEFT:
case RIGHT:
map.put(LayoutConstants.ATTR_LAYOUT_CENTER_VERTICAL,
anchorUiNode.getAttributeValue(
LayoutConstants.ATTR_LAYOUT_CENTER_VERTICAL));
map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_BASELINE,
anchorUiNode.getAttributeValue(
LayoutConstants.ATTR_LAYOUT_ALIGN_BASELINE));
map.put(LayoutConstants.ATTR_LAYOUT_ABOVE,
anchorUiNode.getAttributeValue(LayoutConstants.ATTR_LAYOUT_ABOVE));
map.put(LayoutConstants.ATTR_LAYOUT_BELOW,
anchorUiNode.getAttributeValue(LayoutConstants.ATTR_LAYOUT_BELOW));
break;
}
} else {
// We don't have an anchor node. Assume we're targeting a border and align
// to the parent.
switch(direction) {
case TOP:
map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP,
LayoutConstants.VALUE_TRUE);
break;
case BOTTOM:
map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM,
LayoutConstants.VALUE_TRUE);
break;
case LEFT:
map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT,
LayoutConstants.VALUE_TRUE);
break;
case RIGHT:
map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT,
LayoutConstants.VALUE_TRUE);
break;
}
}
for (Entry<String, String> entry : map.entrySet()) {
uiNode.setAttributeValue(entry.getKey(), entry.getValue(), true /* override */);
}
uiNode.commitDirtyAttributesToXml();
}
});
| private static void | computeClosest(org.eclipse.draw2d.geometry.Point refPoint, UiElementEditPart compareToPart, UiElementEditPart[] currClosests, int[] currMinDists, int direction)Given a certain reference point (drop point), computes the distance to the given
part in the given direction. For example if direction is top, only accepts parts which
bottom is above the reference point, computes their distance and then updates the
current minimal distances and current closest parts arrays accordingly.
Rectangle r = compareToPart.getBounds();
Point p = null;
boolean usable = false;
switch(direction) {
case TOP:
p = r.getBottom();
usable = p.y <= refPoint.y;
break;
case BOTTOM:
p = r.getTop();
usable = p.y >= refPoint.y;
break;
case LEFT:
p = r.getRight();
usable = p.x <= refPoint.x;
break;
case RIGHT:
p = r.getLeft();
usable = p.x >= refPoint.x;
break;
}
if (usable) {
int d = p.getDistance2(refPoint);
if (d < currMinDists[direction]) {
currMinDists[direction] = d;
currClosests[direction] = compareToPart;
}
}
| static com.android.ide.eclipse.editors.layout.parts.UiLayoutEditPart.HighlightInfo | computeDropFeedback(UiLayoutEditPart parentPart, com.android.ide.eclipse.editors.layout.parts.UiLayoutEditPart.HighlightInfo highlightInfo, org.eclipse.draw2d.geometry.Point where)This method is used by {@link UiLayoutEditPart#showDropTarget(Point)} to compute
highlight information when a drop target is moved over a valid drop area.
Since there are no "out" parameters in Java, all the information is returned
via the {@link HighlightInfo} structure passed as parameter.
String layoutType = getXmlLocalName(parentPart);
if (LayoutConstants.ABSOLUTE_LAYOUT.equals(layoutType)) {
highlightInfo.anchorPoint = where;
} else if (LayoutConstants.LINEAR_LAYOUT.equals(layoutType)) {
boolean isVertical = isVertical(parentPart);
highlightInfo.childParts = findLinearTarget(parentPart, where);
computeLinearLine(parentPart, isVertical, highlightInfo);
} else if (LayoutConstants.RELATIVE_LAYOUT.equals(layoutType)) {
RelativeInfo info = findRelativeTarget(parentPart, where, sRelativeInfo);
if (info != null) {
highlightInfo.childParts = sRelativeInfo.targetParts;
computeRelativeLine(parentPart, info, highlightInfo);
}
}
return highlightInfo;
| private static void | computeLinearLine(UiLayoutEditPart parentPart, boolean isVertical, com.android.ide.eclipse.editors.layout.parts.UiLayoutEditPart.HighlightInfo highlightInfo)Computes the highlight line between two parts.
The two parts are listed in HighlightInfo.childParts[2]. Any of the parts
can be null.
The result is stored in HighlightInfo.
Caller must clear the HighlightInfo as appropriate before this call.
Rectangle r = parentPart.getBounds();
if (isVertical) {
Point p = null;
UiElementEditPart part = highlightInfo.childParts[0];
if (part != null) {
p = part.getBounds().getBottom();
} else {
part = highlightInfo.childParts[1];
if (part != null) {
p = part.getBounds().getTop();
}
}
if (p != null) {
// horizontal line with middle anchor point
highlightInfo.tempPoints[0].setLocation(0, p.y);
highlightInfo.tempPoints[1].setLocation(r.width, p.y);
highlightInfo.linePoints = highlightInfo.tempPoints;
highlightInfo.anchorPoint = p.setLocation(r.width / 2, p.y);
}
} else {
Point p = null;
UiElementEditPart part = highlightInfo.childParts[0];
if (part != null) {
p = part.getBounds().getRight();
} else {
part = highlightInfo.childParts[1];
if (part != null) {
p = part.getBounds().getLeft();
}
}
if (p != null) {
// vertical line with middle anchor point
highlightInfo.tempPoints[0].setLocation(p.x, 0);
highlightInfo.tempPoints[1].setLocation(p.x, r.height);
highlightInfo.linePoints = highlightInfo.tempPoints;
highlightInfo.anchorPoint = p.setLocation(p.x, r.height / 2);
}
}
| private static void | computeRelativeLine(UiLayoutEditPart parentPart, com.android.ide.eclipse.editors.layout.parts.DropFeedback$RelativeInfo relInfo, com.android.ide.eclipse.editors.layout.parts.UiLayoutEditPart.HighlightInfo highlightInfo)Computes the highlight line for a drop on a RelativeLayout.
The line is always placed on the side of the anchor part indicated by the
direction. The direction always point from the anchor part to the drop point.
If there's no anchor part, use the other one with a reversed direction.
On output, this updates the {@link HighlightInfo}.
UiElementEditPart[] parts = relInfo.targetParts;
int dir = relInfo.direction;
int index = relInfo.anchorIndex;
UiElementEditPart part = parts[index];
if (part == null) {
dir = sOppositeDirection[dir];
part = parts[1 - index];
}
if (part == null) {
// give up if both parts are null
return;
}
Rectangle r = part.getBounds();
Point p = null;
switch(dir) {
case TOP:
p = r.getTop();
break;
case BOTTOM:
p = r.getBottom();
break;
case LEFT:
p = r.getLeft();
break;
case RIGHT:
p = r.getRight();
break;
}
highlightInfo.anchorPoint = p;
r = parentPart.getBounds();
switch(dir) {
case TOP:
case BOTTOM:
// horizontal line with middle anchor point
highlightInfo.tempPoints[0].setLocation(0, p.y);
highlightInfo.tempPoints[1].setLocation(r.width, p.y);
highlightInfo.linePoints = highlightInfo.tempPoints;
highlightInfo.anchorPoint = p;
break;
case LEFT:
case RIGHT:
// vertical line with middle anchor point
highlightInfo.tempPoints[0].setLocation(p.x, 0);
highlightInfo.tempPoints[1].setLocation(p.x, r.height);
highlightInfo.linePoints = highlightInfo.tempPoints;
highlightInfo.anchorPoint = p;
break;
}
| private static UiElementEditPart | findClosestPart(UiElementEditPart referencePart, int direction)Given a reference parts, finds the closest part in the parent in the given direction.
For example if direction is top, finds the closest sibling part which is above the
reference part and non-overlapping (they can touch.)
if (referencePart == null || referencePart.getParent() == null) {
return null;
}
Rectangle r = referencePart.getBounds();
Point ref = null;
switch(direction) {
case TOP:
ref = r.getTop();
break;
case BOTTOM:
ref = r.getBottom();
break;
case LEFT:
ref = r.getLeft();
break;
case RIGHT:
ref = r.getRight();
break;
}
int minDist = Integer.MAX_VALUE;
UiElementEditPart closestPart = null;
for (Object childPart : referencePart.getParent().getChildren()) {
if (childPart != referencePart && childPart instanceof UiElementEditPart) {
r = ((UiElementEditPart) childPart).getBounds();
Point p = null;
boolean usable = false;
switch(direction) {
case TOP:
p = r.getBottom();
usable = p.y <= ref.y;
break;
case BOTTOM:
p = r.getTop();
usable = p.y >= ref.y;
break;
case LEFT:
p = r.getRight();
usable = p.x <= ref.x;
break;
case RIGHT:
p = r.getLeft();
usable = p.x >= ref.x;
break;
}
if (usable) {
int d = p.getDistance2(ref);
if (d < minDist) {
minDist = d;
closestPart = (UiElementEditPart) childPart;
}
}
}
}
return closestPart;
| private static UiElementEditPart[] | findLinearTarget(UiElementEditPart parent, org.eclipse.draw2d.geometry.Point point)For a given parent edit part that MUST represent a LinearLayout, finds the
element before which the location points.
This computes the edit part that corresponds to what will be the "next sibling" of the new
element.
It returns null if it can't be determined, in which case the element will be added at the
end of the parent child list.
// default orientation is horizontal
boolean isVertical = isVertical(parent);
int target = isVertical ? point.y : point.x;
UiElementEditPart prev = null;
UiElementEditPart next = null;
for (Object child : parent.getChildren()) {
if (child instanceof UiElementEditPart) {
UiElementEditPart childPart = (UiElementEditPart) child;
Point p = childPart.getBounds().getCenter();
int middle = isVertical ? p.y : p.x;
if (target < middle) {
next = childPart;
break;
}
prev = childPart;
}
}
sTempTwoParts[0] = prev;
sTempTwoParts[1] = next;
return sTempTwoParts;
| private static com.android.ide.eclipse.editors.layout.parts.DropFeedback$RelativeInfo | findRelativeTarget(UiElementEditPart parent, org.eclipse.draw2d.geometry.Point point, com.android.ide.eclipse.editors.layout.parts.DropFeedback$RelativeInfo outInfo)Finds the "target" relative layout item for the drop operation & feedback.
If the drop point is exactly on a current item, simply returns the side the drop will occur
compared to the center of that element. For the actual XML, we'll need to insert *after*
that element to make sure that referenced are defined in the right order.
In that case the result contains two elements, the second one always being on the right or
bottom side of the first one. When insert in XML, we want to insert right before that
second element or at the end of the child list if the second element is null.
If the drop point is not exactly on a current element, find the closest in each
direction and align with the two closest of these.
for (int i = 0; i < 4; i++) {
sTempMinDists[i] = Integer.MAX_VALUE;
sTempClosests[i] = null;
}
for (Object child : parent.getChildren()) {
if (child instanceof UiElementEditPart) {
UiElementEditPart childPart = (UiElementEditPart) child;
Rectangle r = childPart.getBounds();
if (r.contains(point)) {
float rx = ((float)(point.x - r.x) / (float)r.width ) - 0.5f;
float ry = ((float)(point.y - r.y) / (float)r.height) - 0.5f;
/* TOP
* \ /
* \ /
* L X R
* / \
* / \
* BOT
*/
int index = 0;
if (Math.abs(rx) >= Math.abs(ry)) {
if (rx < 0) {
outInfo.direction = LEFT;
index = 1;
} else {
outInfo.direction = RIGHT;
}
} else {
if (ry < 0) {
outInfo.direction = TOP;
index = 1;
} else {
outInfo.direction = BOTTOM;
}
}
outInfo.anchorIndex = index;
outInfo.targetParts[index] = childPart;
outInfo.targetParts[1 - index] = findClosestPart(childPart,
outInfo.direction);
return outInfo;
}
computeClosest(point, childPart, sTempClosests, sTempMinDists, TOP);
computeClosest(point, childPart, sTempClosests, sTempMinDists, LEFT);
computeClosest(point, childPart, sTempClosests, sTempMinDists, BOTTOM);
computeClosest(point, childPart, sTempClosests, sTempMinDists, RIGHT);
}
}
UiElementEditPart closest = null;
int minDist = Integer.MAX_VALUE;
int minDir = -1;
for (int i = 0; i <= MAX_DIR; i++) {
if (sTempClosests[i] != null && sTempMinDists[i] < minDist) {
closest = sTempClosests[i];
minDist = sTempMinDists[i];
minDir = i;
}
}
if (closest != null) {
int index = 0;
switch(minDir) {
case TOP:
case LEFT:
index = 0;
break;
case BOTTOM:
case RIGHT:
index = 1;
break;
}
outInfo.anchorIndex = index;
outInfo.targetParts[index] = closest;
outInfo.targetParts[1 - index] = findClosestPart(closest, sOppositeDirection[minDir]);
outInfo.direction = sOppositeDirection[minDir];
return outInfo;
}
return null;
| private static UiElementEditPart | getNextUiSibling(UiElementEditPart part)Returns the next UI sibling of this part, i.e. the element which is just after in
the UI/XML order in the same parent. Returns null if there's no such part.
Note: by "UI sibling" here we mean the sibling in the UiNode hierarchy. By design the
UiNode model has the exact same order as the XML model. This has nothing to do
with the "user interface" order that you see on the rendered Android layouts (e.g. for
LinearLayout they are the same but for AbsoluteLayout or RelativeLayout the UI/XML model
order can be vastly different from the user interface order.)
if (part != null) {
UiElementNode uiNode = part.getUiNode();
if (uiNode != null) {
uiNode = uiNode.getUiNextSibling();
}
if (uiNode != null) {
for (Object childPart : part.getParent().getChildren()) {
if (childPart instanceof UiElementEditPart &&
((UiElementEditPart) childPart).getUiNode() == uiNode) {
return (UiElementEditPart) childPart;
}
}
}
}
return null;
| private static java.lang.String | getXmlLocalName(UiElementEditPart editPart)Returns the XML local name of the ui node associated with this edit part or null.
UiElementNode uiNode = editPart.getUiNode();
if (uiNode != null) {
ElementDescriptor desc = uiNode.getDescriptor();
if (desc != null) {
return desc.getXmlLocalName();
}
}
return null;
| private static boolean | isVertical(UiElementEditPart parent)Returns true if the linear layout is marked as vertical.
String orientation = parent.getStringAttr("orientation"); //$NON-NLS-1$
boolean isVertical = "vertical".equals(orientation) || //$NON-NLS-1$
"1".equals(orientation); //$NON-NLS-1$
return isVertical;
|
|