I managed to do it by writing some custom C++ that combines the behavior of UButton and SButton with UserWidget. I add this Touch Target Widget into the hierarchy of my widget in place of a Button Widget and it handles clicks, drags, etc…
If you use the precise tap mode of Button, and forward drag events to a UUserWidget that returns a drag drop operation, it seems to work really well.
This little code snippet is a modification of some code in OnMouseButtonDown taken from SButton.cpp
else if (InputClickMethod == EButtonClickMethod::PreciseClick)
{
// Basically here I'm checking if I want to support drag detection in my SWidget that copies a lof of SButton code
if (InteractionState->bDetectsDrag)
{
// We need to detect drag for this tap or click if it's set to
LastPointerDragDetection.Add(MouseEvent.GetPointerIndex(), EffectingButton);
// EffectingButton can just bey EKeys::LeftMouseButton, but I support arbitrary keys
return FReply::Handled().DetectDrag( AsShared(), EffectingButton);
}
// do not capture the pointer for precise taps or clicks
//
return FReply::Handled();
}
Here I call from my Slate widget into a UMG widget that then forwards the event onto a UUserWidget of your choice to return a drag drop operation.
FReply SRDBaseInteractionTargetWidget::OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FKey DragKey = LastPointerDragDetection[MouseEvent.GetPointerIndex()];
// This is a slight hack, but unreal's drag drop behavior of using the current mouse position instead of the starting mouse position feels janky
// This forces the mouse event to think it's at the starting position when you began the drag, so you get good feeling drag drop behavior out of the box for free
// at the expense of possibly breaking things since info about the current cursor positon is lost
FVector2D PressedPos = InteractionKeyStates[DragKey].PressedScreenSpacePosition;
FPointerEvent ModifiedPointerEvent = FPointerEvent(MouseEvent, PressedPos,
PressedPos + (MouseEvent.GetScreenSpacePosition() - MouseEvent.GetLastScreenSpacePosition()));
FReply Reply = ExecuteOnDragDetected(MyGeometry, ModifiedPointerEvent);
if (Reply.IsEventHandled())
{
ReleaseAll();
}
return Reply;
}