Thanks for wasting a few days of my time Epic. With the lack of documentation and the layers of junk forming the UI system. This is what I figured out:
Normally you have input actions set up for certain keys. A UserWidget holds an input component which can be added and removed to a PlayerController’s input stack.
PC->PushInputComponent(GetManagedInputComponent());
At first it would seem logical to bind a method on the widget to an input action through this component.
InputComponent->BindAction(INPUTACTIONMAPPING_NavBack, EInputEvent::IE_Released, this, &UMyWidget::ActOnNavBack);
This is not the case because of how input is routed. Input component:
-
Do not prioritize based on what widget is focused and in what order.
-
Do only respect a manually set priority if it consumes the input.
-
Do execute when you type in an editable text widget.
-
Do use the same stack on the controller as any other component.
We could write an extension to a widget to deal with prioritizing by updating the priority when a widget changes on the focus path. 1, 2 and 3 can not be dealt with at this level. This makes the input component on the UUserWidget effectively garbage. The proper way to implement input seems to be by overriding the UserWidget methods such as “NativeOnKeyDown” and the other relevant methods, which are quite a few. Because we don’t want to hardcode any keys on this level, we would still have to manually compare if a key argument exists in any input action before we can even decide how to proceed.
Tell me there is a better way than this -_- it offends me:
// Input
FReply UThatWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) {
if (CanFindAndExecuteInputAction()) {
FEventReply EventReply = FindAndExecuteInputAction(UHIDUtils::GetInputChordFromKeyEvent(InKeyEvent), true);
if (!EventReply.NativeReply.IsEventHandled()) {
EventReply.NativeReply = Super::NativeOnKeyDown(InGeometry, InKeyEvent);
}
return EventReply.NativeReply;
}
return FReply::Unhandled();
}
FReply UThatWidget::NativeOnKeyUp(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) {
if (CanFindAndExecuteInputAction()) {
FEventReply EventReply = FindAndExecuteInputAction(UHIDUtils::GetInputChordFromKeyEvent(InKeyEvent), false);
if (!EventReply.NativeReply.IsEventHandled()) {
EventReply.NativeReply = Super::NativeOnKeyUp(InGeometry, InKeyEvent);
}
return EventReply.NativeReply;
}
return FReply::Unhandled();
}
FReply UThatWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) {
if (CanFindAndExecuteInputAction()) {
FEventReply EventReply = FindAndExecuteInputAction(UHIDUtils::GetInputChordFromPointerEvent(InMouseEvent), true);
if (!EventReply.NativeReply.IsEventHandled()) {
EventReply.NativeReply = Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);
}
return EventReply.NativeReply;
}
return FReply::Unhandled();
}
FReply UThatWidget::NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) {
if (CanFindAndExecuteInputAction()) {
FEventReply EventReply = FindAndExecuteInputAction(UHIDUtils::GetInputChordFromPointerEvent(InMouseEvent), false);
if (!EventReply.NativeReply.IsEventHandled()) {
EventReply.NativeReply = Super::NativeOnMouseButtonUp(InGeometry, InMouseEvent);
}
return EventReply.NativeReply;
}
return FReply::Unhandled();
}
FReply UThatWidget::NativeOnMouseWheel(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) {
if (CanFindAndExecuteInputAction()) {
FEventReply EventReply = FindAndExecuteInputAction(UHIDUtils::GetInputChordFromPointerEvent(InMouseEvent), true);
if (!EventReply.NativeReply.IsEventHandled()) {
EventReply.NativeReply = Super::NativeOnMouseWheel(InGeometry, InMouseEvent);
}
return EventReply.NativeReply;
}
return FReply::Unhandled();
}
FReply UThatWidget::NativeOnMouseButtonDoubleClick(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) {
if (CanFindAndExecuteInputAction()) {
FEventReply EventReply = FindAndExecuteInputAction(UHIDUtils::GetInputChordFromPointerEvent(InMouseEvent), true);
if (!EventReply.NativeReply.IsEventHandled()) {
EventReply.NativeReply = Super::NativeOnMouseButtonDoubleClick(InGeometry, InMouseEvent);
}
return EventReply.NativeReply;
}
return FReply::Unhandled();
}
// Add a sh*t ton of other checks for gestures / voice etc??