How to drag / drop from ScrollBox

I setup a basic drag drop scenario by making a “drop site” blueprint, basically just a square image with C++ handling NativeOnDrop. The other piece of the puzzle is a “draggable item” blueprint, which handles the NativeOnMouseButtonDown and NativeOnDragDetected.

So, this works just fine.

However, long term, I need to have something like this:

I basically need two slots and then an “ItemList” (a UserWidget) below. The list is wrapped in a UserWidget that houses a ScrollBox and HorizontalBox. When I drag an item down onto the ItemList the NativeOnDrop is never called!

After playing around a little, I found that if I set the ScrollBox’s Visibility to SelfHitTestInvisible, then I can properly drop items onto the list. However, that’s not going to work because I also need to be able to scroll the list!

Can someone please tell me how to have a scrolling list which will allow me to drag items onto it and off of it?

thanks.

Hi Dan,

My assumption is that the scroll box is capturing the input before it can get down to the ItemList. There’s a few solutions you could try out.

One option is to toggle the visibility of the scroll box during a drag. Since you don’t need to interact with it mid drag, you could set it to SelfHitTestInvisible when a drag starts and back to Visible when a drag ends. That should let you pass through it and down to your ItemList.

Another, maybe cleaner option would be to go ahead and let your scroll box receive the drop event and handle it there. You could pass things down to your ItemList from there instead of having the ItemList receive it directly.

Best,

Cody

Thanks for your response, Cody.

So I actually think that I tracked this down to SlateApplication not properly handling touch events in conjunction with drag drop operations.

Here is code from SlateApplication at line 4632:

FReply TempReply = FReply::Unhandled();
			if (Event.IsTouchEvent())
			{
				// Previous code that didn't handle drag drop
				// TempReply = CurWidget.Widget->OnTouchEnded( CurWidget.Geometry, Event );

				// My proposed code which mimics the drag drop check present in the "if" statement below
				TempReply = (bIsDragDropping)
					? CurWidget.Widget->OnDrop(CurWidget.Geometry, FDragDropEvent(Event, LocalDragDropContent))
					: CurWidget.Widget->OnTouchEnded(CurWidget.Geometry, Event);
			}
			if (!Event.IsTouchEvent() || (!TempReply.IsEventHandled() && bTouchFallbackToMouse))
			{
				TempReply = (bIsDragDropping)
					? CurWidget.Widget->OnDrop( CurWidget.Geometry, FDragDropEvent( Event, LocalDragDropContent ) )
					: CurWidget.Widget->OnMouseButtonUp( CurWidget.Geometry, Event );
			}
			return TempReply;	

Basically, the touch event was going to the SScrollBox through OnTouchEnded( ) and getting sent back as Handled. This was ignoring the fact that a drag drop was occurring. If you look in the 2nd “if” statement (e.g. !Event.IsTouchEvent( ) ), that code checks to see if a drag drop operation is occurring and appropriately calls OnDrop( ) instead of OnMouseButtonUp( ).

I’m not an expert on SlateApplication, but I think that the code handling touch should have that exact same logic. And in fact, putting that code in (my fix is shown in the code above), seems to fix my issue.

So, the first widget to get the event is the sscrollBox, it now gets an OnDrop( ) called and sends it back as UnHandled( ), which is desired. Then the event goes to a couple canvas constraint slate widgets and finally up to my ItemList userwidget where I handle it appropriately.

Cody, can you bounce this around with some engineers over there or create a bug report on this?

thanks.

Hi Cody,

The original question pertained to dragging INTO a scroll box, but, I also wanted to bring up the big problem of dragging an item OUT OF the scrollbox.

Again for clarity here is my hierarchy:

  • ItemList (user widget)
    • ScrollBox
      • HorizontalBox
        • Draggable Item
        • Draggable Item

Right now, dragging an item OUT OF the scrollbox works fine, but we also want to maintain the ability for the user to scroll the list. The problem is that when the user goes to scroll the list, they end up just peeling off the draggable item which they have their finger on.

