Add MenuSystem which is capable of handling to focus between multiple MenuStacks, so that a closed stack can give focus back to the next most important stack automatically

This commit is contained in:
Steve Streeting 2020-11-19 13:33:22 +00:00
parent f75706abc4
commit 5af66f2243
6 changed files with 85 additions and 9 deletions

View File

@ -87,6 +87,11 @@ void UStevesGameSubsystem::OnInputDetectorModeChanged(int PlayerIndex, EInputMod
OnInputModeChanged.Broadcast(PlayerIndex, NewMode);
}
FMenuSystem* UStevesGameSubsystem::GetMenuSystem()
{
return &MenuSystem;
}
UStevesGameSubsystem::FInputModeDetector::FInputModeDetector()
{
// 4 local players should be plenty usually (will expand if necessary)

View File

@ -131,12 +131,17 @@ void UMenuBase::Open(bool bIsRegain)
break;
}
TakeFocusIfDesired();
}
void UMenuBase::TakeFocusIfDesired()
{
auto GS = GetStevesGameSubsystem(GetWorld());
if (bRequestFocus &&
GS && (GS->GetLastInputModeUsed() != EInputMode::Gamepad || GS->GetLastInputModeUsed() != EInputMode::Keyboard))
{
SetFocusProperly();
}
}
}

View File

@ -4,7 +4,6 @@
#include "StevesGameSubsystem.h"
#include "StevesUEHelpers.h"
#include "StevesUI/MenuBase.h"
#include "Framework/Application/SlateApplication.h"
#include "Kismet/GameplayStatics.h"
@ -180,6 +179,9 @@ void UMenuStack::PushMenuByObject(UMenuBase* NewMenu)
}
Menus.Add(NewMenu);
NewMenu->AddedToStack(this);
if (Menus.Num() == 1)
FirstMenuOpened();
}
void UMenuStack::PopMenu(bool bWasCancel)
@ -217,9 +219,28 @@ void UMenuStack::PopMenuIfTop(UMenuBase* UiMenuBase, bool bWasCancel)
}
}
void UMenuStack::FirstMenuOpened()
{
// tell menu system
auto GS = GetStevesGameSubsystem(GetWorld());
GS->GetMenuSystem()->MenuStackOpened(this);
}
void UMenuStack::RemoveFromParent()
{
Super::RemoveFromParent();
// tell menu system if we're in-game (this gets called in editor too)
auto GS = GetStevesGameSubsystem(GetWorld());
if (GS)
GS->GetMenuSystem()->MenuStackClosed(this);
}
void UMenuStack::LastMenuClosed(bool bWasCancel)
{
RemoveFromParent();
RemoveFromParent(); // this will do MenuSystem interaction
OnClosed.Broadcast(this, bWasCancel);
ApplyInputModeChange(InputModeSettingOnClose);
@ -228,6 +249,7 @@ void UMenuStack::LastMenuClosed(bool bWasCancel)
}
void UMenuStack::CloseAll(bool bWasCancel)
{
// We don't go through normal pop sequence, this is a shot circuit
@ -239,6 +261,19 @@ void UMenuStack::CloseAll(bool bWasCancel)
LastMenuClosed(bWasCancel);
}
bool UMenuStack::IsRequestingFocus() const
{
return Menus.Num() > 0 && Menus.Last()->IsRequestingFocus();
}
void UMenuStack::TakeFocusIfDesired()
{
if (IsRequestingFocus())
{
Menus.Last()->TakeFocusIfDesired();
}
}
void UMenuStack::SetFocusProperly_Implementation()
{
// Delegate to top menu

View File

@ -5,6 +5,7 @@
#include "InputCoreTypes.h"
#include "Framework/Application/IInputProcessor.h"
#include "StevesHelperCommon.h"
#include "StevesUI/MenuSystem.h"
#include "StevesUI/UiTheme.h"
#include "StevesGameSubsystem.generated.h"
@ -91,6 +92,7 @@ protected:
protected:
TSharedPtr<FInputModeDetector> InputDetector;
FMenuSystem MenuSystem;
UPROPERTY(BlueprintReadWrite)
UUiTheme* DefaultUiTheme;
@ -120,4 +122,7 @@ public:
/// Changes the default theme to a different one
void SetDefaultUiTheme(UUiTheme* NewTheme) { DefaultUiTheme = NewTheme; }
/// Get the global menu system
FMenuSystem* GetMenuSystem();
};

View File

@ -76,7 +76,13 @@ public:
UFUNCTION(BlueprintCallable)
void Close(bool bWasCancel);
TWeakObjectPtr<UMenuStack> GetParentStack() const { return ParentStack; };
/// Triggers this menu to take focus if appropriate
/// This means if the menu both requests focus, and gamepad or keyboard is in use
UFUNCTION(BlueprintCallable)
void TakeFocusIfDesired();
TWeakObjectPtr<UMenuStack> GetParentStack() const { return ParentStack; }
bool IsRequestingFocus() const { return bRequestFocus; }
void AddedToStack(UMenuStack* Parent);
void RemovedFromStack(UMenuStack* Parent);

View File

@ -16,7 +16,11 @@ class UMenuBase;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnMenuStackClosed, class UMenuStack*, Stack, bool, bWasCancel);
/// Represents a modal stack of menus which take focus and have a concept of "Back"
/// Each level is a MenuBase
/// Each level within is a MenuBase, which must be "pushed" on to the stack.
/// Contained within MenuSystem (multiple menu stacks supported)
/// Has a Priority so that when multiple menu stacks are open, higher priority gets focus,
/// and when closed, next highest priority gets focus back. Focus is given when the first MenuBase is pushed onto the stack,
/// and given up when the last one is popped.
/// You can style this widget to be the general surrounds in which all MenuBase levels live inside
/// Create a Blueprint subclass of this and make sure you include a UContentWidget with the name
/// "MenuContainer" somewhere in the tree, which is where the menu contents will be placed.
@ -33,6 +37,7 @@ protected:
TArray<UMenuBase*> Menus;
virtual void FirstMenuOpened();
virtual void LastMenuClosed(bool bWasCancel);
virtual void NativeConstruct() override;
@ -48,6 +53,12 @@ protected:
void InputModeChanged(int PlayerIndex, EInputMode NewMode);
public:
/// The focus priority of this stack compared to others. When a MenuStack is opened, if it has higher priority than
/// any existing MenuStack open, it will be given focus. When a MenuStack with focus is closed, the next highest
/// priority one will be given focus.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Behaviour")
int FocusPriority = 0;
/// This property will bind to a blueprint variable of the same name to contain the actual menu content
/// If not set, or the UiMenuBase is set to not use this container, levels are added independently to viewport
/// Use a NamedSlot for this most of the time, it gives you the most layout flexibility.
@ -107,7 +118,16 @@ public:
UFUNCTION(BlueprintCallable)
void CloseAll(bool bWasCancel);
/// Whether the top MenuBase on this stack is requesting focus
UFUNCTION(BlueprintCallable)
bool IsRequestingFocus() const;
/// Triggers this stack to take focus (specifically its topmost MenuBase) if appropriate
/// This means if both top menu on the stack requests focus, and gamepad or keyboard is in use
UFUNCTION(BlueprintCallable)
void TakeFocusIfDesired();
virtual void SetFocusProperly_Implementation() override;
void PopMenuIfTop(UMenuBase* UiMenuBase, bool bWasCancel);
virtual void PopMenuIfTop(UMenuBase* UiMenuBase, bool bWasCancel);
virtual void RemoveFromParent() override;
};