blob: 82d83d4c5bf1cbcd6cedb684bae34e4ddb0de79c [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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.tools.idea.uibuilder.handlers;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.idea.uibuilder.api.DragHandler;
import com.android.tools.idea.uibuilder.api.DragType;
import com.android.tools.idea.uibuilder.api.ViewEditor;
import com.android.tools.idea.uibuilder.graphics.NlDrawingStyle;
import com.android.tools.idea.uibuilder.graphics.NlGraphics;
import com.android.tools.idea.uibuilder.model.AndroidCoordinate;
import com.android.tools.idea.uibuilder.model.Insets;
import com.android.tools.idea.uibuilder.model.NlComponent;
import java.util.List;
import static com.android.SdkConstants.*;
public class CoordinatorLayoutHandler extends FrameLayoutHandler {
@Nullable
@Override
public DragHandler createDragHandler(@NonNull ViewEditor editor,
@NonNull NlComponent layout,
@NonNull List<NlComponent> components,
@NonNull DragType type) {
// The {@link CoordinatorDragHandler} handles the logic for anchoring a single component to an existing component in
// the CoordinatorLayout. If we are moving several components we probably dont want them to be anchored to the same place,
// so instead we use the FrameLayoutHandler in this case.
if (components.size() == 1 && components.get(0) != null) {
return new CoordinatorDragHandler(editor, layout, components, type);
} else {
return super.createDragHandler(editor, layout, components, type);
}
}
private class CoordinatorDragHandler extends FrameDragHandler {
private NlComponent myAnchor;
private NlComponent myDragged;
private String myAnchorGravity;
private String myGravity;
@AndroidCoordinate
private int myPreviewX;
@AndroidCoordinate
private int myPreviewY;
public CoordinatorDragHandler(@NonNull ViewEditor editor,
@NonNull NlComponent layout,
@NonNull List<NlComponent> components,
@NonNull DragType type) {
super(editor, CoordinatorLayoutHandler.this, layout, components, type);
assert components.size() == 1;
myDragged = components.get(0);
assert myDragged != null;
}
@Override
public void start(@AndroidCoordinate int x, @AndroidCoordinate int y, int modifiers) {
super.start(x, y, modifiers);
checkPosition();
}
@Nullable
@Override
public String update(@AndroidCoordinate int x, @AndroidCoordinate int y, int modifiers) {
String result = super.update(x, y, modifiers);
checkPosition();
return result;
}
@Override
public void commit(@AndroidCoordinate int x, @AndroidCoordinate int y, int modifiers) {
checkPosition();
if (myAnchor == null) {
myDragged.setAttribute(AUTO_URI, ATTR_LAYOUT_ANCHOR, null);
myDragged.setAttribute(AUTO_URI, ATTR_LAYOUT_ANCHOR_GRAVITY, null);
super.commit(x, y, modifiers);
} else {
NlComponent root = myDragged.getRoot();
root.ensureNamespace(APP_PREFIX, AUTO_URI);
root.ensureNamespace(ANDROID_NS_NAME, ANDROID_URI);
myAnchor.ensureId();
String id = myAnchor.getAttribute(ANDROID_URI, ATTR_ID);
myDragged.setAttribute(AUTO_URI, ATTR_LAYOUT_ANCHOR, id);
myDragged.setAttribute(AUTO_URI, ATTR_LAYOUT_ANCHOR_GRAVITY, myAnchorGravity);
myDragged.setAttribute(ANDROID_URI, ATTR_LAYOUT_GRAVITY, myGravity);
}
}
@Override
public void paint(@NonNull NlGraphics gc) {
if (myAnchor == null) {
super.paint(gc);
} else {
Insets padding = myAnchor.getPadding();
int anchorX = myAnchor.x + padding.left;
int anchorW = myAnchor.w - padding.width();
int anchorY = myAnchor.y + padding.top;
int anchorH = myAnchor.h - padding.height();
// Highlight the anchor
gc.useStyle(NlDrawingStyle.DROP_RECIPIENT);
gc.drawRect(anchorX, anchorY, anchorW, anchorH);
gc.useStyle(NlDrawingStyle.DROP_ZONE);
gc.drawRect(myAnchor.x - myDragged.w, myAnchor.y - myDragged.h, myDragged.w * 2, myDragged.h * 2);
gc.drawRect(myAnchor.x + myAnchor.w - myDragged.w, myAnchor.y - myDragged.h, myDragged.w * 2, myDragged.h * 2);
gc.drawRect(myAnchor.x - myDragged.w, myAnchor.y + myAnchor.h - myDragged.h, myDragged.w * 2, myDragged.h * 2);
gc.drawRect(myAnchor.x + myAnchor.w - myDragged.w, myAnchor.y + myAnchor.h - myDragged.h, myDragged.w * 2, myDragged.h * 2);
if (myAnchor.w > 4 * myDragged.w) {
gc.drawRect(myAnchor.x + myAnchor.w / 2 - myDragged.w, myAnchor.y - myDragged.h, myDragged.w * 2, myDragged.h * 2);
gc.drawRect(myAnchor.x + myAnchor.w / 2 - myDragged.w, myAnchor.y + myAnchor.h - myDragged.h, myDragged.w * 2, myDragged.h * 2);
}
if (myAnchor.h > 4 * myDragged.h) {
gc.drawRect(myAnchor.x - myDragged.w, myAnchor.y + myAnchor.h / 2 - myDragged.h, myDragged.w * 2, myDragged.h * 2);
gc.drawRect(myAnchor.x + myAnchor.w - myDragged.w, myAnchor.y + myAnchor.h / 2 - myDragged.h, myDragged.w * 2, myDragged.h * 2);
}
if (myAnchor.w > 4 * myDragged.w && myAnchor.h > 4 * myDragged.h) {
gc.drawRect(myAnchor.x + myAnchor.w / 2 - myDragged.w, myAnchor.y + myAnchor.h / 2 - myDragged.h, myDragged.w * 2, myDragged.h * 2);
}
if (myAnchorGravity != null) {
gc.useStyle(NlDrawingStyle.DROP_PREVIEW);
gc.drawRect(myPreviewX, myPreviewY, myDragged.w, myDragged.h);
}
}
}
private void checkPosition() {
myAnchor = findAnchor();
myAnchorGravity = null;
myGravity = null;
myPreviewX = -1;
myPreviewY = -1;
if (myAnchor != null) {
String anchorHgrav = null;
String anchorVgrav = null;
String selfHgrav = null;
String selfVgrav = null;
@AndroidCoordinate int left = -1;
@AndroidCoordinate int top = -1;
@AndroidCoordinate int x = -1;
@AndroidCoordinate int y = -1;
if (lastX < myAnchor.x + myDragged.w) {
anchorHgrav = GRAVITY_VALUE_LEFT;
left = myAnchor.x - myDragged.w;
x = lastX - myAnchor.x;
} else if (lastX >= myAnchor.x + myAnchor.w - myDragged.w) {
anchorHgrav = GRAVITY_VALUE_RIGHT;
left = myAnchor.x + myAnchor.w - myDragged.w;
x = lastX - (myAnchor.x + myAnchor.w - myDragged.w);
} else if (myAnchor.w > 4 * myDragged.w &&
myAnchor.x + myAnchor.w / 2 - myDragged.w <= lastX && lastX < myAnchor.x + myAnchor.w / 2 + myDragged.w) {
anchorHgrav = GRAVITY_VALUE_CENTER_HORIZONTAL;
left = myAnchor.x + myAnchor.w / 2 - myDragged.w;
x = (lastX - (myAnchor.x + myAnchor.w / 2 - myDragged.w)) / 2;
}
if (anchorHgrav != null) {
if (x < myDragged.w / 3) {
selfHgrav = GRAVITY_VALUE_LEFT;
} else if (x < 2 * myDragged.w / 3) {
selfHgrav = GRAVITY_VALUE_CENTER_HORIZONTAL;
left += myDragged.w / 2;
} else {
selfHgrav = GRAVITY_VALUE_RIGHT;
left += myDragged.w;
}
}
if (lastY < myAnchor.y + myDragged.h) {
anchorVgrav = GRAVITY_VALUE_TOP;
top = myAnchor.y - myDragged.h;
y = lastY - myAnchor.y;
} else if (lastY >= myAnchor.y + myAnchor.h - myDragged.h) {
anchorVgrav = GRAVITY_VALUE_BOTTOM;
top = myAnchor.y + myAnchor.h - myDragged.h;
y = lastY - (myAnchor.y + myAnchor.h - myDragged.h);
} else if (myAnchor.h > 4 * myDragged.h &&
myAnchor.y + myAnchor.h / 2 - myDragged.h <= lastY && lastY < myAnchor.y + myAnchor.h / 2 + myDragged.h) {
anchorVgrav = GRAVITY_VALUE_CENTER_VERTICAL;
top = myAnchor.y + myAnchor.h / 2 - myDragged.h;
y = (lastY - (myAnchor.y + myAnchor.h / 2 - myDragged.h)) / 2;
}
if (anchorVgrav != null) {
if (y < myDragged.h / 3) {
selfVgrav = GRAVITY_VALUE_TOP;
} else if (y < 2 * myDragged.h / 3) {
selfVgrav = GRAVITY_VALUE_CENTER_VERTICAL;
top += myDragged.h / 2;
} else {
selfVgrav = GRAVITY_VALUE_BOTTOM;
top += myDragged.h;
}
}
if (anchorHgrav != null && anchorVgrav != null) {
myAnchorGravity = anchorVgrav + "|" + anchorHgrav;
myGravity = selfVgrav + "|" + selfHgrav;
myPreviewX = left;
myPreviewY = top;
}
}
}
@Nullable
NlComponent findAnchor() {
for (int i = layout.getChildCount() - 1; i >= 0; i--) {
NlComponent component = layout.getChild(i);
assert component != null;
if (component.x < lastX && lastX < component.x + component.w &&
component.y < lastY && lastY < component.y + component.h &&
component.w > myDragged.w * 3 && component.h > myDragged.h * 3) {
return component;
}
}
return null;
}
}
}