In past games (UE3 / Scaleform) we would do this:

  • User scrolls parallel to list → Scroll List
  • User scrolls perpendicularly to list → Peel off item and start drag operation

With how established the slate code base is, making a change like this seems really difficult.

An alternative would be that the scrollbox would always scroll and a long press on an item would “pop” it off. However, that actually seems pretty complicated to implement as well:

  1. Draggable object’s NativeOnMouseDown could start a timer for 0.2 seconds.
  2. After the timer is up, create a drag and drop op.
  3. Oh shoot, dead end, because the only way to create a drag drop op is to create one in NativeOnDragDetected which passes the DragDropOperation back to the SObjectWidget::OnDragDetected which passes it back in an FReply.

I don’t know… Do you have any thoughts on how we might achieve what we are looking for?

Hi Dan,

Looks like you did indeed run into a bug. I’ve filed a bug report UE-34709 and submitted a fix for 4.14 (CL# 3089667). Feel free to pull down that change if you want, and let me know if it causes anything unexpected. I’ll respond to your other comment shortly.

thanks for confirming

You may want to ditch using the ScrollBox for this purpose, it’s really there for the simple usecases, with this many interconnected concerns you may need a widget with more knowledge. You probably want to take the Tile/ListView wrapper which is marked as experimental, and make your own version based on that. It has much better support for handling the kinds of cases you’re going to need to care about. For example, do you always want to insert at the end of the list? Or do you want the ability to determine the zone, so that you can insert between two items?

Up to you though - you may want to just make a brand new scrolling panel that does exactly what you want if you can’t bend them to your will - let me know though, I’d love to improve these controls so their better suited for mobile out of the box of possible.

But I think there’s a way you can make the scrollbox work, will take some improvements to the scrollbox, and maybe a new API to slate/widgets:

Scrolling + Dragging based on direction is going to be tricky. Thinking about it, my gut says there needs to be a new flag when capturing the ‘mouse’.

The first problem is, the item HAS to capture the mouse. There’s no way around it. Not capturing the mouse on the item, opens up a whole host of frustrations from users feeling like they Dragged A, but instead B gets dragged, because your finger drifted onto another item during the swipe, or left before drag was detected.

The issue that opens up, is that now because the item captured the mouse, the scrollbox can’t scroll. Not only that, if the item says, nope, not a drag, nevermind, it’s now too late for the scrollbox to function the way it normally would, and begin scrolling. But it could be patched to correctly deal with the case.

The flow (in my head) would basically run like this,

Item gets mouse down - Captures mouse
Item gets movement - monitors for directional, are they dragging up and out?
A) Item Detects intent to drag, issues DetectDrag, waits for DragDetection callback. Stops looking for intent.
B) Item fails to identify intent, and releases mouse capture.

IF B) Scrollbox, suddenly starts seeing mouse movement data, BUT it does not have mouse capture, and it never received a mouse down. This is the critical bit - what should be the behavior of the scrollbox? Because normally you don’t want a scrollbox to start moving just because someone pressed the mouse down outside of it, and then dragged into it, that would be terrible.

My gut says there needs to be a way parents of mouse capturing widgets need to some how remain notified of events occurring over them, but not through some path they would mistaken as being something they could handle.

You might be able to get PreviewMouseButtonDown, to assist in this area, as it could be used on a scrollbox to know when there’s intent to press coming, but if I remember correctly, there’s no way of accurately knowing when the capturing widget releases capture. There almost needs to be an event like, ChildWidgetCaptureBegin/ChildWidgetCaptureEnd, so you’re in the know when they release it, and can then pick it up, in the case of the scrollbox.

I don’t believe we simulate a mouse enter/leave on touch devices, so I don’t think you’d be able to rely on that.

i solved with this

Thanks for offering this - unfortunately it’s a 100% workaround as sometimes the Y value is a little less than this and then the user cannot drag out. If it’s lowered anymore then often the user cannot scroll :frowning:

Did anyone find a solid solution for dragging operation items from a scrollbar?

1 Like