2 years have passed, is this method still valid? Thanks
I made a button that based on CommonBoundActionButton, and put it into my menu which is a CommonActivatableWidget, but when I push it onto the stack, the hovered and unhovered events did not work for keyboard. What do I need to do? Thanks!
Did you ever figure out the TODO? Its still not working and my focus is all over the place because Epic games cant make a good systemâŚ
This workaround still works like a charm, but there is another issue, when you navigate to the last button and press down arrow key, it goes to the first button, but somehow it doesnât respond to confirm
Also tagging @Patterson in this:
But⌠when i hover one with the mouse and tap a keyboard key⌠i have two hovered buttonsâŚ
I found a guide somewhere suggesting making changes to UGameViewportClient, ie. NOT possible to really solve without C++. What I did was create a custom viewport client class (now I guess it should inherit from the Common UI viewport client instead) :
UCLASS()
class GF_UI_API UGF_GameViewportClientBase : public UGameViewportClient
{
GENERATED_BODY()
public:
void ShowMouseCursor();
void HideMouseCursor();
virtual EMouseCursor::Type GetCursor(FViewport* InViewport, int32 X, int32 Y) override;
private:
bool mShowMouseCursor = true;
};
And implementation:
void UGF_GameViewportClientBase::ShowMouseCursor()
{
mShowMouseCursor = true;
FSlateApplication::Get().OnCursorSet(); // SlateApplication calls ::GetCursor() on this class.
}
void UGF_GameViewportClientBase::HideMouseCursor()
{
mShowMouseCursor = false;
FSlateApplication::Get().OnCursorSet(); // SlateApplication calls ::GetCursor() on this class.
}
EMouseCursor::Type UGF_GameViewportClientBase::GetCursor(FViewport* InViewport, int32 X, int32 Y)
{
// Since this function is called whenever the mouse is _moved_, that would be a good time to start
// showing the cursor again if it was hidden. The check is needed because ::HideMouseCursor
// calls on the SlateApplication which forces this function to be called exactly once. And that
// hides the cursor, until the user starts moving the mouse.
if (!mShowMouseCursor)
{
mShowMouseCursor = true;
return EMouseCursor::None;
}
return Super::GetCursor(InViewport, X, Y);
}
Itâs probably not perfect, but it works. Only problem is that even when the cursor is hidden it has still hit-testing enabled. So it can be invisible and hover over a button, which means that the user will see two âactiveâ buttons when using keyboard keys.
Iâm still looking for a good solution to that second half of my problem. Any hints are appreciated!
Hi there, have found out the solution? Thanks!
Itâs ridiculous that there is no way to apply hover styles when button is focused without jiggling with two Button Style Blueprints. There is just no way to do that in C++ because all styles and functions that apply them are just private.
Hello all. After spending a week researching solutions to this problem, including this thread, I have put together a combination of some of the best ideas (this tutorial included) as well as my own solutions to get a fully functioning Keyboard+Mouse+Gamepad common button.
The only downside is it requires not just one but two additional ButtonStyles (unlike @Pattersonâs solution which required one additional). I may tackle this in the future, but unfortunately CommonUI has a lot of this functionality locked down tight in private code sections, so I have simply labeled this as todo.
This solution requires C++ and is a C++ adaptation of Pattersonâs implementation, so I apologize in advance to you blueprint only users. Unfortunately there is no way to replicate some of this in blueprint only.
Hopefully this well help some people, and maybe some keen eyed engineers may be able to even improve upon it.
Youâll notice in some places where I am checking the player controller against index 0. This is in case you want to avoid local multiplayer widgets belonging to other players interfering with the mouse and keyboard. If you donât need those checks you can leave them out. I havenât fully tested local multiplayer either so if anyone does this please report back.
With this Solution you will probably need to include Slate, SlateCore, CommonUI, and CommonInput in your additional dependencies. One improvement may be to use an enum for Normal, Hovered, and Pressed states and expose it to blueprint, in case you want to use these styles for text or something like that. Or you could simply get the current style which should be up to date by the time blueprints are called anyway. Just one thing to be aware of (and that I did not test around).
Edit: I will continue to make some edits as I make improvements to or simplify this code
Edit 2: Well I guess this still has a few problems. But it does seem better than solutions so far.
MyCommonButtonBase.h:
#pragma once
#include "CoreMinimal.h"
#include "CommonButtonBase.h"
#include "MyCommonButtonBase.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyCommonButtonDelegate, UMyCommonButtonBase*, MyCommonButton);
/**
* Class to get around Common UI Buttons being Difficult at Keyboard events
*/
UCLASS()
class My_API UMyCommonButtonBase : public UCommonButtonBase
{
GENERATED_BODY()
public:
UMyCommonButtonBase();
// Use this to determine which widget to pick in UCommonActivatableWidget::GetDesiredFocusTarget
UPROPERTY(BlueprintAssignable)
FMyCommonButtonDelegate OnFocused;
protected:
// Begin UUserWidget Overrides
virtual void NativeOnInitialized() override;
virtual void NativeOnAddedToFocusPath(const FFocusEvent& InFocusEvent) override;
virtual void NativeOnRemovedFromFocusPath(const FFocusEvent& InFocusEvent) override;
virtual void NativeOnHovered() override;
virtual void NativeOnUnhovered() override;
virtual FReply NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
virtual FReply NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override;
virtual FReply NativeOnKeyUp(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override;
// End UUserWidget Overrides
// This should be identical to the Set Style, except with the normal state equal to
// the hovered state
UPROPERTY(EditAnywhere, Category = Style)
TSubclassOf<UCommonButtonStyle> HoveredStyle;
// This should be identical to the Set Style, except with the normal AND hovered state
// equal to the pressed state
UPROPERTY(EditAnywhere, Category = Style)
TSubclassOf<UCommonButtonStyle> PressedStyle;
UPROPERTY(Transient)
TSubclassOf<UCommonButtonStyle> DefaultStyle;
UPROPERTY(Transient)
FDataTableRowHandle DefaultTriggeringInputAction;
UPROPERTY(Transient)
FDataTableRowHandle NullTriggeringInputAction;
};
MyCommonButtonBase.cpp:
#include "MyCommonButtonBase.h"
#include "Kismet/GameplayStatics.h"
DECLARE_LOG_CATEGORY_EXTERN(LogMyCommonButtonBase, Log, All);
DEFINE_LOG_CATEGORY(LogMyCommonButtonBase);
UMyCommonButtonBase::UMyCommonButtonBase()
{
// Default to focusable because a non-focusable button doesn't make any sense
SetIsFocusable(true);
}
void UMyCommonButtonBase::NativeOnInitialized()
{
// Store the TriggeringInputAction and then disable it for now
DefaultTriggeringInputAction = TriggeringInputAction;
TriggeringInputAction = NullTriggeringInputAction;
// Store the Default Style
DefaultStyle = Style;
// TODO: Is there a way we can generate these styles so the user doesn't have to
// create a bunch of duplicate styles? Most of this functionality is set to private
// in CommonUI so it seems difficult short of modifying CommonUI directly, which is
// not portable.
if (!IsValid(HoveredStyle))
{
UE_LOG(LogMyCommonButtonBase, Warning, TEXT("%s: HoveredStyle is null. Did you forget to set it?"), *GetName());
// This is a fallback in case the user forgot to set a Hovered Style
HoveredStyle = DefaultStyle;
}
if (!IsValid(PressedStyle))
{
UE_LOG(LogMyCommonButtonBase, Warning, TEXT("%s: PressedStyle is null. Did you forget to set it?"), *GetName());
// This is a fallback in case the user forgot to set a Pressed Style
PressedStyle = DefaultStyle;
}
}
void UMyCommonButtonBase::NativeOnAddedToFocusPath(const FFocusEvent& InFocusEvent)
{
if (InFocusEvent.GetCause() == EFocusCause::SetDirectly)
{
// Prevent infinitely recurring focus path events from manually setting focus
return;
}
Super::NativeOnAddedToFocusPath(InFocusEvent);
// This will activate our input action (can press)
TriggeringInputAction = DefaultTriggeringInputAction;
// Simulate hovered style
SetStyle(HoveredStyle);
OnFocused.Broadcast(this);
}
void UMyCommonButtonBase::NativeOnRemovedFromFocusPath(const FFocusEvent& InFocusEvent)
{
Super::NativeOnRemovedFromFocusPath(InFocusEvent);
// This will deactivate our input action (cannot press)
TriggeringInputAction = NullTriggeringInputAction;
// Restore style to the default state
SetStyle(DefaultStyle);
}
void UMyCommonButtonBase::NativeOnHovered()
{
Super::NativeOnHovered();
// Set the keyboard focus as well
if (GetOwningLocalPlayer()->GetPlatformUserIndex() == 0)
{
OnFocused.Broadcast(this);
SetKeyboardFocus();
}
}
void UMyCommonButtonBase::NativeOnUnhovered()
{
if (HasKeyboardFocus())
{
// There is no handy Clear Keyboard or User focus event so we need to go through Slate
FSlateApplication::Get().ClearKeyboardFocus();
}
Super::NativeOnHovered();
}
FReply UMyCommonButtonBase::NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
FReply Reply = Super::NativeOnMouseMove(InGeometry, InMouseEvent);
// OnMouseMove triggers even if the delta is zero, so just double check that we actually
// moved the mouse. It may also trigger if we aren't hovering over this widget so use our
// IsHovered flag to check for that
if (IsHovered() && InMouseEvent.GetCursorDelta().SizeSquared() > 0.0f && GetOwningLocalPlayer()->GetPlatformUserIndex() == 0)
{
// Gamepad or Keyboard may have changed the focus, so when we actually move the mouse set focus back
SetKeyboardFocus();
}
return Reply;
}
FReply UMyCommonButtonBase::NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent)
{
FReply Reply = Super::NativeOnKeyDown(InGeometry, InKeyEvent);
if (FSlateApplication::Get().GetNavigationActionFromKey(InKeyEvent) == EUINavigationAction::Accept)
{
if (PressMethod == EButtonPressMethod::ButtonRelease || (PressMethod == EButtonPressMethod::DownAndUp))
{
SetStyle(PressedStyle);
HandleButtonPressed();
}
}
return Reply;
}
FReply UMyCommonButtonBase::NativeOnKeyUp(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent)
{
FReply Reply = Super::NativeOnKeyUp(InGeometry, InKeyEvent);
if (FSlateApplication::Get().GetNavigationActionFromKey(InKeyEvent) == EUINavigationAction::Accept)
{
if (PressMethod == EButtonPressMethod::ButtonRelease || (PressMethod == EButtonPressMethod::DownAndUp))
{
SetStyle(HoveredStyle);
if (IsPressed())
{
HandleButtonReleased();
HandleButtonClicked();
}
}
}
return Reply;
}