Interaction conflict between CommonInput mappings and standard widget navigation

We are in a situation where an UMG panel uses standard widget navigation (gamepad left stick) + validation (A/FaceButtonDown button), and at the same time, a global CommonButtonBase button is set to react on an InputAction using the A button in hold mode.

The problem is that the A button no longer works to press navigable widgets with another button on screen mapped to react on the A+Hold. If we change the A+Hold on something else (Y+Hold for instance), the navigable widgets can be pressed again with A.

I am not sure to start to fix that…

Please see the attached screenshot. The Finish button is mapped to an input action DataTable, using FaceButtonDown + Hold. The more info Call to Action is just a fake button, the A input should be handled through the standard widget navigation/validation, pressing A for the focused widget.

Thanks!

Mathieu

Hi,

CommonUI has it’s own universal Click/Back bindings that you can set up, I think the best option here will be to bind A to the Click action so that it can route through the CommonUI router instead of relying on the native Slate navigation. There’s some info in section 3 of the CommonUI docs, can you try binding that button to the default click action and see if that solves things for you? I think it should be fine to have separate bindings for the hold and non-hold version of the same key, that binding should “click” the invisible cursor that we use in CommonUI to hover the focused button (which is slightly different than “pressing” it via a key, but the end result should be the same). Let me know if that doesn’t work from you and we can see if there’s a workaround to try.

Best,

Cody

Hey Cody,

Thanks for your answer!

I would like to try your solution, but the problem is that some of the widget blueprints I use inherit CommonUserWidget, not CommonButtonBase. They are part of a catalog of reuasable components for our session setup (rotators, texfields, sliders, etc). I currently use NativeOnMouseButtonDown on these. How would you make them evolve to work like the CommonUi Button? Looking at the code of CommonButtonBase, it seems it hooks up to the events of an embedded button (UCommonButtonInternalBase). So do I add an internal button to all my ommonUserWidgets?

Thanks!

Mathieu

Hey Cody,

Thanks for your answer!

So I was a bit afraid of the codebase you mentionned. So I started with your proposed UCommonButtonBase inheritance idea. It works but requires a bit of effort: in order to work, I have to add a, input action binding from a datatable for all my modules. The problem is that when A is pressed, they all rect to it. So I have to filter and check that a module if the focused one before I start to respond to the On Clicked event. It is doable though, maybe I can try to install/uninstall the input action when the widgets get focus / unfocused, to have a simpler C++ solution, and avoid the branch in every blueprint graph.

Before I applied this to every widget of my hierarchy, I still tried to debug FCommonAnalogCursor::HandleKeyDownEvent, adding a selective breakpoint to pressing EKeys::Gamepad_FaceButton_Bottom. Going step by step, I found out that FActionRouterBindingCollection::ProcessHoldInput is setting the result to handled, by finding A+Hold binding in the ActionBindings container. See attached image.

I am not sure what is best to do from here. Should I modify the code and add a test to check if the Key is Gamepad_FaceButton_Bottom and not consider it handled?

`Binding->BeginHold();
if (Key != EKeys::Gamepad_FaceButton_Bottom)
{
ProcessResult = EProcessHoldActionResult::Handled;
}

Binding->OnHoldActionPressed.Broadcast();`

Or is the UCommonButtonBase scheme better?

Thanks!

Mathieu

So I have tried to add that “if”, but it does not work: the default A press of the widget will happen as well as the A hold. I suppose that in this case, the default A validation should only be triggered when A is released before the Hold duration is reached. But I am not sure how to do that right.

Hi,

That’s what we’re trying to handle in UCommonUIActionRouterBase::ProcessInput by sending a new event if the hold “fails”:

`const auto ProcessInputOnActionRouter = [&ProcessHoldInputFunc, &ProcessNormalInputFunc, InputEvent](const UCommonUIActionRouterBase& ActionRouter)
{
EProcessHoldActionResult ProcessHoldResult = ProcessHoldInputFunc(ActionRouter);
if (ProcessHoldResult == EProcessHoldActionResult::Handled)
{
return true;
}

if (ProcessHoldResult == EProcessHoldActionResult::GeneratePress)
{
// A hold action was in progress but quickly aborted, so we want to generate a press action now for any normal bindings that are interested
ProcessNormalInputFunc(ActionRouter, IE_Pressed);
}

// Even if no widget cares about this input, we don’t want to let anything through to the actual game while we’re in menu mode
return ProcessNormalInputFunc(ActionRouter, InputEvent);
};`However, it seems that at this point we’re already handling it in the router, so we’d need a regular binding to handle the fallback event (that is, we can’t rely on the virtual click). What we would *like* to happen here is something like this:

  1. The face button is pressed, and a hold action begins
  2. The face button is released early, and the hold action is cancelled. A press action is sent.
  3. The press action is unhandled (nothing is bound to the bottom face button), so the analog cursor sends it’s click

The problem seems to be the disconnect between the hold action and the generated press action. The hold will tell the analog cursor that it’s handling the event (initially), and the new generated press event doesn’t originate from the cursor. I’m chatting with the team now to see if we can come up with a good long-term fix here, either by sending an event to the cursor when a hold action is cancelled or by having the cursor track button states (it should still receive a key up for that face button, so it could send the corresponding down+up). If you’re looking for a shorter term workaround, you could try adding a flag to CommonAnalogCursor to track the accept button’s state. Then, in FCommonAnalogCursor::HandleKeyUpEvent if the flag hasn’t been set (i.e. you never called FAnalogCursor::HandleKeyDownEvent), you could send it as part of the key up event. Something like this:

