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