Ok here the solution I found in c++.
This is surrely improvable but it does the job for me.
I have not implemented a way to manage overlapping, it is not necessary for me now.
This code is owning by the UWidgetComponent I mentionned above
file MyWidgetComponent.h
public:
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
/// In my own code, this is elsewhere in a library for widgets. I put it here for simplicity sake.
// This is pasted from UWidgetTree::ForWidgetAndChildren function,
// But here it allows to go recursively or not
static void ForWidgetAndChildren(UWidget* Widget, TFunctionRef<void(UWidget*)> Predicate, bool Recursive = true);
private:
bool bWidgetIsTouched = false;
FVector2D PrevTouchPos;
FVector2D TouchPos;
void ManageTouchEvent();
bool InWidgetZone(FVector2D TouchPos, UWidget* Widget);
file MyWidgetComponent.cpp
///...
/// associated includes
#include "Blueprint/WidgetTree.h"
#include "Components/Button.h"
#include "Components/CanvasPanelSlot.h"
#include "ExtendUserWidget.h"
#include "Engine/GameViewportClient.h"
#include "Engine/Engine.h"
#include "Slate/SceneViewport.h"
/// ...
void UMyWidgetComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction * ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// No needs to interact if not visible
if (!bVisible) return;
ManageTouchEvents();
}
void UMyWidgetComponent::ManageTouchEvents()
{
APlayerController* Controller = GetWorld()->GetFirstPlayerController();
if (Widget == nullptr) return;
if (Controller == nullptr) return;
if (!GEngine || !GEngine->GameViewport || !GEngine->GameViewport->GetGameViewport()) return;
float TouchX, TouchY;
bool bIsCurrentlyPressed;
Controller->GetInputTouchState(ETouchIndex::Touch1, TouchX, TouchY, bIsCurrentlyPressed);
if (!bIsCurrentlyPressed && bWidgetIsTouched) {
bWidgetIsTouched = false;
UE_LOG(LogTemp, Warning, TEXT("Touch End"));
// Final goal is here
// DispatchOnInputTouchEnd(ETouchIndex::Touch1);
}
if (!bIsCurrentlyPressed) return;
PrevTouchPos = TouchPos;
TouchPos.X = TouchX;
TouchPos.Y = TouchY;
// No interaction here!
if (Widget->GetVisibility() != ESlateVisibility::SelfHitTestInvisible
&& Widget->GetVisibility() != ESlateVisibility::Visible) {
return;
}
if (!InWidgetZone(TouchPos, Widget->GetRootWidget())) return;
if (!bWidgetIsTouched) {
bWidgetIsTouched = true;
UE_LOG(LogTemp, Warning, TEXT("Touch Start"));
// Final goal is here
//DispatchOnInputTouchBegin(ETouchIndex::Touch1);
return;
}
if (!PrevTouchPos.Equals(TouchPos, 0.0001f)) {
UE_LOG(LogTemp, Warning, TEXT("Touch Move"));
}
}
bool UMyWidgetComponent::InWidgetZone(FVector2D TouchPos, UUserWidget * Widget)
{
// No need to go further here
if (Widget->GetVisibility() != ESlateVisibility::SelfHitTestInvisible
&& Widget->GetVisibility() != ESlateVisibility::Visible) {
return false;
}
// Check if in zone
if (Widget->GetVisibility() == ESlateVisibility::Visible) {
FGeometry ViewportGeo = GEngine->GameViewport->GetGameViewport()->GetCachedGeometry();
// This works only for a visible widget.
FGeometry Geo = Widget->GetCachedGeometry();
FVector2D WidgetSize = Geo.GetAbsoluteSize();
FVector2D WidgetPos = Geo.GetAbsolutePositionAtCoordinates(FVector2D(0, 0));
WidgetPos = ViewportGeo.AbsoluteToLocal(WidgetPos);
// Check if touch position is in the widget zone
if (TouchPos > WidgetPos && TouchPos < WidgetPos + WidgetSize) {
return true;
}
}
// If touch position is not on the widget or his visibility is set to SelfHitTestInvisible,
// maybe we have better chance in it's children ?
TArray<UWidget*> AllWidgets;
// Retrieve each direct children to test them
UMyWidgetComponent::ForWidgetAndChildren(Widget, [&AllWidgets](UWidget* ChildWidget) {
AllWidgets.Add(ChildWidget);
}, false);
for (UWidget* SubWidget : AllWidgets)
{
// Go to check recursively in this widget's children
bool bIsInZone = InWidgetZone(TouchPos, SubWidget);
// If a child is hit, no need to go further
if (bIsInZone) {
return true;
} else {
continue;
}
}
return false;
}
void UMyWidgetComponent::ForWidgetAndChildren(UWidget * Widget, TFunctionRef<void(UWidget*)> Predicate, bool Recursive)
{
// Search for any named slot with content that we need to dive into.
if (INamedSlotInterface* NamedSlotHost = Cast<INamedSlotInterface>(Widget))
{
TArray<FName> SlotNames;
NamedSlotHost->GetSlotNames(SlotNames);
for (FName SlotName : SlotNames)
{
if (UWidget* SlotContent = NamedSlotHost->GetContentForSlot(SlotName))
{
Predicate(SlotContent);
if (Recursive) {
ForWidgetAndChildren(SlotContent, Predicate);
}
}
}
}
// Search standard children.
if (UPanelWidget* PanelParent = Cast<UPanelWidget>(Widget))
{
for (int32 ChildIndex = 0; ChildIndex < PanelParent->GetChildrenCount(); ChildIndex++)
{
if (UWidget* ChildWidget = PanelParent->GetChildAt(ChildIndex))
{
Predicate(ChildWidget);
if (Recursive) {
ForWidgetAndChildren(ChildWidget, Predicate);
}
}
}
}
}