From 5af66f22430c16f7a86a0c2cb8f7df1887de51f2 Mon Sep 17 00:00:00 2001 From: Steve Streeting Date: Thu, 19 Nov 2020 13:33:22 +0000 Subject: [PATCH] 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 --- .../Private/StevesGameSubsystem.cpp | 5 +++ .../Private/StevesUI/MenuBase.cpp | 11 ++++-- .../Private/StevesUI/MenuStack.cpp | 39 ++++++++++++++++++- .../Public/StevesGameSubsystem.h | 5 +++ .../Public/StevesUI/MenuBase.h | 8 +++- .../Public/StevesUI/MenuStack.h | 26 +++++++++++-- 6 files changed, 85 insertions(+), 9 deletions(-) diff --git a/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp b/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp index fd9c182..1166b14 100644 --- a/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp +++ b/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp @@ -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) diff --git a/Source/StevesUEHelpers/Private/StevesUI/MenuBase.cpp b/Source/StevesUEHelpers/Private/StevesUI/MenuBase.cpp index 208685e..3198d63 100644 --- a/Source/StevesUEHelpers/Private/StevesUI/MenuBase.cpp +++ b/Source/StevesUEHelpers/Private/StevesUI/MenuBase.cpp @@ -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(); - } - - + } } + diff --git a/Source/StevesUEHelpers/Private/StevesUI/MenuStack.cpp b/Source/StevesUEHelpers/Private/StevesUI/MenuStack.cpp index b236fc2..5a921e6 100644 --- a/Source/StevesUEHelpers/Private/StevesUI/MenuStack.cpp +++ b/Source/StevesUEHelpers/Private/StevesUI/MenuStack.cpp @@ -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 diff --git a/Source/StevesUEHelpers/Public/StevesGameSubsystem.h b/Source/StevesUEHelpers/Public/StevesGameSubsystem.h index bd2fdda..d15a76f 100644 --- a/Source/StevesUEHelpers/Public/StevesGameSubsystem.h +++ b/Source/StevesUEHelpers/Public/StevesGameSubsystem.h @@ -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 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(); }; diff --git a/Source/StevesUEHelpers/Public/StevesUI/MenuBase.h b/Source/StevesUEHelpers/Public/StevesUI/MenuBase.h index e0f32ce..406eae5 100644 --- a/Source/StevesUEHelpers/Public/StevesUI/MenuBase.h +++ b/Source/StevesUEHelpers/Public/StevesUI/MenuBase.h @@ -76,7 +76,13 @@ public: UFUNCTION(BlueprintCallable) void Close(bool bWasCancel); - TWeakObjectPtr 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 GetParentStack() const { return ParentStack; } + bool IsRequestingFocus() const { return bRequestFocus; } void AddedToStack(UMenuStack* Parent); void RemovedFromStack(UMenuStack* Parent); diff --git a/Source/StevesUEHelpers/Public/StevesUI/MenuStack.h b/Source/StevesUEHelpers/Public/StevesUI/MenuStack.h index f1c826a..7148f65 100644 --- a/Source/StevesUEHelpers/Public/StevesUI/MenuStack.h +++ b/Source/StevesUEHelpers/Public/StevesUI/MenuStack.h @@ -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 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; };