diff --git a/ReadMe.md b/ReadMe.md index ed3c7fd..183612f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -21,6 +21,8 @@ which makes a bunch of things better: * Events raised whenever a player uses a different input method (in game, and in UI) (Actually reliable and not dependent on vagaries of input mappings / differences between UI and game input) +:heart: **[Support my work on Patreon!](https://www.patreon.com/stevestreeting)** + ## Installing this plugin ### Cloning @@ -180,4 +182,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Source/StevesUEHelpers/Private/StevesUI/InputImage.cpp b/Source/StevesUEHelpers/Private/StevesUI/InputImage.cpp index 522067f..cd155b7 100644 --- a/Source/StevesUEHelpers/Private/StevesUI/InputImage.cpp +++ b/Source/StevesUEHelpers/Private/StevesUI/InputImage.cpp @@ -149,7 +149,11 @@ void UInputImage::UpdateImageFromTable(const FKey& InKey, const TSoftObjectPtrFindRow(InKey.GetFName(), "Find Key Image"); if (SpriteRow) - SetBrushFromAtlasInterface(SpriteRow->Sprite); + { + // Match size is needed incase size has changed + // Need to make it update region in case inside a scale box or something else that needs to adjust + SetBrushFromAtlasInterface(SpriteRow->Sprite, true); + } } UUiTheme* UInputImage::GetTheme() diff --git a/Source/StevesUEHelpers/Private/StevesUI/MenuBase.cpp b/Source/StevesUEHelpers/Private/StevesUI/MenuBase.cpp index 3bea507..208685e 100644 --- a/Source/StevesUEHelpers/Private/StevesUI/MenuBase.cpp +++ b/Source/StevesUEHelpers/Private/StevesUI/MenuBase.cpp @@ -5,6 +5,7 @@ #include "StevesUEHelpers.h" #include "StevesUI/MenuStack.h" #include "Components/ContentWidget.h" +#include "Kismet/GameplayStatics.h" void UMenuBase::Close(bool bWasCancel) { @@ -13,26 +14,19 @@ void UMenuBase::Close(bool bWasCancel) if (ParentStack.IsValid()) { ParentStack->PopMenuIfTop(this, bWasCancel); + } else + { + // standalone mode + RemoveFromParent(); + PreviousFocusWidget.Reset(); } } void UMenuBase::AddedToStack(UMenuStack* Parent) { ParentStack = MakeWeakObjectPtr(Parent); - if (bEmbedInParentContainer) - EmbedInParent(); - else - AddToViewport(); - SetVisibility(ESlateVisibility::Visible); - auto GS = GetStevesGameSubsystem(GetWorld()); - if (bRequestFocus && - GS && (GS->GetLastInputModeUsed() != EInputMode::Gamepad || GS->GetLastInputModeUsed() != EInputMode::Keyboard)) - { - SetFocusProperly(); - } - - + Open(false); } @@ -73,16 +67,7 @@ void UMenuBase::SupercededInStack() void UMenuBase::RegainedFocusInStack() { - if (bEmbedInParentContainer) - EmbedInParent(); - else - AddToViewport(); - SetVisibility(ESlateVisibility::Visible); - - if (bRequestFocus) - { - SetFocusProperly(); - } + Open(true); } @@ -94,6 +79,64 @@ void UMenuBase::EmbedInParent() ParentStack->MenuContainer->SetContent(this); } else - UE_LOG(LogCustomUI, Error, TEXT("Cannot embed %s in parent, missing container"), *this->GetName()) + UE_LOG(LogStevesUI, Error, TEXT("Cannot embed %s in parent, missing container"), *this->GetName()) + +} + +void UMenuBase::Open(bool bIsRegain) +{ + if (ParentStack.IsValid() && bEmbedInParentContainer) + EmbedInParent(); + else + AddToViewport(); + SetVisibility(ESlateVisibility::Visible); + + auto PC = GetOwningPlayer(); + switch (InputModeSetting) + { + case EInputModeChange::DoNotChange: + break; + case EInputModeChange::UIOnly: + PC->SetInputMode(FInputModeUIOnly()); + break; + case EInputModeChange::GameAndUI: + PC->SetInputMode(FInputModeGameAndUI()); + break; + case EInputModeChange::GameOnly: + PC->SetInputMode(FInputModeGameOnly()); + break; + } + + switch (MousePointerVisibility) + { + case EMousePointerVisibilityChange::DoNotChange: + break; + case EMousePointerVisibilityChange::Visible: + PC->bShowMouseCursor = true; + break; + case EMousePointerVisibilityChange::Hidden: + PC->bShowMouseCursor = false; + break; + } + + switch (GamePauseSetting) + { + case EGamePauseChange::DoNotChange: + break; + case EGamePauseChange::Paused: + UGameplayStatics::SetGamePaused(GetWorld(), true); + break; + case EGamePauseChange::Unpaused: + UGameplayStatics::SetGamePaused(GetWorld(), false); + break; + } + + 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 5e0c132..149cb90 100644 --- a/Source/StevesUEHelpers/Private/StevesUI/MenuStack.cpp +++ b/Source/StevesUEHelpers/Private/StevesUI/MenuStack.cpp @@ -123,7 +123,7 @@ void UMenuStack::PopMenuIfTop(UMenuBase* UiMenuBase, bool bWasCancel) } else { - UE_LOG(LogCustomUI, Error, TEXT("Tried to pop menu %s but it wasn't the top level"), *UiMenuBase->GetName()); + UE_LOG(LogStevesUI, Error, TEXT("Tried to pop menu %s but it wasn't the top level"), *UiMenuBase->GetName()); } } diff --git a/Source/StevesUEHelpers/Private/StevesUI/OptionWidgetBase.cpp b/Source/StevesUEHelpers/Private/StevesUI/OptionWidgetBase.cpp index a1263c7..af5152e 100644 --- a/Source/StevesUEHelpers/Private/StevesUI/OptionWidgetBase.cpp +++ b/Source/StevesUEHelpers/Private/StevesUI/OptionWidgetBase.cpp @@ -19,29 +19,29 @@ void UOptionWidgetBase::NativeConstruct() ClearOptions(); } else - UE_LOG(LogCustomUI, Error, TEXT("StevesGameSubsystem is missing!")); + UE_LOG(LogStevesUI, Error, TEXT("StevesGameSubsystem is missing!")); // Check we can bind everything we need, bind click if (!MouseVersion) - UE_LOG(LogCustomUI, Error, TEXT("%s should have a MouseVersion instance."), *this->GetClass()->GetName()); + UE_LOG(LogStevesUI, Error, TEXT("%s should have a MouseVersion instance."), *this->GetClass()->GetName()); if (!GamepadVersion) - UE_LOG(LogCustomUI, Error, TEXT("%s should have a GamepadVersion instance."), *this->GetClass()->GetName()); + UE_LOG(LogStevesUI, Error, TEXT("%s should have a GamepadVersion instance."), *this->GetClass()->GetName()); if (MouseUpButton) MouseUpButton->OnClicked.AddDynamic(this, &UOptionWidgetBase::MouseUpClicked); else - UE_LOG(LogCustomUI, Error, TEXT("%s should have a MouseUpButton instance."), *this->GetClass()->GetName()); + UE_LOG(LogStevesUI, Error, TEXT("%s should have a MouseUpButton instance."), *this->GetClass()->GetName()); if (MouseDownButton) MouseDownButton->OnClicked.AddDynamic(this, &UOptionWidgetBase::MouseDownClicked); else - UE_LOG(LogCustomUI, Error, TEXT("%s should have a MouseDownButton instance."), *this->GetClass()->GetName()); + UE_LOG(LogStevesUI, Error, TEXT("%s should have a MouseDownButton instance."), *this->GetClass()->GetName()); if (!MouseUpImage) - UE_LOG(LogCustomUI, Error, TEXT("%s should have a MouseUpImage instance."), *this->GetClass()->GetName()); + UE_LOG(LogStevesUI, Error, TEXT("%s should have a MouseUpImage instance."), *this->GetClass()->GetName()); if (!MouseDownImage) - UE_LOG(LogCustomUI, Error, TEXT("%s should have a MouseDownImage instance."), *this->GetClass()->GetName()); + UE_LOG(LogStevesUI, Error, TEXT("%s should have a MouseDownImage instance."), *this->GetClass()->GetName()); if (!GamepadUpImage) - UE_LOG(LogCustomUI, Error, TEXT("%s should have a GamepadUpImage instance."), *this->GetClass()->GetName()); + UE_LOG(LogStevesUI, Error, TEXT("%s should have a GamepadUpImage instance."), *this->GetClass()->GetName()); if (!GamepadDownImage) - UE_LOG(LogCustomUI, Error, TEXT("%s should have a GamepadDownImage instance."), *this->GetClass()->GetName()); + UE_LOG(LogStevesUI, Error, TEXT("%s should have a GamepadDownImage instance."), *this->GetClass()->GetName()); } diff --git a/Source/StevesUEHelpers/Private/StevesUI/StevesUI.cpp b/Source/StevesUEHelpers/Private/StevesUI/StevesUI.cpp index 3a8b6f3..28ef663 100644 --- a/Source/StevesUEHelpers/Private/StevesUI/StevesUI.cpp +++ b/Source/StevesUEHelpers/Private/StevesUI/StevesUI.cpp @@ -7,7 +7,7 @@ #include "Components/PanelWidget.h" #include "Components/Widget.h" -DEFINE_LOG_CATEGORY(LogCustomUI); +DEFINE_LOG_CATEGORY(LogStevesUI); UWidget* FindWidgetFromSlate(SWidget* SW, UWidget* Parent) { diff --git a/Source/StevesUEHelpers/Private/StevesUI/StevesUI.h b/Source/StevesUEHelpers/Private/StevesUI/StevesUI.h index ea5c45d..ba917c9 100644 --- a/Source/StevesUEHelpers/Private/StevesUI/StevesUI.h +++ b/Source/StevesUEHelpers/Private/StevesUI/StevesUI.h @@ -5,7 +5,7 @@ class UWidget; class SWidget; -DECLARE_LOG_CATEGORY_EXTERN(LogCustomUI, Warning, Warning) +DECLARE_LOG_CATEGORY_EXTERN(LogStevesUI, Warning, Warning) /** diff --git a/Source/StevesUEHelpers/Public/StevesUI/FocusableUserWidget.h b/Source/StevesUEHelpers/Public/StevesUI/FocusableUserWidget.h index 4bff758..5ee33dc 100644 --- a/Source/StevesUEHelpers/Public/StevesUI/FocusableUserWidget.h +++ b/Source/StevesUEHelpers/Public/StevesUI/FocusableUserWidget.h @@ -14,7 +14,7 @@ class STEVESUEHELPERS_API UFocusableUserWidget : public UUserWidget public: /// UWidget::SetFocus is not virtual FFS. This does the same as SetFocus by default but can be overridden, /// e.g. to delegate focus to specific children - UFUNCTION(BlueprintNativeEvent) + UFUNCTION(BlueprintNativeEvent, BlueprintCallable) void SetFocusProperly(); }; diff --git a/Source/StevesUEHelpers/Public/StevesUI/MenuBase.h b/Source/StevesUEHelpers/Public/StevesUI/MenuBase.h index fafc78b..135648d 100644 --- a/Source/StevesUEHelpers/Public/StevesUI/MenuBase.h +++ b/Source/StevesUEHelpers/Public/StevesUI/MenuBase.h @@ -10,10 +10,38 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnMenuClosed, UMenuBase*, Menu, bool, bWasCancelled); -/// This class is one element of the UUiMenuStack and represents one level in the chain of -/// an assumed modal stack. It is responsible for implementing how it gets added -/// to the viewport or parent container, and removed again, and what it does when receiving -/// focus. + +UENUM(BlueprintType) +enum class EInputModeChange : uint8 +{ + DoNotChange UMETA(DisplayName="No Change"), + UIOnly UMETA(DisplayName="UI Only"), + GameAndUI UMETA(DisplayName="Game And UI"), + GameOnly UMETA(DisplayName="Game Only") +}; + +UENUM(BlueprintType) +enum class EMousePointerVisibilityChange : uint8 +{ + DoNotChange UMETA(DisplayName="No Change"), + Visible UMETA(DisplayName="Pointer Visible"), + Hidden UMETA(DisplayName="Pointer Hidden") +}; + +UENUM(BlueprintType) +enum class EGamePauseChange : uint8 +{ + DoNotChange UMETA(DisplayName="No Change"), + Paused UMETA(DisplayName="Pause Game"), + Unpaused UMETA(DisplayName="Unpause Game") +}; + +/// This class is a type of focusable panel designed for menus or other dialogs. +/// It can be added to a UMenuStack to put it in context of a larger navigable group, +/// and if so represents one level in the chain of an assumed modal stack. Use UMenuStack::PushMenuByClass/Object +/// to add an entry of this type to the stack +/// If you use this class standalone instead without a stack, then you must call Open() on this instance to +/// make it add itself to the viewport. UCLASS(Abstract, BlueprintType) class STEVESUEHELPERS_API UMenuBase : public UFocusablePanel { @@ -29,23 +57,48 @@ protected: /// Whether this menu should request focus when it is displayed /// The widget which is focussed will either be the InitialFocusWidget on newly displayed, or /// the previously selected widget if regaining focus - UPROPERTY(EditAnywhere, BlueprintReadWrite) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Focus") bool bRequestFocus = true; - /// Set this property to true if you want this menu to embed itself in the parent UUiMenuStack's MenuContainer + /// Set this property to true if you want this menu to embed itself in the parent UMenuStack's MenuContainer /// If false, this Menu will be added to the viewport independently and can float over other menus in the stack - UPROPERTY(EditAnywhere, BlueprintReadWrite) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Layout") bool bEmbedInParentContainer = true; /// Whether to hide this menu when it's superceded by another in the stack. This property is only relevant /// when bEmbedInParentContainer = false, since only one menu can be embedded at once. - UPROPERTY(EditAnywhere, BlueprintReadWrite) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Behavior") bool bHideWhenSuperceded = true; - void EmbedInParent(); + /// How this menu should set the input mode when it becomes the top of the stack + /// This can be useful if your menus have variable input settings between levels in the stack + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Behavior") + EInputModeChange InputModeSetting = EInputModeChange::DoNotChange; + + /// How this menu should set the mouse pointer visibility when it becomes the top of the stack + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Behavior") + EMousePointerVisibilityChange MousePointerVisibility = EMousePointerVisibilityChange::DoNotChange; + + /// How this menu should set the game pause state when it becomes top of the stack + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Behavior") + EGamePauseChange GamePauseSetting = EGamePauseChange::DoNotChange; + + virtual void EmbedInParent(); public: - /// Close this menu level + + /** + * @brief Open this menu. You should only call this if you're NOT using this in a UMenuStack, because the stack will + * call it for you when you add this menu to it + + * @param bIsRegainedFocus Set this to true if the reason this menu is opening is that it regained focus in a stack + */ + UFUNCTION(BlueprintCallable) + void Open(bool bIsRegainedFocus = false); + /** + * @brief Close this menu. + * @param bWasCancel Set this to true if the reason for closure was a cancellation action + */ UFUNCTION(BlueprintCallable) void Close(bool bWasCancel); diff --git a/StevesUEHelpers.uplugin b/StevesUEHelpers.uplugin index 4084248..277220d 100644 --- a/StevesUEHelpers.uplugin +++ b/StevesUEHelpers.uplugin @@ -9,7 +9,7 @@ "CreatedByURL" : "https://www.stevestreeting.com", "DocsURL" : "", "MarketplaceURL" : "", - "SupportURL" : "", + "SupportURL" : "https://github.com/sinbad/StevesUEHelpers", "EnabledByDefault" : true, "CanContainContent" : false, "IsBetaVersion" : true,