Common UI and Keyboard ONLY, no Mouse

TLDR; How would you setup Common UI in a project that should work on a PC that doesn’t have a mouse? For real.

Has anyone figured out a way to disable the mouse input and cursor entirely in a Common UI setup? I’m working on a project that is PC and Console compatible and the UI menus should be keyboard and d-pad navigable only. A mouse or controller cursor should have no business in the UI. I’ve come across a ton of posts touching on the matter pre-Common UI, but nothing seems to work or really cover what I’m trying to accomplish here.

I’ve followed the 3 hr long Epic video on Common UI and have adapted a bunch of UI BP/C++ from the Lyra example so I have a nice setup that switches contexts between PC and Console via input detection and shows platform specific action icons relative the active platform.

It seems no amount of mouse input/cursor disabling props/commands on the Player Controller or on the Activatable Widgets have any impact what so ever. The cursor and it’s movement input will just not go away, nor also do any keyboard WASD or arrow key input actually navigate menu buttons at all.

The closest I have come, which isn’t quite good enough, is un-checking “Supports Mouse and Keyboard” under the Windows Platform Input in Common UI Input Settings, along with a Default Input Type of Gamepad. It removes the mouse cursor entirely and allows keyboard input navigation, along with controller d-pad input navigation, which is great, but the context switching between PC and Console doesn’t happen any more, as evident by the Common Action Widget icons remaining Console related. Also, mouse and controller joystick input still seem to navigate button selection, despite their cursors being not visible.

I’ve been digging around the engine code trying to figure out some options. I get a sense that tinkering with UInputBehaviorSet and/or UInputRouter might be of interest but I lose the trail trying to connect them back to anything I can adjustable or override, such as the UCommonGameViewportClient or Player Controller.

Any advice or tips would be greatly appreciated!

1 Like

Alright, I think I figured it out. A little hackier than I’d preferer but it does the job.

There are a few parts to it.

  1. Hiding mouse cursor and blocking mouse movement input (from hovering over buttons.)
    In the Widget Stack Layout (See above video and/or Lyra Project,) add a full screen Canvas with Visibility: Visible and Cursor: None.

  2. Transferring button focus to button selection, and blocking mouse click from robbing focus
    In your button widgets base class
    .h

UCLASS(Abstract, meta = (DisableNativeTick))
class TRIFECTA_API UTrifectaCommonButton : public UCommonButtonBase
{
    GENERATED_BODY()

public:
#if PLATFORM_DESKTOP || WITH_EDITOR
    virtual void NativeOnAddedToFocusPath(const FFocusEvent& InFocusEvent) override;

    virtual void NativeOnRemovedFromFocusPath(const FFocusEvent& InFocusEvent) override;

    virtual void NativeOnFocusChanging(const FWeakWidgetPath& PreviousFocusPath, const FWidgetPath& NewWidgetPath, const FFocusEvent& InFocusEvent) override;
#endif
};

.cpp

#if PLATFORM_DESKTOP || WITH_EDITOR

void UTrifectaCommonButton::NativeOnAddedToFocusPath(const FFocusEvent& InFocusEvent)
{
    if (InFocusEvent.GetCause() != EFocusCause::Mouse)
    {
        SetSelectedInternal(true);

        Super::NativeOnAddedToFocusPath(InFocusEvent);
    }
}

void UTrifectaCommonButton::NativeOnRemovedFromFocusPath(const FFocusEvent& InFocusEvent)
{
    if (InFocusEvent.GetCause() != EFocusCause::Mouse)
    {
        SetSelectedInternal(false);

        Super::NativeOnRemovedFromFocusPath(InFocusEvent);
    }
}

void UTrifectaCommonButton::NativeOnFocusChanging(const FWeakWidgetPath& PreviousFocusPath, const FWidgetPath& NewWidgetPath, const FFocusEvent& InFocusEvent)
{
    if (InFocusEvent.GetCause() != EFocusCause::Mouse)
    {
        Super::NativeOnFocusChanging(PreviousFocusPath, NewWidgetPath, InFocusEvent);
    }
    else
    {
        SetKeyboardFocus();
    }
}

#endif
  1. Adding Selected variations to your Button Style

Before

After

Selected Base is the new Normal Hovered
Selected Pressed is the new Normal Pressed

2 Likes

thanks my dude it worked perfectly <3

Btw in case anyone wants to support mouse also, I found a solution and verified it works in production

Mostly when you click outside any widget, Viewport receives focus thats not what we want, to prevent such behavior:

  1. Create button class inherited from UCommonButtonBase with NativeOnFocusChanging
// UBBaseCommonButton.cpp 
class BOGATYR_API UBBaseCommonButton : public UCommonButtonBase, public IUserObjectListEntry
{
	GENERATED_BODY()

public:
#if PLATFORM_DESKTOP || WITH_EDITOR
    virtual void NativeOnFocusChanging(const FWeakWidgetPath& PreviousFocusPath, const FWidgetPath& NewWidgetPath, const FFocusEvent& InFocusEvent) override;
#endif
};
  1. in NativeOnFocusChanging check that FFocusEvent was caused by mouse and new focused widget type is SViewport or for editor - SPIEViewport and prevent such event by focusing back button, not ideal but good solution in my case
    .h
// UBBaseCommonButton.h

#if PLATFORM_DESKTOP || WITH_EDITOR
void UBBaseCommonButton::NativeOnFocusChanging(const FWeakWidgetPath& PreviousFocusPath, const FWidgetPath& NewWidgetPath, const FFocusEvent& InFocusEvent)
{
    TSharedRef<SWidget> NewWidget = NewWidgetPath.GetLastWidget();
    TWeakPtr<SWidget> PreviousWidget = PreviousFocusPath.GetLastWidget();
    FName WidgetType = NewWidget.Get().GetType();

    if (InFocusEvent.GetCause() == EFocusCause::Mouse
        && (WidgetType == FName("SPIEViewport") || WidgetType == FName("SViewport")))
    {
        SetKeyboardFocus();
        return;
    }
    Super::NativeOnFocusChanging(PreviousFocusPath, NewWidgetPath, InFocusEvent);
}
#endif
4 Likes

Thanks man, works great, but you don’t need to add a canvas, just set visible and cursor none for overlay widget, works too.