// We support binding actions to the virtual accept key, so it's a special flower that gets processed right now const bool bIsVirtualAccept = InKeyEvent.GetKey() == EKeys::Virtual_Accept; if (bIsVirtualAccept && ActionRouter.ProcessInput(InKeyEvent.GetKey(), IE_Released) == ERouteUIInputResult::Handled) { return true; } else if (!bIsVirtualAccept || ShouldVirtualAcceptSimulateMouseButton(InKeyEvent, IE_Released)) { if (!bHandledVirtualAcceptDown) { UCommonInputSubsystem& InputSubsytem = ActionRouter.GetInputSubsystem(); InputSubsytem.SetIsGamepadSimulatedClick(bIsVirtualAccept); bool bReturnValue = FAnalogCursor::HandleKeyDownEvent(SlateApp, InKeyEvent); InputSubsytem.SetIsGamepadSimulatedClick(false); } return FAnalogCursor::HandleKeyUpEvent(SlateApp, InKeyEvent); }I think that should work for you since nothing should be handling that KeyUp event on the accept button, but let me know if you run into any issues. If we’re able to submit a proper fix, I’ll send it your way.

Best,

Cody

Hey Cody!

Thanks for the enquiry and the search for a fix.

So I have tried implementing your solution. I have added the bool member bHandledVirtualAcceptDown to the class, copied your code fragment to FCommonAnalogCursor::HandleKeyUpEvent. But I am completely over my head to determine where to set bHandledVirtualAcceptDown to true and when to set it to false. I have tried different attempts to set it to true in FAnalogCursor::HandleKeyDownEvent, but the result is either the virtual accept no longer works in any screen, or the situation is the same as before (Hold + A overrides virtual accept).

If you could please pinpoint to me where to set it to true and false, that would be great!

Thanks!

Mathieu

Hi,

Here’s what I had in mind, though I haven’t had a chance to test it. In FCommonAnalogCursor::HandleKeyDownEvent we want to indicate that there is a “handled” key down even that is active:

if (bIsVirtualAccept && ActionRouter.ProcessInput(InKeyEvent.GetKey(), InputEventType) == ERouteUIInputResult::Handled) { bHandledVirtualAcceptDown = true; return true; }Then in FCommonAnalogCursor::HandleKeyUpEvent we deal with the case where the keydown was handled (consumed) but the keyup was not (note that I updated this snippet a bit):

// We support binding actions to the virtual accept key, so it's a special flower that gets processed right now const bool bIsVirtualAccept = InKeyEvent.GetKey() == EKeys::Virtual_Accept; if (bIsVirtualAccept && ActionRouter.ProcessInput(InKeyEvent.GetKey(), IE_Released) == ERouteUIInputResult::Handled) { bHandledVirtualAcceptDown = false; return true; } else if (!bIsVirtualAccept || ShouldVirtualAcceptSimulateMouseButton(InKeyEvent, IE_Released)) { if (bIsVirtualAccept && bHandledVirtualAcceptDown) { UCommonInputSubsystem& InputSubsytem = ActionRouter.GetInputSubsystem(); InputSubsytem.SetIsGamepadSimulatedClick(bIsVirtualAccept); bool bReturnValue = FAnalogCursor::HandleKeyDownEvent(SlateApp, InKeyEvent); InputSubsytem.SetIsGamepadSimulatedClick(false); bHandledVirtualAcceptDown = false; } return FAnalogCursor::HandleKeyUpEvent(SlateApp, InKeyEvent); }What I’m hoping is that the key up event will be unhandled by the hold binding, so we’ll catch it here as a key up event with no corresponding key down. Then, we can send through the virtual click. This should work since we send the KeyUp event as a normal input event when the hold action is cancelled:

`// Even if no widget cares about this input, we don’t want to let anything through to the actual game while we’re in menu mode
bHandledInput = ProcessNormalInputFunc(InputEvent);
}

if (bHandledInput)
{
return ERouteUIInputResult::Handled;
}
return CanProcessNormalGameInput() ? ERouteUIInputResult::Unhandled : ERouteUIInputResult::BlockGameInput;`Hopefully that works as a temporary workaround!

Hey Cody,

Guinea pig reporting here: the fix works!

Thanks a lot! I suppose you can ask your team to implement the real fix, but it seems safe so far.

Thanks again,

Mathieu

Ah I see. I don’t think it should be necessary to add new button widgets, perhaps we can track down why the virtual click isn’t making it through to the widget. The CommonAnalogCursor is hooked up as a preprocessor so any button presses will hit FCommonAnalogCursor::HandleKeyDownEvent before being processed by the input router. Here we filter out events based on if the router handled them:

if (bIsVirtualAccept && ActionRouter.ProcessInput(InKeyEvent.GetKey(), InputEventType) == ERouteUIInputResult::Handled) { return true; } else if (!bIsVirtualAccept || ShouldVirtualAcceptSimulateMouseButton(InKeyEvent, IE_Pressed)) { // click virtual mouseIn order for this event to still be processed as a click, we would need the input binding to return ERouteUIInputResult::BlockGameInput (or unhandled). Alternatively, we do send a press event immediately when we determine a hold event wasn’t held long enough, though I don’t know if that helps here since we need to route the event back to the analog cursor vs. triggering a different binding. It might be worth dropping a breakpoint in UCommonUIActionRouterBase::ProcessInput to see where it’s being flagged as handled, since I would expect the event to be considered unhandled in a case where the only action processing it is a hold action that didn’t complete. Otherwise, you could look at refactoring the widgets to be CommonButtons so that you could bind the face button directly and not rely on native mouse events, though that may be a larger undertaking.

The other option here if you’re looking for a quicker workaround would be to modify CommonAnalogCursor to ensure it still sends the click event regardless of the existence of a binding on the same button. The concern there would be that you don’t really know that you want to send the mouse click until the hold action has been cancelled, so it might take a bit of extra processing to avoid firing both actions at once.

Best,

Cody