diff --git a/.idea/misc.xml b/.idea/misc.xml
index c29387da3..c264938f6 100755
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -7,6 +7,7 @@
+
diff --git a/app/src/main/java/com/itsaky/androidide/DesignerActivity.java b/app/src/main/java/com/itsaky/androidide/DesignerActivity.java
index 0ea3755c0..20b80d76a 100755
--- a/app/src/main/java/com/itsaky/androidide/DesignerActivity.java
+++ b/app/src/main/java/com/itsaky/androidide/DesignerActivity.java
@@ -236,6 +236,8 @@ public class DesignerActivity extends StudioActivity implements WidgetItemAdapte
.show ();
}
+ // FIXME Generated XML does not leave a space between
+ // codes of two adjacent views (in same parent)
private void finishWithResult (String result) {
final var intent = new Intent ();
intent.putExtra (KEY_GENERATED_CODE, result);
@@ -289,6 +291,7 @@ public class DesignerActivity extends StudioActivity implements WidgetItemAdapte
final var data = view.getExtraData ();
view.asView ().startDragAndDrop (clip, shadow, data, 0);
+ view.removeFromParent ();
return true;
}
diff --git a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java
index 8e50ce101..8dfcd8707 100755
--- a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java
+++ b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java
@@ -18,6 +18,7 @@
package com.itsaky.androidide.ui;
import android.content.Context;
+import android.graphics.RectF;
import android.view.DragEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -27,7 +28,6 @@ import androidx.annotation.NonNull;
import com.blankj.utilcode.util.SizeUtils;
import com.itsaky.androidide.DesignerActivity;
import com.itsaky.androidide.R;
-import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.models.UIWidget;
import com.itsaky.androidide.utils.Logger;
import com.itsaky.layoutinflater.IAttribute;
@@ -45,112 +45,170 @@ import org.jetbrains.annotations.Contract;
* @author Akash Yadav
*/
public class WidgetDragListener implements View.OnDragListener {
-
- private final View placeholder;
+
+ private final IView placeholder;
private final IViewGroup viewGroup;
private final OnViewAddedListener addedListener;
-
+
private final int PLACEHOLDER_HEIGHT = 20; // in dp
private final int PLACEHOLDER_WIDTH = 40; // in dp
-
+
public static final String ANDROID_NS = "android";
-
- private static final Logger LOG = Logger.instance("WidgetDragListener");
-
- public WidgetDragListener(@NonNull Context context, IViewGroup viewGroup, OnViewAddedListener addedListener) {
- this.placeholder = new View(context);
+
+ private static final Logger LOG = Logger.instance ("WidgetDragListener");
+
+ public WidgetDragListener (@NonNull Context context, IViewGroup viewGroup, OnViewAddedListener addedListener) {
+ this.placeholder = new UiView ("android.view.View", new View (context), true);
this.viewGroup = viewGroup;
this.addedListener = addedListener;
- this.placeholder.setBackgroundResource(R.drawable.bg_widget_drag_placeholder);
- this.placeholder.setLayoutParams(new ViewGroup.LayoutParams(SizeUtils.dp2px(PLACEHOLDER_WIDTH), SizeUtils.dp2px(PLACEHOLDER_HEIGHT)));
+ this.placeholder.asView ().setBackgroundResource (R.drawable.bg_widget_drag_placeholder);
+ this.placeholder.asView ().setLayoutParams (new ViewGroup.LayoutParams (SizeUtils.dp2px (PLACEHOLDER_WIDTH), SizeUtils.dp2px (PLACEHOLDER_HEIGHT)));
}
-
+
@Override
- public boolean onDrag(View v, @NonNull DragEvent event) {
- if (!(v instanceof ViewGroup)) {
- throw new UnsupportedOperationException("WidgetDragListener can be set only to a ViewGroup");
- }
-
- final var group = (ViewGroup) v; // Assuming that this drag listener is always set to ViewGroup(s)
- final var action = event.getAction();
- final var clipDesc = event.getClipDescription();
- final var dragData = (WidgetDragData) event.getLocalState();
-
- var canHandle = true;
-
+ public boolean onDrag (View v, @NonNull DragEvent event) {
+ final var action = event.getAction ();
+ final var clipDesc = event.getClipDescription ();
+ final var dragData = (WidgetDragData) event.getLocalState ();
+
+ var handled = true;
switch (action) {
case DragEvent.ACTION_DRAG_STARTED:
- canHandle = clipDesc.hasMimeType(DesignerActivity.DRAGGING_WIDGET_MIME);
+ handled = clipDesc.hasMimeType (DesignerActivity.DRAGGING_WIDGET_MIME);
break;
case DragEvent.ACTION_DRAG_ENTERED:
- removePlaceholderFromParent(); // If placeholder is added in another group, remove it.
- group.addView(this.placeholder);
+ case DragEvent.ACTION_DRAG_LOCATION :
+ this.placeholder.removeFromParent (); // If placeholder is added in another group, remove it.
+ this.viewGroup.addView (this.placeholder, findPlaceHolderIndex (event.getX (), event.getY ()));
break;
case DragEvent.ACTION_DRAG_EXITED:
- removePlaceholderFromParent();
+ this.placeholder.removeFromParent ();
break;
case DragEvent.ACTION_DROP:
- removePlaceholderFromParent();
- var view = dragData.isAlreadyInflated ? dragData.alreadyInflatedView : createView(dragData.newDragData);
- applyBasicAttributes(view);
-
- // Remove from parent if it has a parent
- view.removeFromParent();
-
- this.viewGroup.addView(view);
-
- if (this.addedListener != null) {
- this.addedListener.onViewAdded(view);
+ final var index = this.viewGroup.indexOfChild (this.placeholder);
+ this.placeholder.removeFromParent ();
+
+ var view = dragData.isAlreadyInflated ? dragData.alreadyInflatedView : createView (dragData.newDragData);
+ if (!dragData.isAlreadyInflated) {
+ applyBasicAttributes (view);
}
-
+
+ // Remove from parent if it has a parent
+ view.removeFromParent ();
+ this.viewGroup.addView (view, index);
+
+ if (this.addedListener != null) {
+ this.addedListener.onViewAdded (view);
+ }
+
break;
}
-
- return canHandle;
+
+ return handled;
}
-
+
private void applyBasicAttributes (@NonNull IView view) {
- view.addAttribute(layoutWidthAttr());
- view.addAttribute(layoutHeightAttr());
+ view.addAttribute (layoutWidthAttr ());
+ view.addAttribute (layoutHeightAttr ());
}
-
+
+ /**
+ * Finds the index at which the placeholder will be added
+ * in the current view group.
+ *
+ * @param x The x coordinate of the touch event
+ * @param y The y coordinate of the touch event
+ * @return The index at which the placeholder of the view should be added.
+ * The returned index is always valid index in the {@link #viewGroup}.
+ */
+ private int findPlaceHolderIndex (final float x, final float y) {
+ final var count = this.viewGroup.getChildCount ();
+ for (int i = 0; i < this.viewGroup.getChildCount (); i++) {
+ final var child = this.viewGroup.getChildAt (i);
+ final var rect = getViewRect (child);
+ if (rect.contains (x, y)) {
+ final var top = topHalf (rect);
+ final var bottom = bottomHalf (rect);
+ if (top.contains (x, y)) {
+ return Math.max (0, i - 1);
+ } else if (bottom.contains (x, y)){
+ return Math.min (count, i + 1);
+ }
+ }
+ }
+
+ // If we don't find a suitable index, return the last index
+ return count;
+ }
+
+ @NonNull
+ private RectF getViewRect (@NonNull IView view) {
+ final var v = view.asView ();
+
+ final var rect = new RectF ();
+ rect.left = v.getLeft ();
+ rect.top = v.getTop ();
+ rect.right = rect.left + v.getWidth ();
+ rect.bottom = rect.top + v.getHeight ();
+
+ return rect;
+ }
+
+ private RectF topHalf (RectF src) {
+ final RectF result = new RectF (src);
+ result.bottom -= result.height () / 2;
+ return src;
+ }
+
+ private RectF bottomHalf (RectF src) {
+ final RectF result = new RectF (src);
+ result.top += result.height () / 2;
+ return src;
+ }
+
+ private RectF leftHalf (RectF src) {
+ final RectF result = new RectF (src);
+ result.right -= result.width () / 2;
+ return src;
+ }
+
+ private RectF rightHalf (RectF src) {
+ final RectF result = new RectF (src);
+ result.left += result.width () / 2;
+ return src;
+ }
+
@NonNull
@Contract(" -> new")
- private IAttribute layoutHeightAttr() {
- return new UiAttribute(ANDROID_NS, "layout_height", "wrap_content");
+ private IAttribute layoutHeightAttr () {
+ return new UiAttribute (ANDROID_NS, "layout_height", "wrap_content");
}
-
+
@NonNull
@Contract(" -> new")
- private IAttribute layoutWidthAttr() {
- return new UiAttribute(ANDROID_NS, "layout_width", "wrap_content");
+ private IAttribute layoutWidthAttr () {
+ return new UiAttribute (ANDROID_NS, "layout_width", "wrap_content");
}
-
+
@NonNull
private IView createView (@NonNull UIWidget widget) {
try {
- final var clazz = widget.asClass();
- final var constructor = clazz.getConstructor(Context.class);
- constructor.setAccessible(true);
- final var instance = constructor.newInstance(this.viewGroup.asView().getContext());
-
+ final var clazz = widget.asClass ();
+ final var constructor = clazz.getConstructor (Context.class);
+ constructor.setAccessible (true);
+ final var instance = constructor.newInstance (this.viewGroup.asView ().getContext ());
+
if (instance instanceof ViewGroup) {
- return new UiViewGroup(clazz.getName(), (ViewGroup) instance);
+ return new UiViewGroup (clazz.getName (), (ViewGroup) instance);
} else {
- return new UiView(clazz.getName(), instance);
+ return new UiView (clazz.getName (), instance);
}
} catch (Throwable e) {
- LOG.error("Unable to add widget", e);
- throw new RuntimeException("Unable to add widget", e);
+ LOG.error ("Unable to add widget", e);
+ throw new RuntimeException ("Unable to add widget", e);
}
}
-
- private void removePlaceholderFromParent () {
- if (this.placeholder.getParent() != null) {
- ((ViewGroup) this.placeholder.getParent()).removeView((this.placeholder));
- }
- }
-
+
public static interface OnViewAddedListener {
void onViewAdded (IView view);
}
diff --git a/layout-inflater/src/main/java/com/itsaky/layoutinflater/impl/UiViewGroup.java b/layout-inflater/src/main/java/com/itsaky/layoutinflater/impl/UiViewGroup.java
index b9a664bed..25d2603e4 100755
--- a/layout-inflater/src/main/java/com/itsaky/layoutinflater/impl/UiViewGroup.java
+++ b/layout-inflater/src/main/java/com/itsaky/layoutinflater/impl/UiViewGroup.java
@@ -20,6 +20,7 @@ package com.itsaky.layoutinflater.impl;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.itsaky.layoutinflater.IView;
import java.util.ArrayList;
@@ -59,28 +60,32 @@ public class UiViewGroup extends BaseViewGroup {
@Override
public void removeView(int index) {
-
final IView view = children.get(index);
+ removeView (view);
+ }
+
+ @Override
+ public void removeView(@Nullable IView view) {
+ if (view == null) {
+ LOG.warn ("Null view passed to IViewGroup.removeView(IView). Ignoring.");
+ return;
+ }
- this.viewGroup.removeViewAt(index);
- this.children.remove(index);
+ this.viewGroup.removeView(view.asView());
+ this.children.remove(view);
view.setParent (null);
onViewRemoved(view);
}
- @Override
- public void removeView(@NonNull IView view) {
- this.viewGroup.removeView(view.asView());
- this.children.remove(view);
-
- onViewRemoved(view);
- }
-
@Override
public void removeAll() {
- this.viewGroup.removeAllViews();
- this.children.clear();
+ for (var child : this.children) {
+ // Remove each view individually
+ // This will make sure that we set the parent of the children to null
+ // and call #onViewRemoved (IView)
+ removeView (child);
+ }
}
@Override