diff --git a/Binaries/Win64/UnrealEditor-SettingsWidgetConstructor.dll b/Binaries/Win64/UnrealEditor-SettingsWidgetConstructor.dll index 9ea4320..3386516 100644 Binary files a/Binaries/Win64/UnrealEditor-SettingsWidgetConstructor.dll and b/Binaries/Win64/UnrealEditor-SettingsWidgetConstructor.dll differ diff --git a/Binaries/Win64/UnrealEditor-SettingsWidgetConstructor.pdb b/Binaries/Win64/UnrealEditor-SettingsWidgetConstructor.pdb index 0495de0..72c175b 100644 Binary files a/Binaries/Win64/UnrealEditor-SettingsWidgetConstructor.pdb and b/Binaries/Win64/UnrealEditor-SettingsWidgetConstructor.pdb differ diff --git a/Binaries/Win64/UnrealEditor-SettingsWidgetConstructorEditor.dll b/Binaries/Win64/UnrealEditor-SettingsWidgetConstructorEditor.dll index 9091c7f..9d6f036 100644 Binary files a/Binaries/Win64/UnrealEditor-SettingsWidgetConstructorEditor.dll and b/Binaries/Win64/UnrealEditor-SettingsWidgetConstructorEditor.dll differ diff --git a/Binaries/Win64/UnrealEditor-SettingsWidgetConstructorEditor.pdb b/Binaries/Win64/UnrealEditor-SettingsWidgetConstructorEditor.pdb index f8456e2..434aee7 100644 Binary files a/Binaries/Win64/UnrealEditor-SettingsWidgetConstructorEditor.pdb and b/Binaries/Win64/UnrealEditor-SettingsWidgetConstructorEditor.pdb differ diff --git a/Binaries/Win64/UnrealEditor.modules b/Binaries/Win64/UnrealEditor.modules index 7583808..f50e933 100644 --- a/Binaries/Win64/UnrealEditor.modules +++ b/Binaries/Win64/UnrealEditor.modules @@ -1,5 +1,5 @@ { - "BuildId": "25360045", + "BuildId": "27405482", "Modules": { "SettingsWidgetConstructor": "UnrealEditor-SettingsWidgetConstructor.dll", diff --git a/Config/BaseSettingsDataTable.json b/Config/BaseSettingsDataTable.json index 6f2e468..c200fa9 100644 --- a/Config/BaseSettingsDataTable.json +++ b/Config/BaseSettingsDataTable.json @@ -10,7 +10,7 @@ { "TagName": "Settings.TextLine" }, - "StaticContext": + "Owner": { "FunctionClass": "None", "FunctionName": "None" @@ -25,7 +25,7 @@ "FunctionClass": "None", "FunctionName": "None" }, - "Caption": "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"B1FD8AB64A187ECF1AF534A23B1EB4B9\", \"SETTINGS\")", + "Caption": "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"9B5F34484FB5155A7E20C0B96B5D76AB\", \"SETTINGS\")", "Tooltip": "NSLOCTEXT(\"Core\", \"None\", \"None\")", "Padding": { @@ -40,6 +40,10 @@ { "GameplayTags": [], "ParentTags": [] + }, + "ShowNextToSettingOverride": + { + "TagName": "None" } }, "Button": @@ -94,23 +98,23 @@ { "TagName": "Settings.Combobox" }, - "StaticContext": + "Owner": { - "FunctionClass": "None", - "FunctionName": "None" + "FunctionClass": "/Script/CoreUObject.Class'/Script/SettingsWidgetConstructor.SettingsUtilsLibrary'", + "FunctionName": "GetGameUserSettings" }, "Setter": { - "FunctionClass": "None", - "FunctionName": "None" + "FunctionClass": "/Script/CoreUObject.Class'/Script/Engine.GameUserSettings'", + "FunctionName": "SetOverallScalabilityLevel" }, "Getter": { - "FunctionClass": "None", - "FunctionName": "None" + "FunctionClass": "/Script/CoreUObject.Class'/Script/Engine.GameUserSettings'", + "FunctionName": "GetOverallScalabilityLevel" }, - "Caption": "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"C5758D8D4760AC9C55185480331D72DB\", \"Overall Quality\")", - "Tooltip": "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"67193396456DFF30A0D91E85F3A0A62D\", \"Choose the overall quality level\")", + "Caption": "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"3D05344D4AC20700809DC9B76E80BC28\", \"Overall Quality\")", + "Tooltip": "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"9F345551422A4511C85D2B8E8D740C23\", \"Choose the overall quality level\")", "Padding": { "Left": 0, @@ -124,6 +128,10 @@ { "GameplayTags": [], "ParentTags": [] + }, + "ShowNextToSettingOverride": + { + "TagName": "None" } }, "Button": @@ -147,12 +155,11 @@ "FunctionName": "None" }, "Members": [ - "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"4BD5AC644E575D63EA6A51BAE630502E\", \"Custom\")", - "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"9A1037DA4D94CD073C1A48A4E72533DB\", \"Low\")", - "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"74DF82A24FD955EB28D2508AE0D01227\", \"Medium\")", - "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"CE3E2FCE45CCF68724AEB2AAE98A46AA\", \"High\")", - "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"402F856C457B291C5F3FC09F9136E9F8\", \"Very High\")", - "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"BDDAE76445B3279B1F40D498D16C0C24\", \"Ultra\")" + "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"CCA672154B5FEA29315F3EADAEF2019F\", \"Low\")", + "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"3D9EECC54A0F8310659B85A3B3F7BB19\", \"Medium\")", + "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"455615594854614F98882F94A79F65B1\", \"High\")", + "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"80910DF74E3E587682422488BCBBD7A3\", \"Epic\")", + "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"213FC09D4F9042E4C7F73BAE9D693D75\", \"Cinematic\")" ], "TextJustify": "Center" }, @@ -175,33 +182,33 @@ } }, { - "Name": "Settings.Slider", + "Name": "Settings.Checkbox", "SettingsPicker": { - "SettingsType": "Slider", + "SettingsType": "Checkbox", "PrimaryData": { "Tag": { - "TagName": "Settings.Slider" + "TagName": "Settings.Checkbox" }, - "StaticContext": + "Owner": { - "FunctionClass": "None", - "FunctionName": "None" + "FunctionClass": "/Script/CoreUObject.Class'/Script/SettingsWidgetConstructor.SettingsUtilsLibrary'", + "FunctionName": "GetGameUserSettings" }, "Setter": { - "FunctionClass": "None", - "FunctionName": "None" + "FunctionClass": "/Script/CoreUObject.Class'/Script/Engine.GameUserSettings'", + "FunctionName": "SetVSyncEnabled" }, "Getter": { - "FunctionClass": "None", - "FunctionName": "None" + "FunctionClass": "/Script/CoreUObject.Class'/Script/Engine.GameUserSettings'", + "FunctionName": "IsVSyncEnabled" }, - "Caption": "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"FDDC03E84D349EAC5743FEB1C9E6B87D\", \"Music Volume\")", - "Tooltip": "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"3EC174FB4DEA3029A69CEEA9C382A9DD\", \"Set the sound volume for the background music\")", + "Caption": "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"4B34951E482BBA92506C3D8A3054B05B\", \"V-Sync\")", + "Tooltip": "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"69E51E954CA7C3F7A9DB57A88A5B8BBA\", \"Check to enable the V-Sync\")", "Padding": { "Left": 0, @@ -215,6 +222,10 @@ { "GameplayTags": [], "ParentTags": [] + }, + "ShowNextToSettingOverride": + { + "TagName": "None" } }, "Button": @@ -259,17 +270,17 @@ } }, { - "Name": "Settings.Checkbox", + "Name": "Settings.UserInput", "SettingsPicker": { - "SettingsType": "Checkbox", + "SettingsType": "UserInput", "PrimaryData": { "Tag": { - "TagName": "Settings.Checkbox" + "TagName": "Settings.UserInput" }, - "StaticContext": + "Owner": { "FunctionClass": "None", "FunctionName": "None" @@ -284,8 +295,8 @@ "FunctionClass": "None", "FunctionName": "None" }, - "Caption": "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"30E1B9914168CFDFD022ACAAF72408A2\", \"Fullscreen\")", - "Tooltip": "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"D4DAAF084A3BE24A202DE68898F1E2E5\", \"Check to enable the fullscreen mode\")", + "Caption": "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"27AF3120412B8FDA02FE3F951893C245\", \"Player Name\")", + "Tooltip": "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"002E379E4E5144B9BB5A128E98C49EDD\", \"Enter your nickname\")", "Padding": { "Left": 0, @@ -294,11 +305,15 @@ "Bottom": 15 }, "LineHeight": 48, - "bStartOnNextColumn": false, + "bStartOnNextColumn": true, "SettingsToUpdate": { "GameplayTags": [], "ParentTags": [] + }, + "ShowNextToSettingOverride": + { + "TagName": "None" } }, "Button": @@ -334,7 +349,7 @@ }, "UserInput": { - "MaxCharactersNumber": 0 + "MaxCharactersNumber": 16 }, "CustomWidget": { @@ -343,17 +358,17 @@ } }, { - "Name": "Settings.UserInput", + "Name": "Settings.Slider", "SettingsPicker": { - "SettingsType": "UserInput", + "SettingsType": "Slider", "PrimaryData": { "Tag": { - "TagName": "Settings.UserInput" + "TagName": "Settings.Slider" }, - "StaticContext": + "Owner": { "FunctionClass": "None", "FunctionName": "None" @@ -368,8 +383,8 @@ "FunctionClass": "None", "FunctionName": "None" }, - "Caption": "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"0A4D44494F98A414FFD6F399B8C3DAFE\", \"Player Name\")", - "Tooltip": "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"91BCE84A4CF9B58F61A9C8BB16663DB5\", \"Enter your nickname\")", + "Caption": "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"B93D9EA548CA0CFCED454784E1FD16D4\", \"Player Name Scale\")", + "Tooltip": "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"921CAF0148DA0D735D5A358549AF2F1C\", \"Set the Player Scale Scale\")", "Padding": { "Left": 0, @@ -378,11 +393,15 @@ "Bottom": 15 }, "LineHeight": 48, - "bStartOnNextColumn": true, + "bStartOnNextColumn": false, "SettingsToUpdate": { "GameplayTags": [], "ParentTags": [] + }, + "ShowNextToSettingOverride": + { + "TagName": "None" } }, "Button": @@ -418,7 +437,7 @@ }, "UserInput": { - "MaxCharactersNumber": 8 + "MaxCharactersNumber": 0 }, "CustomWidget": { @@ -437,23 +456,23 @@ { "TagName": "Settings.Button" }, - "StaticContext": + "Owner": { - "FunctionClass": "None", - "FunctionName": "None" + "FunctionClass": "/Script/CoreUObject.Class'/Script/SettingsWidgetConstructor.SettingsUtilsLibrary'", + "FunctionName": "GetSettingsWidget" }, "Setter": { - "FunctionClass": "None", - "FunctionName": "None" + "FunctionClass": "/Script/CoreUObject.Class'/Script/SettingsWidgetConstructor.SettingsWidget'", + "FunctionName": "SaveSettings" }, "Getter": { "FunctionClass": "None", "FunctionName": "None" }, - "Caption": "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"CFC97B654007D0E10720D4B425844586\", \"Back\")", - "Tooltip": "NSLOCTEXT(\"[F67605FD4FE1D83F0EC04D97554FA9A9]\", \"A31C26AB4E3A28D6819DC4A4D7EFA70D\", \"Return back to the main menu\")", + "Caption": "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"2F0E1E0E4E3EC020E6020FB66C005751\", \"Apply\")", + "Tooltip": "NSLOCTEXT(\"[B300B6BE227CC798280E67264B750EDE]\", \"BFD2A32B4EDE7484E5AA3499437BB3D7\", \"Apply and save all settings to config\")", "Padding": { "Left": 0, @@ -467,6 +486,10 @@ { "GameplayTags": [], "ParentTags": [] + }, + "ShowNextToSettingOverride": + { + "TagName": "None" } }, "Button": @@ -510,4 +533,4 @@ } } } -] +] \ No newline at end of file diff --git a/Config/BaseSettingsWidgetConstructor.ini b/Config/BaseSettingsWidgetConstructor.ini index 2c521b3..6bc0ac6 100644 --- a/Config/BaseSettingsWidgetConstructor.ini +++ b/Config/BaseSettingsWidgetConstructor.ini @@ -3,6 +3,7 @@ SettingsWidgetClassInternal=WidgetBlueprintGeneratedClass'/SettingsWidgetConstru [/Script/SettingsWidgetConstructor.SettingsDataAsset] SettingsDataTableInternal= +SettingsDataRegistryInternal=/SettingsWidgetConstructor/Data/DR_SettingsDataTables.DR_SettingsDataTables ButtonClassInternal=WidgetBlueprintGeneratedClass'/SettingsWidgetConstructor/Subwidgets/WBP_SettingsButton.WBP_SettingsButton_C' CheckboxClassInternal=WidgetBlueprintGeneratedClass'/SettingsWidgetConstructor/Subwidgets/WBP_SettingsCheckBox.WBP_SettingsCheckBox_C' ComboboxClassInternal=WidgetBlueprintGeneratedClass'/SettingsWidgetConstructor/Subwidgets/WBP_SettingsCombobox.WBP_SettingsCombobox_C' @@ -10,6 +11,7 @@ SliderClassInternal=WidgetBlueprintGeneratedClass'/SettingsWidgetConstructor/Sub TextLineClassInternal=WidgetBlueprintGeneratedClass'/SettingsWidgetConstructor/Subwidgets/WBP_SettingsTextLine.WBP_SettingsTextLine_C' UserInputClassInternal=WidgetBlueprintGeneratedClass'/SettingsWidgetConstructor/Subwidgets/WBP_SettingsTextInput.WBP_SettingsTextInput_C' bAutoConstructInternal=True +bAutoFocusOnOpenInternal=True SettingsPercentSizeInternal=(X=0.600000,Y=0.400000) SettingsPaddingInternal=(Left=50.000000,Top=50.000000,Right=50.000000,Bottom=50.000000) ScrollboxPercentHeightInternal=1.000000 diff --git a/Content/Data/DR_SettingsDataTables.uasset b/Content/Data/DR_SettingsDataTables.uasset new file mode 100644 index 0000000..0942d35 Binary files /dev/null and b/Content/Data/DR_SettingsDataTables.uasset differ diff --git a/Content/WBP_SettingsWidgetConstructor.uasset b/Content/WBP_SettingsWidgetConstructor.uasset index 2519a5d..13c2864 100644 Binary files a/Content/WBP_SettingsWidgetConstructor.uasset and b/Content/WBP_SettingsWidgetConstructor.uasset differ diff --git a/README.md b/README.md index 7ea43ef..243224c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Settings Widget Constructor is a handy plugin for Unreal Engine 5 that lets you ![Settings UI](https://user-images.githubusercontent.com/20540872/147825296-ce7d33da-dfda-4757-b070-bfd08f700134.jpg) -image +image ## 📚 Documentation @@ -17,12 +17,21 @@ Check out our [Release](https://github.com/JanSeliv/SettingsWidgetConstructor/re Also, explore this [game project repository](https://github.com/JanSeliv/Bomber) to view the Settings Widget Constructor in action. ## 📅 Changelog +#### 2023-10-12 +- Updated to **Unreal Engine 5.3** +- Added **multiple Settings Data Tables** support ([see doc](https://docs.google.com/document/d/1IXnOqrgaXTClP-0cIo28a9f6GHc9N1BCgTNnMk-X9VQ/edit#heading=h.cix3vjszb2vm)). +- Implemented **Deferred Bindings**: now Getters and Setters are automatically rebound for failed settings. +- Added **Blueprint Function Library** support to allow any its blueprint function to be used as an _Owner_ in a setting row. #### 2023-05-26 - 🎉 Initial public release on Unreal Engine 5.2 ## 📫 Feedback & Contribution -This is an open-source project and we encourage you to contribute. If you encounter any bugs, or if you have any feature requests, please file an issue in the GitHub repository. +Feedback and contributions from the community are highly appreciated! + +If you'd like to contribute, please fork the project and create a pull request targeting the `develop` branch. + +If you've found a bug or have an idea for a new feature, please open a new issue on GitHub. Thank you! ## 📜 License diff --git a/SettingsWidgetConstructor.uplugin b/SettingsWidgetConstructor.uplugin index fd9fd14..e315d22 100644 --- a/SettingsWidgetConstructor.uplugin +++ b/SettingsWidgetConstructor.uplugin @@ -10,7 +10,7 @@ "DocsURL": "", "MarketplaceURL": "", "SupportURL": "mailto:janseliw@gmail.com", - "EngineVersion": "5.2.0", + "EngineVersion": "5.3.0", "EnabledByDefault": true, "CanContainContent": true, "IsBetaVersion": false, @@ -31,6 +31,10 @@ { "Name": "GameplayTagsEditor", "Enabled": true + }, + { + "Name": "DataRegistry", + "Enabled": true } ] } diff --git a/Source/SettingsWidgetConstructor/Private/Data/SettingArchetypesData.cpp b/Source/SettingsWidgetConstructor/Private/Data/SettingArchetypesData.cpp new file mode 100644 index 0000000..21c2781 --- /dev/null +++ b/Source/SettingsWidgetConstructor/Private/Data/SettingArchetypesData.cpp @@ -0,0 +1,265 @@ +// Copyright (c) Yevhenii Selivanov + +#include "Data/SettingArchetypesData.h" +//--- +#include "Data/SettingsDataAsset.h" +#include "Data/SettingTag.h" +#include "UI/SettingSubWidget.h" +#include "UI/SettingsWidget.h" +//--- +#include UE_INLINE_GENERATED_CPP_BY_NAME(SettingArchetypesData) + +/********************************************************************************************* + * FSettingsButton + ********************************************************************************************* */ + +// Returns the sub-widget class of this setting type +TSubclassOf FSettingsButton::GetSubWidgetClass() const +{ + return USettingsDataAsset::Get().GetButtonClass(); +} + +// Calls the Set function of the Settings Widget of this setting type +void FSettingsButton::SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) +{ + SettingsWidget.SetSettingButtonPressed(Tag); +} + +// Calls the Bind function of the Settings Widget of this setting type +void FSettingsButton::BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) +{ + SettingsWidget.BindButton(PrimaryData, *this); +} + +// Calls the Add function of the Settings Widget of this setting type +void FSettingsButton::AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) +{ + SettingsWidget.AddButton(PrimaryData, *this); +} + +/********************************************************************************************* + * FSettingsCheckbox + ********************************************************************************************* */ + +// Returns the sub-widget class of this setting type +TSubclassOf FSettingsCheckbox::GetSubWidgetClass() const +{ + return USettingsDataAsset::Get().GetCheckboxClass(); +} + +// Calls the Get function of the Settings Widget of this setting type +void FSettingsCheckbox::GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const +{ + const bool Value = SettingsWidget.GetCheckboxValue(Tag); + OutResult = Value ? TEXT("true") : TEXT("false"); +} + +// Calls the Set function of the Settings Widget of this setting type +void FSettingsCheckbox::SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) +{ + const bool NewValue = Value.ToBool(); + SettingsWidget.SetSettingCheckbox(Tag, NewValue); +} + +// Calls the Bind function of the Settings Widget of this setting type +void FSettingsCheckbox::BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) +{ + SettingsWidget.BindCheckbox(PrimaryData, *this); +} + +// Calls the Add function of the Settings Widget of this setting type +void FSettingsCheckbox::AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) +{ + SettingsWidget.AddCheckbox(InOutPrimaryData, *this); +} + +/********************************************************************************************* + * FSettingsCombobox + ********************************************************************************************* */ + +// Returns the sub-widget class of this setting type +TSubclassOf FSettingsCombobox::GetSubWidgetClass() const +{ + return USettingsDataAsset::Get().GetComboboxClass(); +} + +// Calls the Get function of the Settings Widget of this setting type +void FSettingsCombobox::GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const +{ + const int32 Value = SettingsWidget.GetComboboxIndex(Tag); + OutResult = FString::Printf(TEXT("%d"), Value); +} + +// Calls the Set function of the Settings Widget of this setting type +void FSettingsCombobox::SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) +{ + if (Value.IsNumeric()) + { + const int32 NewValue = FCString::Atoi(*Value); + SettingsWidget.SetSettingComboboxIndex(Tag, NewValue); + } + else + { + static const FString Delimiter = TEXT(","); + TArray SeparatedStrings; + Value.ParseIntoArray(SeparatedStrings, *Delimiter); + + TArray NewMembers; + NewMembers.Reserve(SeparatedStrings.Num()); + for (FString& StringIt : SeparatedStrings) + { + NewMembers.Emplace(FText::FromString(MoveTemp(StringIt))); + } + SettingsWidget.SetSettingComboboxMembers(Tag, NewMembers); + } +} + +// Calls the Bind function of the Settings Widget of this setting type +void FSettingsCombobox::BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) +{ + SettingsWidget.BindCombobox(PrimaryData, *this); +} + +// Calls the Add function of the Settings Widget of this setting type +void FSettingsCombobox::AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) +{ + SettingsWidget.AddCombobox(InOutPrimaryData, *this); +} + +/********************************************************************************************* + * FSettingsSlider + ********************************************************************************************* */ + +// Returns the sub-widget class of this setting type +TSubclassOf FSettingsSlider::GetSubWidgetClass() const +{ + return USettingsDataAsset::Get().GetSliderClass(); +} + +// Calls the Get function of the Settings Widget of this setting type +void FSettingsSlider::GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const +{ + const double Value = SettingsWidget.GetSliderValue(Tag); + OutResult = FString::Printf(TEXT("%f"), Value); +} + +// Calls the Set function of the Settings Widget of this setting type +void FSettingsSlider::SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) +{ + const double NewValue = FCString::Atod(*Value); + SettingsWidget.SetSettingSlider(Tag, NewValue); +} + +// Calls the Bind function of the Settings Widget of this setting type +void FSettingsSlider::BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) +{ + SettingsWidget.BindSlider(PrimaryData, *this); +} + +// Calls the Add function of the Settings Widget of this setting type +void FSettingsSlider::AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) +{ + SettingsWidget.AddSlider(InOutPrimaryData, *this); +} + +/********************************************************************************************* + * FSettingsTextLine + ********************************************************************************************* */ + +// Returns the sub-widget class of this setting type +TSubclassOf FSettingsTextLine::GetSubWidgetClass() const +{ + return USettingsDataAsset::Get().GetTextLineClass(); +} + +// Calls the Get function of the Settings Widget of this setting type +void FSettingsTextLine::GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const +{ + FText OutText; + SettingsWidget.GetTextLineValue(Tag, OutText); + OutResult = OutText.ToString(); +} + +// Calls the Set function of the Settings Widget of this setting type +void FSettingsTextLine::SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) +{ + const FText NewValue = FText::FromString(Value); + SettingsWidget.SetSettingTextLine(Tag, NewValue); +} + +// Calls the Bind function of the Settings Widget of this setting type +void FSettingsTextLine::BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) +{ + SettingsWidget.BindTextLine(PrimaryData, *this); +} + +// Calls the Add function of the Settings Widget of this setting type +void FSettingsTextLine::AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) +{ + SettingsWidget.AddTextLine(InOutPrimaryData, *this); +} + +/********************************************************************************************* + * FSettingsUserInput + ********************************************************************************************* */ + +// Returns the sub-widget class of this setting type +TSubclassOf FSettingsUserInput::GetSubWidgetClass() const +{ + return USettingsDataAsset::Get().GetUserInputClass(); +} + +// Calls the Get function of the Settings Widget of this setting type +void FSettingsUserInput::GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const +{ + const FName Value = SettingsWidget.GetUserInputValue(Tag); + OutResult = Value.ToString(); +} + +// Calls the Set function of the Settings Widget of this setting type +void FSettingsUserInput::SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) +{ + const FName NewValue = *Value; + SettingsWidget.SetSettingUserInput(Tag, NewValue); +} + +// Calls the Bind function of the Settings Widget of this setting type +void FSettingsUserInput::BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) +{ + SettingsWidget.BindUserInput(PrimaryData, *this); +} + +// Calls the Add function of the Settings Widget of this setting type +void FSettingsUserInput::AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) +{ + SettingsWidget.AddUserInput(InOutPrimaryData, *this); +} + +/********************************************************************************************* + * FSettingsCustomWidget + ********************************************************************************************* */ + +// Returns the sub-widget class of this setting type +TSubclassOf FSettingsCustomWidget::GetSubWidgetClass() const +{ + return CustomWidgetClass; +} + +// Calls the Get function of the Settings Widget of this setting type +void FSettingsCustomWidget::GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const +{ + const TSoftObjectPtr CustomWidget = SettingsWidget.GetCustomWidget(Tag); + OutResult = CustomWidget.IsValid() ? CustomWidget.ToSoftObjectPath().ToString() : TEXT(""); +} + +// Calls the Bind function of the Settings Widget of this setting type +void FSettingsCustomWidget::BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) +{ + SettingsWidget.BindCustomWidget(PrimaryData, *this); +} + +// Calls the Add function of the Settings Widget of this setting type +void FSettingsCustomWidget::AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) +{ + SettingsWidget.AddCustomWidget(InOutPrimaryData, *this); +} diff --git a/Source/SettingsWidgetConstructor/Private/Data/SettingTag.cpp b/Source/SettingsWidgetConstructor/Private/Data/SettingTag.cpp index e52347a..eeeede2 100644 --- a/Source/SettingsWidgetConstructor/Private/Data/SettingTag.cpp +++ b/Source/SettingsWidgetConstructor/Private/Data/SettingTag.cpp @@ -2,7 +2,7 @@ #include "Data/SettingTag.h" //--- -#include "GameplayTags/Classes/GameplayTagsManager.h" +#include "GameplayTagsManager.h" //--- #include UE_INLINE_GENERATED_CPP_BY_NAME(SettingTag) diff --git a/Source/SettingsWidgetConstructor/Private/Data/SettingsDataAsset.cpp b/Source/SettingsWidgetConstructor/Private/Data/SettingsDataAsset.cpp new file mode 100644 index 0000000..7eaceba --- /dev/null +++ b/Source/SettingsWidgetConstructor/Private/Data/SettingsDataAsset.cpp @@ -0,0 +1,57 @@ +// Copyright (c) Yevhenii Selivanov + +#include "Data/SettingsDataAsset.h" +//--- +#include "Data/SettingsDataTable.h" +#include "MyUtilsLibraries/SettingsUtilsLibrary.h" +//--- +#include UE_INLINE_GENERATED_CPP_BY_NAME(SettingsDataAsset) + +// Returns the data table, it has to be set manually +const USettingsDataTable* USettingsDataAsset::GetSettingsDataTable() const +{ + return SettingsDataTableInternal.LoadSynchronous(); +} + +// Returns the Settings Data Registry asset, is automatically set by default to which 'Settings Data Table' is added by itself +const UDataRegistry* USettingsDataAsset::GetSettingsDataRegistry() const +{ + return SettingsDataRegistryInternal.LoadSynchronous(); +} + +// Overrides post init to register Settings Data Table by default on startup +void USettingsDataAsset::PostInitProperties() +{ + Super::PostInitProperties(); + + if (!GEngine || !GEngine->IsInitialized()) + { + FCoreDelegates::OnPostEngineInit.AddUObject(this, &ThisClass::OnPostEngineInit); + } + else + { + OnPostEngineInit(); + } +} + +// Is called once Engine is initialized, so we can register Settings Data Table by default on startup +void USettingsDataAsset::OnPostEngineInit() +{ + USettingsUtilsLibrary::RegisterDataTable(SettingsDataTableInternal); +} + +#if WITH_EDITOR +// Overrides property change events to handle picking Settings Data Table +void USettingsDataAsset::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + static const FName PropertyName = GET_MEMBER_NAME_CHECKED(USettingsDataAsset, SettingsDataTableInternal); + const FProperty* Property = PropertyChangedEvent.Property; + if (Property && Property->GetFName() == PropertyName) + { + // The Settings Data Table has been changed, so we have to register it + USettingsUtilsLibrary::RegisterDataTable(SettingsDataTableInternal); + } +} +#endif // WITH_EDITOR diff --git a/Source/SettingsWidgetConstructor/Private/Data/SettingsDataTable.cpp b/Source/SettingsWidgetConstructor/Private/Data/SettingsDataTable.cpp index 3cfb6ed..88d2eea 100644 --- a/Source/SettingsWidgetConstructor/Private/Data/SettingsDataTable.cpp +++ b/Source/SettingsWidgetConstructor/Private/Data/SettingsDataTable.cpp @@ -2,6 +2,8 @@ #include "Data/SettingsDataTable.h" //--- +#include "Data/SettingsRow.h" +//--- #if WITH_EDITOR #include "DataTableEditorUtils.h" // FDataTableEditorUtils::RenameRow #endif diff --git a/Source/SettingsWidgetConstructor/Private/Data/SettingsRow.cpp b/Source/SettingsWidgetConstructor/Private/Data/SettingsRow.cpp index 3a550ee..c2df7a1 100644 --- a/Source/SettingsWidgetConstructor/Private/Data/SettingsRow.cpp +++ b/Source/SettingsWidgetConstructor/Private/Data/SettingsRow.cpp @@ -16,11 +16,22 @@ bool FSettingsPrimary::operator==(const FSettingsPrimary& Other) const return GetTypeHash(*this) == GetTypeHash(Other); } +// Is executed to obtain holding object +UObject* FSettingsPrimary::GetSettingOwner(const UObject* WorldContext) const +{ + if (OwnerFunc.IsBound()) + { + return OwnerFunc.Execute(WorldContext); + } + + return nullptr; +} + // Creates a hash value uint32 GetTypeHash(const FSettingsPrimary& Other) { const uint32 TagHash = GetTypeHash(Other.Tag); - const uint32 ObjectContextHash = GetTypeHash(Other.StaticContext); + const uint32 ObjectContextHash = GetTypeHash(Other.Owner); const uint32 SetterHash = GetTypeHash(Other.Setter); const uint32 GetterHash = GetTypeHash(Other.Getter); const uint32 CaptionHash = GetTypeHash(Other.Caption.ToString()); @@ -32,22 +43,21 @@ uint32 GetTypeHash(const FSettingsPrimary& Other) const uint32 LineHeightHash = GetTypeHash(Other.LineHeight); const uint32 StartOnNextColumnHash = GetTypeHash(Other.bStartOnNextColumn); const uint32 SettingsToUpdateHash = GetTypeHash(Other.SettingsToUpdate.ToStringSimple()); - return HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine( - TagHash, ObjectContextHash), SetterHash), GetterHash), CaptionHash), TooltipHash), PaddingLeftHash), PaddingTopHash), PaddingRightHash), PaddingBottomHash), LineHeightHash), StartOnNextColumnHash), SettingsToUpdateHash); + return HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(HashCombine(TagHash, ObjectContextHash), SetterHash), GetterHash), CaptionHash), TooltipHash), PaddingLeftHash), PaddingTopHash), PaddingRightHash), PaddingBottomHash), LineHeightHash), StartOnNextColumnHash), SettingsToUpdateHash); } // Returns the pointer to one of the chosen in-game type -const FSettingsDataBase* FSettingsPicker::GetChosenSettingsData() const +FSettingsDataBase* FSettingsPicker::GetChosenSettingsData() const { - const FSettingsDataBase* FoundSetting = nullptr; if (!SettingsType.IsNone()) { static const UScriptStruct* const& SettingsPickerStruct = StaticStruct(); const FProperty* FoundProperty = SettingsPickerStruct ? SettingsPickerStruct->FindPropertyByName(SettingsType) : nullptr; const FStructProperty* FoundStructProperty = CastField(FoundProperty); - FoundSetting = FoundStructProperty ? FoundStructProperty->ContainerPtrToValuePtr(this, 0) : nullptr; + const FSettingsDataBase* FoundSetting = FoundStructProperty ? FoundStructProperty->ContainerPtrToValuePtr(this, 0) : nullptr; + return const_cast(FoundSetting); } - return FoundSetting; + return nullptr; } // Compares for equality diff --git a/Source/SettingsWidgetConstructor/Private/FunctionPickerData/FunctionPicker.cpp b/Source/SettingsWidgetConstructor/Private/FunctionPickerData/FunctionPicker.cpp index 579daac..ca5a456 100644 --- a/Source/SettingsWidgetConstructor/Private/FunctionPickerData/FunctionPicker.cpp +++ b/Source/SettingsWidgetConstructor/Private/FunctionPickerData/FunctionPicker.cpp @@ -8,7 +8,7 @@ const FSWCFunctionPicker FSWCFunctionPicker::Empty = FSWCFunctionPicker(); // Custom constructor to set all members values FSWCFunctionPicker::FSWCFunctionPicker(UClass* InFunctionClass, FName InFunctionName) : FunctionClass(InFunctionClass) - , FunctionName(InFunctionName) {} + , FunctionName(InFunctionName) {} // Returns the function pointer based on set data to this structure UFunction* FSWCFunctionPicker::GetFunction() const @@ -19,7 +19,7 @@ UFunction* FSWCFunctionPicker::GetFunction() const } if (FunctionClass - && !FunctionName.IsNone()) + && !FunctionName.IsNone()) { UFunction* FoundFunction = FunctionClass->FindFunctionByName(FunctionName, EIncludeSuperFlag::ExcludeSuper); CachedFunctionInternal = FoundFunction; @@ -29,11 +29,10 @@ UFunction* FSWCFunctionPicker::GetFunction() const return nullptr; } -// Compares for equality -bool FSWCFunctionPicker::operator==(const FSWCFunctionPicker& Other) const +// Returns string in text format: Class::Function +FString FSWCFunctionPicker::ToDisplayString() const { - return Other.FunctionClass->IsChildOf(this->FunctionClass) - && Other.FunctionName == this->FunctionName; + return IsValid() ? FString::Printf(TEXT("%s::%s"), *FunctionClass->GetName(), *FunctionName.ToString()) : FString(); } // Creates a hash value diff --git a/Source/SettingsWidgetConstructor/Private/FunctionPickerData/SWCFunctionPicker.h b/Source/SettingsWidgetConstructor/Private/FunctionPickerData/SWCFunctionPicker.h index 40454e8..af57189 100644 --- a/Source/SettingsWidgetConstructor/Private/FunctionPickerData/SWCFunctionPicker.h +++ b/Source/SettingsWidgetConstructor/Private/FunctionPickerData/SWCFunctionPicker.h @@ -21,7 +21,7 @@ * * Example: * UPROPERTY(EditDefaultsOnly, meta = (FunctionSetterTemplate = "/Script/FunctionPicker.FunctionPickerTemplate::OnSetMembers__DelegateSignature")) - * FFunctionPicker SetMembers = FFunctionPicker::Empty; + * FSWCFunctionPicker SetMembers = FSWCFunctionPicker::Empty; */ USTRUCT(BlueprintType) struct SETTINGSWIDGETCONSTRUCTOR_API FSWCFunctionPicker @@ -38,26 +38,35 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSWCFunctionPicker FSWCFunctionPicker(UClass* InFunctionClass, FName InFunctionName); /** The class where function can be found. */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (DisplayName = "Class")) + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (DisplayName = "Class", AllowAbstract, ShowOnlyInnerProperties)) TObjectPtr FunctionClass = nullptr; /** The function name to choose for specified class.*/ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (DisplayName = "Function")) + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (DisplayName = "Function", ShowOnlyInnerProperties)) FName FunctionName = NAME_None; /** Returns true if is valid. */ - FORCEINLINE bool IsValid() const { return !(*this == Empty); } + FORCEINLINE bool IsValid() const { return FunctionClass && !FunctionName.IsNone(); } /** Returns the function pointer based on set data to this structure. */ UFunction* GetFunction() const; + /** Returns string in text format: Class::Function. */ + FString ToDisplayString() const; + /** Compares for equality. * @param Other The other object being compared. */ - bool operator==(const FSWCFunctionPicker& Other) const; + FORCEINLINE bool operator==(const FSWCFunctionPicker& Other) const { return GetTypeHash(*this) == GetTypeHash(Other); } /** Creates a hash value. * @param Other the other object to create a hash value for. */ - friend uint32 GetTypeHash(const FSWCFunctionPicker& Other); + friend SETTINGSWIDGETCONSTRUCTOR_API uint32 GetTypeHash(const FSWCFunctionPicker& Other); + + /** bool operator */ + FORCEINLINE operator bool() const { return IsValid(); } + + /** FName operator */ + FORCEINLINE operator FName() const { return FunctionName; } protected: /** Contains cached function ptr for performance reasons. */ diff --git a/Source/SettingsWidgetConstructor/Private/FunctionPickerData/SWCFunctionPickerTemplate.h b/Source/SettingsWidgetConstructor/Private/FunctionPickerData/SWCFunctionPickerTemplate.h index 7009592..80e7d0e 100644 --- a/Source/SettingsWidgetConstructor/Private/FunctionPickerData/SWCFunctionPickerTemplate.h +++ b/Source/SettingsWidgetConstructor/Private/FunctionPickerData/SWCFunctionPickerTemplate.h @@ -9,7 +9,7 @@ /** * Delegates wrapper that are used as templates for FFunctionPicker properties. * Has to have reflection to allow find its members by FFunctionPickerCustomization: - * UFunctionPickerTemplate::StaticClass()->FindFunctionByName("OnStaticContext__DelegateSignature"); + * UFunctionPickerTemplate::StaticClass()->FindFunctionByName("OnGetterObject__DelegateSignature"); * DECLARE_DYNAMIC_DELEGATE can't be declared under USTRUCT */ UCLASS(Abstract, Const, Transient) @@ -18,7 +18,7 @@ class USWCFunctionPickerTemplate : public UObject GENERATED_BODY() public: - DECLARE_DYNAMIC_DELEGATE_RetVal(UObject*, FOnStaticContext); + DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(UObject*, FOnGetterObject, const UObject*, WorldContext); DECLARE_DYNAMIC_DELEGATE(FOnButtonPressed); @@ -45,4 +45,4 @@ class USWCFunctionPickerTemplate : public UObject DECLARE_DYNAMIC_DELEGATE_OneParam(FOnGetMembers, TArray&, OutParam); DECLARE_DYNAMIC_DELEGATE_RetVal(FName, FOnGetterName); -}; \ No newline at end of file +}; diff --git a/Source/SettingsWidgetConstructor/Private/MyUtilsLibraries/SettingsUtilsLibrary.cpp b/Source/SettingsWidgetConstructor/Private/MyUtilsLibraries/SettingsUtilsLibrary.cpp new file mode 100644 index 0000000..aa92a7a --- /dev/null +++ b/Source/SettingsWidgetConstructor/Private/MyUtilsLibraries/SettingsUtilsLibrary.cpp @@ -0,0 +1,191 @@ +// Copyright (c) Yevhenii Selivanov + +#include "MyUtilsLibraries/SettingsUtilsLibrary.h" +//--- +#include "MyUtilsLibraries/SWCWidgetUtilsLibrary.h" +#include "GameFramework/GameUserSettings.h" +#include "UI/SettingsWidget.h" +#include "Data/SettingsDataAsset.h" +#include "Data/SettingsDataTable.h" +#include "Data/SettingsRow.h" +//--- +#include "Engine/Engine.h" +#include "Engine/World.h" +#include "DataRegistrySource_DataTable.h" +#include "DataRegistrySubsystem.h" +//--- +#include UE_INLINE_GENERATED_CPP_BY_NAME(SettingsUtilsLibrary) + +// Returns the Settings widget from viewport +USettingsWidget* USettingsUtilsLibrary::GetSettingsWidget(const UObject* WorldContextObject) +{ + UWorld* World = GEngine ? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull) : nullptr; + return FSWCWidgetUtilsLibrary::FindWidgetOfClass(World); +} + +// Returns the Game User Settings object +UGameUserSettings* USettingsUtilsLibrary::GetGameUserSettings(const UObject* OptionalWorldContext/* = nullptr*/) +{ + return GEngine ? GEngine->GetGameUserSettings() : nullptr; +} + +// Returns all Settings Rows from project's Settings Data Table and all other additional Data Tables from 'SettingsDataTable' Data Registry +void USettingsUtilsLibrary::GetAllSettingRows(TMap& OutSettingRows) +{ + if (!OutSettingRows.IsEmpty()) + { + OutSettingRows.Empty(); + } + + TSet OutDataTables; + GetAllSettingDataTables(OutDataTables); + + if (!ensureMsgf(!OutDataTables.IsEmpty(), TEXT("ASSERT: [%i] %s:\n'Settings Data Table' is not set in the 'Project Settings', can't retrieve any settings!"), __LINE__, *FString(__FUNCTION__))) + { + return; + } + + /** + * Sort Setting Rows based on the FSettingsPrimary::ShowNextToSettingOverride property. + * All the next settings after ShowNextToSettingOverride in the same table will be also shown next to it. + * Sort is needed since setting can be shown based on another setting in different Settings Data Table, so we want to fix the order. + */ + + TArray OrderedSettings; + TMap> OverrideBlocks; + + // Collect settings and override blocks + for (const USettingsDataTable* TableIt : OutDataTables) + { + checkf(TableIt, TEXT("ERROR: [%i] %s:\n'TableIt' is null!"), __LINE__, *FString(__FUNCTION__)); + + TMap TableRows; + TableIt->GetSettingRows(TableRows); + + TArray CurrentOverrideBlock; + FSettingTag CurrentOverrideTag; + + for (const TTuple& Pair : TableRows) + { + const FSettingsRow& Row = Pair.Value; + const FSettingTag& OverrideTag = Row.SettingsPicker.PrimaryData.ShowNextToSettingOverride; + + if (OverrideTag.IsValid()) + { + // Store the previous block if any, then start a new block + if (CurrentOverrideBlock.Num() > 0) + { + OverrideBlocks.Emplace(CurrentOverrideTag, MoveTemp(CurrentOverrideBlock)); + } + CurrentOverrideBlock.Empty(); + CurrentOverrideTag = OverrideTag; + } + + if (CurrentOverrideTag.IsValid()) + { + CurrentOverrideBlock.Emplace(Row); + } + else + { + OrderedSettings.Emplace(Row); + } + } + + // Add the last block if any + if (CurrentOverrideBlock.Num() > 0) + { + OverrideBlocks.Emplace(CurrentOverrideTag, MoveTemp(CurrentOverrideBlock)); + } + } + + // Build the final map, handling the override blocks + for (const FSettingsRow& Row : OrderedSettings) + { + const FName Tag = Row.SettingsPicker.PrimaryData.Tag.GetTagName(); + OutSettingRows.Add(Tag, Row); + + // Check if there's an override block for this tag + const FSettingTag SettingTag = Row.SettingsPicker.PrimaryData.Tag; + TArray* OverrideBlock = OverrideBlocks.Find(SettingTag); + if (OverrideBlock) + { + // Add the override block next to the current setting + for (const FSettingsRow& OverrideRow : *OverrideBlock) + { + const FName OverrideTag = OverrideRow.SettingsPicker.PrimaryData.Tag.GetTagName(); + OutSettingRows.Add(OverrideTag, OverrideRow); + } + } + } +} + +/********************************************************************************************* + * Multiple Data Tables support + ********************************************************************************************* */ + +// Registers the Settings Data Table with the Data Registry +void USettingsUtilsLibrary::RegisterDataTable(const TSoftObjectPtr SettingsDataTable) +{ + UDataRegistrySubsystem* DataRegistrySubsystem = UDataRegistrySubsystem::Get(); + checkf(DataRegistrySubsystem, TEXT("ERROR: [%i] %s:\n'DataRegistrySubsystem' is null!"), __LINE__, *FString(__FUNCTION__)); + + const TSoftObjectPtr& SettingsDataRegistry = USettingsDataAsset::Get().GetSettingsDataRegistrySoft(); + if (ensureMsgf(!SettingsDataRegistry.IsNull(), TEXT("ASSERT: 'SettingsDataRegistry' is null, it has to be set automatically, something went wrong!"))) + { + // Initialize the Settings Data Registry + DataRegistrySubsystem->LoadRegistryPath(SettingsDataRegistry.ToSoftObjectPath()); + } + + // If set, add the Settings Data Table to the Settings Data Registry + const FSoftObjectPath DataTablePath = SettingsDataTable.ToSoftObjectPath(); + if (!DataTablePath.IsNull()) + { + TMap> AssetMap; + static const FDataRegistryType RegistryToAddTo{TEXT("SettingsDataTable")}; + TArray& AssetList = AssetMap.Add(RegistryToAddTo); + AssetList.Emplace(DataTablePath); + DataRegistrySubsystem->PreregisterSpecificAssets(AssetMap); + } +} + +// Returns all Settings Data Tables added to 'SettingsDataTable' Data Registry +void USettingsUtilsLibrary::GetAllSettingDataTables(TSet& OutDataTables) +{ + if (!OutDataTables.IsEmpty()) + { + OutDataTables.Empty(); + } + + const UDataRegistry* SettingsDataRegistry = USettingsDataAsset::Get().GetSettingsDataRegistry(); + if (!ensureMsgf(SettingsDataRegistry, TEXT("ASSERT: 'SettingsDataRegistry' is not loaded, can't retrieve any settings!"))) + { + return; + } + + const UScriptStruct* SettingStruct = FSettingsRow::StaticStruct(); + TMap OutItemMap; + SettingsDataRegistry->GetAllCachedItems(OutItemMap, SettingStruct); + + // Obtain all Settings Data Tables from the registry + for (const TTuple& ItemIt : OutItemMap) + { + FDataRegistryLookup Lookup; + SettingsDataRegistry->ResolveDataRegistryId(/*out*/Lookup, ItemIt.Key); + + FName ResolvedName; + constexpr int32 LookupIndex = 0; + UDataRegistrySource* LookupSource = SettingsDataRegistry->LookupSource(ResolvedName, Lookup, LookupIndex); + + const UDataRegistrySource_DataTable* DataTableSource = Cast(LookupSource); + if (!DataTableSource || DataTableSource->SourceTable.IsNull()) + { + continue; + } + + const USettingsDataTable* DataTable = Cast(DataTableSource->SourceTable.LoadSynchronous()); + if (ensureMsgf(DataTable, TEXT("ASSERT: [%i] %s:\nNext Settings Data Table is found, but can't be loaded: %s"), __LINE__, *FString(__FUNCTION__), *DataTableSource->SourceTable.GetAssetName())) + { + OutDataTables.Add(DataTable); + } + } +} diff --git a/Source/SettingsWidgetConstructor/Private/UI/SettingSubWidget.cpp b/Source/SettingsWidgetConstructor/Private/UI/SettingSubWidget.cpp index 1082f15..536f4bd 100644 --- a/Source/SettingsWidgetConstructor/Private/UI/SettingSubWidget.cpp +++ b/Source/SettingsWidgetConstructor/Private/UI/SettingSubWidget.cpp @@ -14,6 +14,7 @@ #include "Components/Slider.h" #include "Components/TextBlock.h" #include "Widgets/Input/SButton.h" +#include "Widgets/Input/SComboBox.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SSlider.h" //--- diff --git a/Source/SettingsWidgetConstructor/Private/UI/SettingsWidget.cpp b/Source/SettingsWidgetConstructor/Private/UI/SettingsWidget.cpp index 97b179d..e88dae0 100644 --- a/Source/SettingsWidgetConstructor/Private/UI/SettingsWidget.cpp +++ b/Source/SettingsWidgetConstructor/Private/UI/SettingsWidget.cpp @@ -2,16 +2,20 @@ #include "UI/SettingsWidget.h" //--- +#include "Data/SettingsDataAsset.h" +#include "MyUtilsLibraries/SettingsUtilsLibrary.h" +#include "UI/SettingSubWidget.h" +//--- #include "Blueprint/WidgetLayoutLibrary.h" #include "Components/SizeBox.h" #include "GameFramework/GameUserSettings.h" //--- -#include "Data/SettingsDataAsset.h" -#include "Data/SettingsDataTable.h" -#include "UI/SettingSubWidget.h" -//--- #include UE_INLINE_GENERATED_CPP_BY_NAME(SettingsWidget) +/* --------------------------------------------------- + * Public functions + * --------------------------------------------------- */ + // Try to find the setting row const FSettingsPicker& USettingsWidget::FindSettingRow(FName PotentialTagName) const { @@ -56,7 +60,7 @@ void USettingsWidget::SaveSettings() for (const TTuple& RowIt : SettingsTableRowsInternal) { - if (UObject* ContextObject = RowIt.Value.PrimaryData.StaticContextObject.Get()) + if (UObject* ContextObject = RowIt.Value.PrimaryData.GetSettingOwner(this)) { ContextObject->SaveConfig(); } @@ -66,7 +70,7 @@ void USettingsWidget::SaveSettings() // Apply all current settings on device void USettingsWidget::ApplySettings() { - UGameUserSettings* GameUserSettings = GEngine->GetGameUserSettings(); + UGameUserSettings* GameUserSettings = USettingsUtilsLibrary::GetGameUserSettings(); if (!GameUserSettings) { return; @@ -100,38 +104,24 @@ void USettingsWidget::UpdateSettings(const FGameplayTagContainer& SettingsToUpda continue; } - const FSettingsDataBase* ChosenData = Setting.GetChosenSettingsData(); + FSettingsDataBase* ChosenData = Setting.GetChosenSettingsData(); if (!ChosenData) { continue; } - if (ChosenData == &Setting.Checkbox) - { - const bool NewValue = GetCheckboxValue(SettingTag); - SetSettingCheckbox(SettingTag, NewValue); - } - else if (ChosenData == &Setting.Combobox) - { - const int32 NewValue = GetComboboxIndex(SettingTag); - SetSettingComboboxIndex(SettingTag, NewValue); - } - else if (ChosenData == &Setting.Slider) - { - const double NewValue = GetSliderValue(SettingTag); - SetSettingSlider(SettingTag, NewValue); - } - else if (ChosenData == &Setting.TextLine) - { - FText NewValue = TEXT_NONE; - GetTextLineValue(SettingTag, /*Out*/NewValue); - SetSettingTextLine(SettingTag, NewValue); - } - else if (ChosenData == &Setting.UserInput) + UObject* Owner = Setting.PrimaryData.GetSettingOwner(this); + if (!Owner) { - const FName NewValue = GetUserInputValue(SettingTag); - SetSettingUserInput(SettingTag, NewValue); + continue; } + + // Obtain the latest value from configs and set it + Owner->LoadConfig(); + + FString Result; + ChosenData->GetSettingValue(*this, SettingTag, /*Out*/Result); + ChosenData->SetSettingValue(*this, SettingTag, Result); } } @@ -151,6 +141,10 @@ const FSettingTag& USettingsWidget::GetTagByFunction(const FSettingFunctionPicke return FSettingTag::EmptySettingTag; } +/* --------------------------------------------------- + * Setters by setting types + * --------------------------------------------------- */ + // Set value to the option by tag void USettingsWidget::SetSettingValue(FName TagName, const FString& Value) { @@ -160,65 +154,45 @@ void USettingsWidget::SetSettingValue(FName TagName, const FString& Value) return; } - const FSettingsDataBase* ChosenData = FoundRow.GetChosenSettingsData(); + FSettingsDataBase* ChosenData = FoundRow.GetChosenSettingsData(); if (!ChosenData) { return; } const FSettingTag& Tag = FoundRow.PrimaryData.Tag; - if (!Tag.IsValid()) - { - return; - } - - if (ChosenData == &FoundRow.Button) - { - SetSettingButtonPressed(Tag); - } - else if (ChosenData == &FoundRow.Checkbox) - { - const bool NewValue = Value.ToBool(); - SetSettingCheckbox(Tag, NewValue); - } - else if (ChosenData == &FoundRow.Combobox) - { - if (Value.IsNumeric()) - { - const int32 NewValue = FCString::Atoi(*Value); - SetSettingComboboxIndex(Tag, NewValue); - } - else - { - static const FString Delimiter = TEXT(","); - TArray SeparatedStrings; - Value.ParseIntoArray(SeparatedStrings, *Delimiter); - - TArray NewMembers; - NewMembers.Reserve(SeparatedStrings.Num()); - for (FString& StringIt : SeparatedStrings) - { - NewMembers.Emplace(FText::FromString(MoveTemp(StringIt))); - } - SetSettingComboboxMembers(Tag, NewMembers); - } - } - else if (ChosenData == &FoundRow.Slider) - { - const double NewValue = FCString::Atod(*Value); - SetSettingSlider(Tag, NewValue); - } - else if (ChosenData == &FoundRow.TextLine) - { - const FText NewValue = FText::FromString(Value); - SetSettingTextLine(Tag, NewValue); - } - else if (ChosenData == &FoundRow.UserInput) - { - const FName NewValue = *Value; - SetSettingUserInput(Tag, NewValue); - } -} + if (Tag.IsValid()) + { + ChosenData->SetSettingValue(*this, Tag, Value); + } +} + +/** Executes the common pattern of setting a value, executing if bound, and updating the settings. + * @param Tag The tag used to find the setting row. + * @param DataMember The member that holds the desired value. + * @param MemberValue The specific member to set the value to. + * @param Value The new value to set. + * @param SetterExpression The expression to update the setter delegate. */ +#define SET_SETTING_VALUE(Tag, DataMember, MemberValue, Value, SetterExpression) \ + do { \ + if (!Tag.IsValid()) \ + { \ + return; \ + } \ + FSettingsPicker* FoundRowPtr = SettingsTableRowsInternal.Find(Tag.GetTagName());\ + if (!FoundRowPtr) \ + { \ + return; \ + } \ + auto& Data = FoundRowPtr->DataMember; \ + if (Data.MemberValue == Value) \ + { \ + return; \ + } \ + Data.MemberValue = Value; \ + Data.SetterExpression.ExecuteIfBound(Value); \ + UpdateSettings(FoundRowPtr->PrimaryData.SettingsToUpdate); \ + } while (0) // Press button void USettingsWidget::SetSettingButtonPressed(const FSettingTag& ButtonTag) @@ -244,26 +218,7 @@ void USettingsWidget::SetSettingButtonPressed(const FSettingTag& ButtonTag) // Toggle checkbox void USettingsWidget::SetSettingCheckbox(const FSettingTag& CheckboxTag, bool InValue) { - if (!CheckboxTag.IsValid()) - { - return; - } - - FSettingsPicker* SettingsRowPtr = SettingsTableRowsInternal.Find(CheckboxTag.GetTagName()); - if (!SettingsRowPtr) - { - return; - } - - bool& bIsSetRef = SettingsRowPtr->Checkbox.bIsSet; - if (bIsSetRef == InValue) - { - return; - } - - bIsSetRef = InValue; - SettingsRowPtr->Checkbox.OnSetterBool.ExecuteIfBound(InValue); - UpdateSettings(SettingsRowPtr->PrimaryData.SettingsToUpdate); + SET_SETTING_VALUE(CheckboxTag, Checkbox, bIsSet, InValue, OnSetterBool); // BP implementation SetCheckbox(CheckboxTag, InValue); @@ -273,32 +228,12 @@ void USettingsWidget::SetSettingCheckbox(const FSettingTag& CheckboxTag, bool In // Set chosen member index for a combobox void USettingsWidget::SetSettingComboboxIndex(const FSettingTag& ComboboxTag, int32 InValue) { - if (!ComboboxTag.IsValid()) - { - return; - } - if (InValue == INDEX_NONE) { return; } - FSettingsPicker* SettingsRowPtr = SettingsTableRowsInternal.Find(ComboboxTag.GetTagName()); - if (!SettingsRowPtr) - { - return; - } - - FSettingsPicker& SettingsRowRef = *SettingsRowPtr; - int32& ChosenMemberIndexRef = SettingsRowRef.Combobox.ChosenMemberIndex; - if (ChosenMemberIndexRef == InValue) - { - return; - } - - ChosenMemberIndexRef = InValue; - SettingsRowRef.Combobox.OnSetterInt.ExecuteIfBound(InValue); - UpdateSettings(SettingsRowRef.PrimaryData.SettingsToUpdate); + SET_SETTING_VALUE(ComboboxTag, Combobox, ChosenMemberIndex, InValue, OnSetterInt); // BP implementation SetComboboxIndex(ComboboxTag, InValue); @@ -328,29 +263,10 @@ void USettingsWidget::SetSettingComboboxMembers(const FSettingTag& ComboboxTag, // Set current value for a slider void USettingsWidget::SetSettingSlider(const FSettingTag& SliderTag, double InValue) { - if (!SliderTag.IsValid()) - { - return; - } - - FSettingsPicker* SettingsRowPtr = SettingsTableRowsInternal.Find(SliderTag.GetTagName()); - if (!SettingsRowPtr) - { - return; - } - static constexpr double MinValue = 0.0; static constexpr float MaxValue = 1.0; const double NewValue = FMath::Clamp(InValue, MinValue, MaxValue); - double& ChosenValueRef = SettingsRowPtr->Slider.ChosenValue; - if (ChosenValueRef == NewValue) - { - return; - } - - ChosenValueRef = NewValue; - SettingsRowPtr->Slider.OnSetterFloat.ExecuteIfBound(InValue); - UpdateSettings(SettingsRowPtr->PrimaryData.SettingsToUpdate); + SET_SETTING_VALUE(SliderTag, Slider, ChosenValue, NewValue, OnSetterFloat); // BP implementation SetSlider(SliderTag, InValue); @@ -456,136 +372,86 @@ void USettingsWidget::SetSettingCustomWidget(const FSettingTag& CustomWidgetTag, UpdateSettings(SettingsRowPtr->PrimaryData.SettingsToUpdate); } +/* --------------------------------------------------- + * Getters by setting types + * --------------------------------------------------- */ + +/** Retrieve a specific setting row using a given tag. + * @param Tag The tag used to find the setting row. + * @param DataMember The member that holds the desired value. */ +#define GET_SETTING_ROW(Tag, DataMember) \ + const FSettingsPicker& FoundRow = GetSettingRow(Tag); \ + if (!FoundRow.IsValid()) { return; } \ + const auto& Data = FoundRow.DataMember; + +/** Executes the common pattern of getting a value from a data structure. + * @param Tag The tag used to find the setting row. + * @param DataMember The member that holds the desired value. + * @param ValueType The type of value to retrieve. + * @param ValueExpression The expression to retrieve the value. + * @param GetterExpression The expression to retrieve the getter delegate. + * @param DefaultValue The default value to return if no value is found. */ +#define GET_SETTING_VALUE(Tag, DataMember, ValueType, ValueExpression, GetterExpression, DefaultValue) \ + { \ + const FSettingsPicker& FoundRow = GetSettingRow(Tag); \ + ValueType Value = DefaultValue; \ + if (FoundRow.IsValid()) \ + { \ + const auto& Data = FoundRow.DataMember; \ + Value = ValueExpression; \ + const auto& Getter = GetterExpression; \ + if (Getter.IsBound()) \ + { \ + Value = Getter.Execute(); \ + } \ + } \ + return Value; \ + } + // Returns is a checkbox toggled bool USettingsWidget::GetCheckboxValue(const FSettingTag& CheckboxTag) const { - if (!CheckboxTag.IsValid()) - { - return false; - } - - const FSettingsPicker& FoundRow = GetSettingRow(CheckboxTag); - bool Value = false; - if (FoundRow.IsValid()) - { - const FSettingsCheckbox& Data = FoundRow.Checkbox; - Value = Data.bIsSet; - - const USettingFunctionTemplate::FOnGetterBool& Getter = Data.OnGetterBool; - if (Getter.IsBound()) - { - Value = Getter.Execute(); - } - } - return Value; + GET_SETTING_VALUE(CheckboxTag, Checkbox, bool, Data.bIsSet, Data.OnGetterBool, false); } // Returns chosen member index of a combobox int32 USettingsWidget::GetComboboxIndex(const FSettingTag& ComboboxTag) const { - const FSettingsPicker& FoundRow = GetSettingRow(ComboboxTag); - int32 Value = false; - if (FoundRow.IsValid()) - { - const FSettingsCombobox& Data = FoundRow.Combobox; - Value = Data.ChosenMemberIndex; - - const USettingFunctionTemplate::FOnGetterInt& Getter = Data.OnGetterInt; - if (Getter.IsBound()) - { - Value = Getter.Execute(); - } - } - return Value; + GET_SETTING_VALUE(ComboboxTag, Combobox, int32, Data.ChosenMemberIndex, Data.OnGetterInt, 0); } // Get all members of a combobox void USettingsWidget::GetComboboxMembers(const FSettingTag& ComboboxTag, TArray& OutMembers) const { - const FSettingsPicker& FoundRow = GetSettingRow(ComboboxTag); - if (FoundRow.IsValid()) - { - const FSettingsCombobox& Data = FoundRow.Combobox; - OutMembers = Data.Members; - - const USettingFunctionTemplate::FOnGetMembers& Getter = Data.OnGetMembers; - if (Getter.IsBound()) - { - Getter.Execute(OutMembers); - } - } + GET_SETTING_ROW(ComboboxTag, Combobox) + OutMembers = Data.Members; + Data.OnGetMembers.ExecuteIfBound(OutMembers); } // Get current value of a slider [0...1] double USettingsWidget::GetSliderValue(const FSettingTag& SliderTag) const { - const FSettingsPicker& FoundRow = GetSettingRow(SliderTag); - double Value = 0.0; - if (FoundRow.IsValid()) - { - const FSettingsSlider& Data = FoundRow.Slider; - Value = Data.ChosenValue; - - const USettingFunctionTemplate::FOnGetterFloat& Getter = Data.OnGetterFloat; - if (Getter.IsBound()) - { - Value = Getter.Execute(); - } - } - return Value; + GET_SETTING_VALUE(SliderTag, Slider, double, Data.ChosenValue, Data.OnGetterFloat, 0.f); } // Get current text of a simple text widget void USettingsWidget::GetTextLineValue(const FSettingTag& TextLineTag, FText& OutText) const { - const FSettingsPicker& FoundRow = GetSettingRow(TextLineTag); - if (FoundRow.IsValid()) - { - OutText = FoundRow.PrimaryData.Caption; - - const USettingFunctionTemplate::FOnGetterText& Getter = FoundRow.TextLine.OnGetterText; - if (Getter.IsBound()) - { - Getter.Execute(OutText); - } - } + GET_SETTING_ROW(TextLineTag, PrimaryData) + OutText = Data.Caption; + FoundRow.TextLine.OnGetterText.ExecuteIfBound(OutText); } // Get current input name of the text input FName USettingsWidget::GetUserInputValue(const FSettingTag& UserInputTag) const { - const FSettingsPicker& FoundRow = GetSettingRow(UserInputTag); - FName Value = NAME_None; - if (FoundRow.IsValid()) - { - const FSettingsUserInput& Data = FoundRow.UserInput; - Value = Data.UserInput; - - const USettingFunctionTemplate::FOnGetterName& Getter = Data.OnGetterName; - if (Getter.IsBound()) - { - Value = Getter.Execute(); - } - } - return Value; + GET_SETTING_VALUE(UserInputTag, UserInput, FName, Data.UserInput, Data.OnGetterName, NAME_None); } // Get custom widget of the setting by specified tag USettingCustomWidget* USettingsWidget::GetCustomWidget(const FSettingTag& CustomWidgetTag) const { - const FSettingsPicker& FoundRow = GetSettingRow(CustomWidgetTag); - USettingCustomWidget* CustomWidget = nullptr; - if (FoundRow.IsValid()) - { - CustomWidget = Cast(FoundRow.PrimaryData.SettingSubWidget.Get()); - - const USettingFunctionTemplate::FOnGetterWidget& Getter = FoundRow.CustomWidget.OnGetterWidget; - if (Getter.IsBound()) - { - CustomWidget = Getter.Execute(); - } - } - return CustomWidget; + GET_SETTING_VALUE(CustomWidgetTag, CustomWidget, USettingCustomWidget*, Cast(FoundRow.PrimaryData.SettingSubWidget.Get()), Data.OnGetterWidget, nullptr); } // Get setting widget object by specified tag @@ -595,6 +461,10 @@ USettingSubWidget* USettingsWidget::GetSettingSubWidget(const FSettingTag& Setti return PrimaryData.IsValid() ? PrimaryData.SettingSubWidget.Get() : nullptr; } +/* --------------------------------------------------- + * Style + * --------------------------------------------------- */ + // Returns the size of the Settings widget on the screen FVector2D USettingsWidget::GetSettingsSize() const { @@ -649,7 +519,6 @@ FVector2D USettingsWidget::GetSubWidgetsSize(int32 SectionsBitmask) const return SubWidgetsHeight; } - // Returns the height of a setting scrollbox on the screen float USettingsWidget::GetScrollBoxHeight() const { @@ -679,7 +548,7 @@ float USettingsWidget::GetScrollBoxHeight() const } // Is blueprint-event called that returns the style brush by specified button state -FSlateBrush USettingsWidget::GetButtonBrush(ESettingsButtonState State) const +FSlateBrush USettingsWidget::GetButtonBrush(ESettingsButtonState State) { const USettingsDataAsset& SettingsDataAsset = USettingsDataAsset::Get(); const FMiscThemeData& MiscThemeData = SettingsDataAsset.GetMiscThemeData(); @@ -711,6 +580,10 @@ FSlateBrush USettingsWidget::GetButtonBrush(ESettingsButtonState State) const return SlateBrush; } +/* --------------------------------------------------- + * Protected functions + * --------------------------------------------------- */ + // Called after the underlying slate widget is constructed void USettingsWidget::NativeConstruct() { @@ -720,6 +593,8 @@ void USettingsWidget::NativeConstruct() { TryConstructSettings(); } + + TryFocusOnUI(); } // Is called right after the game was started and windows size is set to construct settings @@ -747,24 +622,24 @@ void USettingsWidget::ConstructSettings() // BP implementation to cache some data before creating subwidgets OnConstructSettings(); + FGameplayTagContainer AddedSettings; for (TTuple& RowIt : SettingsTableRowsInternal) { - AddSetting(RowIt.Value); + FSettingsPicker& SettingRef = RowIt.Value; + BindSetting(SettingRef); + AddSetting(SettingRef); + AddedSettings.AddTag(SettingRef.PrimaryData.Tag); } + UpdateSettings(AddedSettings); + UpdateScrollBoxesHeight(); } void USettingsWidget::UpdateSettingsTableRows() { - const USettingsDataTable* SettingsDataTable = USettingsDataAsset::Get().GetSettingsDataTable(); - if (!ensureMsgf(SettingsDataTable, TEXT("ASSERT: 'SettingsDataTable' is not valid"))) - { - return; - } - TMap SettingRows; - SettingsDataTable->GetSettingRows(/*Out*/SettingRows); + USettingsUtilsLibrary::GetAllSettingRows(/*Out*/SettingRows); if (!ensureMsgf(!SettingRows.IsEmpty(), TEXT("ASSERT: 'SettingRows' are empty"))) { return; @@ -797,26 +672,31 @@ void USettingsWidget::OnToggleSettings(bool bIsVisible) } // Bind and set static object delegate -void USettingsWidget::TryBindStaticContext(FSettingsPrimary& Primary) +bool USettingsWidget::TryBindOwner(FSettingsPrimary& Primary) { - UObject* FoundContextObj = nullptr; - if (UFunction* FunctionPtr = Primary.StaticContext.GetFunction()) + const UObject* FoundContextObj = nullptr; + const FSettingFunctionPicker& Owner = Primary.Owner; + if (Owner.IsValid()) { - FunctionPtr->ProcessEvent(FunctionPtr, /*Out*/&FoundContextObj); + Primary.OwnerFunc.BindUFunction(Owner.FunctionClass->GetDefaultObject(), Owner.FunctionName); + FoundContextObj = Primary.GetSettingOwner(this); } if (!FoundContextObj) { - return; - } + if (Owner.IsValid()) + { + // Static context function is set, but returning object is null, + // most likely such object is not initialized yet, + // defer binding to try to rebind it later + DeferredBindingsInternal.AddTag(Primary.Tag); + } - Primary.StaticContextObject = FoundContextObj; + return false; + } const UClass* ContextClass = FoundContextObj->GetClass(); - if (!ensureMsgf(ContextClass, TEXT("ASSERT: 'ContextClass' is not valid"))) - { - return; - } + checkf(ContextClass, TEXT("ERROR: [%i] %s:\n'ContextClass' is null!"), __LINE__, *FString(__FUNCTION__)); // Cache all functions that are contained in returned object for (TFieldIterator It(ContextClass, EFieldIteratorFlags::IncludeSuper); It; ++It) @@ -830,9 +710,11 @@ void USettingsWidget::TryBindStaticContext(FSettingsPrimary& Primary) const FName FunctionNameIt = FunctionIt->GetFName(); if (!FunctionNameIt.IsNone()) { - Primary.StaticContextFunctionList.Emplace(FunctionNameIt); + Primary.OwnerFunctionList.Emplace(FunctionNameIt); } } + + return true; } // Creates new widget based on specified setting class and sets it to specified primary data @@ -921,10 +803,14 @@ void USettingsWidget::OpenSettings() TryConstructSettings(); + TryRebindDeferredContexts(); + SetVisibility(ESlateVisibility::Visible); OnToggleSettings(true); + TryFocusOnUI(); + OnOpenSettings(); } @@ -960,226 +846,178 @@ void USettingsWidget::ToggleSettings() } } -// Add setting on UI. -void USettingsWidget::AddSetting(FSettingsPicker& Setting) +// Is called on opening to focus the widget on UI if allowed +void USettingsWidget::TryFocusOnUI() { - const FSettingsDataBase* ChosenData = Setting.GetChosenSettingsData(); - if (!ChosenData) + if (!USettingsDataAsset::Get().IsAutoFocusOnOpen()) { return; } - FSettingsPrimary& PrimaryData = Setting.PrimaryData; - TryBindStaticContext(PrimaryData); - - if (Setting.PrimaryData.bStartOnNextColumn) - { - StartNextColumn(); - } - - if (ChosenData == &Setting.Button) - { - AddSettingButton(PrimaryData, Setting.Button); - } - else if (ChosenData == &Setting.Checkbox) - { - AddSettingCheckbox(PrimaryData, Setting.Checkbox); - } - else if (ChosenData == &Setting.Combobox) - { - AddSettingCombobox(PrimaryData, Setting.Combobox); - } - else if (ChosenData == &Setting.Slider) - { - AddSettingSlider(PrimaryData, Setting.Slider); - } - else if (ChosenData == &Setting.TextLine) + APlayerController* PlayerController = GetOwningPlayer(); + if (!ensureMsgf(PlayerController, TEXT("ASSERT: [%i] %s:\n'PlayerController' is not valid!"), __LINE__, *FString(__FUNCTION__))) { - AddSettingTextLine(PrimaryData, Setting.TextLine); - } - else if (ChosenData == &Setting.UserInput) - { - AddSettingUserInput(PrimaryData, Setting.UserInput); - } - else if (ChosenData == &Setting.CustomWidget) - { - AddSettingCustomWidget(PrimaryData, Setting.CustomWidget); + return; } - UpdateSettings(FGameplayTagContainer(PrimaryData.Tag)); + static const FInputModeGameAndUI GameAndUI{}; + PlayerController->SetInputMode(GameAndUI); + PlayerController->SetShowMouseCursor(true); + PlayerController->bEnableClickEvents = true; + PlayerController->bEnableMouseOverEvents = true; } -// Add button on UI -void USettingsWidget::AddSettingButton(FSettingsPrimary& Primary, FSettingsButton& Data) +/* --------------------------------------------------- + * Bind by setting types + * --------------------------------------------------- */ + +// Bind setting to specified Get/Set delegates, so both methods will be called +bool USettingsWidget::BindSetting(FSettingsPicker& Setting) { - const TSubclassOf ButtonClass = USettingsDataAsset::Get().GetButtonClass(); - CreateSettingSubWidget(Primary, ButtonClass); + FSettingsDataBase* ChosenData = Setting.GetChosenSettingsData(); + if (!ChosenData) + { + return false; + } - if (UObject* StaticContextObject = Primary.StaticContextObject.Get()) + if (TryBindOwner(Setting.PrimaryData)) { - const FName SetterFunctionName = Primary.Setter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(SetterFunctionName)) - { - Data.OnButtonPressed.BindUFunction(StaticContextObject, SetterFunctionName); - } + ChosenData->BindSetting(*this, Setting.PrimaryData); + return true; } - AddButton(Primary, Data); + return false; } -// Add checkbox on UI -void USettingsWidget::AddSettingCheckbox(FSettingsPrimary& Primary, FSettingsCheckbox& Data) -{ - const TSubclassOf CheckboxClass = USettingsDataAsset::Get().GetCheckboxClass(); - CreateSettingSubWidget(Primary, CheckboxClass); +/** + * Macro to create and bind a UI widget. + * @param Primary Primary settings for the widget + * @param Data Data structure containing widget properties + * @param GetterFunction The getter function to bind + * @param SetterFunction The setter function to bind + * @param AdditionalFunctionCalls Any additional function calls needed for specific widgets + */ +#define BIND_SETTING(Primary, Data, GetterFunction, SetterFunction) \ + do \ + { \ + if (UObject* OwnerObject = Primary.GetSettingOwner(this)) \ + { \ + const FName GetterFunctionName = Primary.Getter.FunctionName; \ + if (Primary.OwnerFunctionList.Contains(GetterFunctionName)) \ + { \ + Data.GetterFunction.BindUFunction(OwnerObject, GetterFunctionName); \ + } \ + const FName SetterFunctionName = Primary.Setter.FunctionName; \ + if (Primary.OwnerFunctionList.Contains(SetterFunctionName)) \ + { \ + Data.SetterFunction.BindUFunction(OwnerObject, SetterFunctionName); \ + } \ + } \ + } while (0) - if (UObject* StaticContextObject = Primary.StaticContextObject.Get()) - { - const FName GetterFunctionName = Primary.Getter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(GetterFunctionName)) - { - Data.OnGetterBool.BindUFunction(StaticContextObject, GetterFunctionName); - } - - const FName SetterFunctionName = Primary.Setter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(SetterFunctionName)) - { - Data.OnSetterBool.BindUFunction(StaticContextObject, SetterFunctionName); - } - } +// Bind button to own Get/Set delegates +void USettingsWidget::BindButton(const FSettingsPrimary& Primary, FSettingsButton& Data) +{ + BIND_SETTING(Primary, Data, OnButtonPressed, OnButtonPressed); +} - AddCheckbox(Primary, Data); +// Bind checkbox to own Get/Set delegates +void USettingsWidget::BindCheckbox(const FSettingsPrimary& Primary, FSettingsCheckbox& Data) +{ + BIND_SETTING(Primary, Data, OnGetterBool, OnSetterBool); } -// Add combobox on UI -void USettingsWidget::AddSettingCombobox(FSettingsPrimary& Primary, FSettingsCombobox& Data) +// Bind combobox to own Get/Set delegates +void USettingsWidget::BindCombobox(const FSettingsPrimary& Primary, FSettingsCombobox& Data) { - const TSubclassOf ComboboxClass = USettingsDataAsset::Get().GetComboboxClass(); - CreateSettingSubWidget(Primary, ComboboxClass); + BIND_SETTING(Primary, Data, OnGetterInt, OnSetterInt); - if (UObject* StaticContextObject = Primary.StaticContextObject.Get()) + if (UObject* OwnerObject = Primary.GetSettingOwner(this)) { const FName GetMembersFunctionName = Data.GetMembers.FunctionName; - if (Primary.StaticContextFunctionList.Contains(GetMembersFunctionName)) + if (Primary.OwnerFunctionList.Contains(GetMembersFunctionName)) { - Data.OnGetMembers.BindUFunction(StaticContextObject, GetMembersFunctionName); + Data.OnGetMembers.BindUFunction(OwnerObject, GetMembersFunctionName); Data.OnGetMembers.ExecuteIfBound(Data.Members); } const FName SetMembersFunctionName = Data.SetMembers.FunctionName; - if (Primary.StaticContextFunctionList.Contains(SetMembersFunctionName)) + if (Primary.OwnerFunctionList.Contains(SetMembersFunctionName)) { - Data.OnSetMembers.BindUFunction(StaticContextObject, SetMembersFunctionName); + Data.OnSetMembers.BindUFunction(OwnerObject, SetMembersFunctionName); Data.OnSetMembers.ExecuteIfBound(Data.Members); } - - const FName GetterFunctionName = Primary.Getter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(GetterFunctionName)) - { - Data.OnGetterInt.BindUFunction(StaticContextObject, GetterFunctionName); - } - - const FName SetterFunctionName = Primary.Setter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(SetterFunctionName)) - { - Data.OnSetterInt.BindUFunction(StaticContextObject, SetterFunctionName); - } } - - AddCombobox(Primary, Data); } -// Add slider on UI -void USettingsWidget::AddSettingSlider(FSettingsPrimary& Primary, FSettingsSlider& Data) +// Bind slider to own Get/Set delegates +void USettingsWidget::BindSlider(const FSettingsPrimary& Primary, FSettingsSlider& Data) { - const TSubclassOf& SliderClass = USettingsDataAsset::Get().GetSliderClass(); - CreateSettingSubWidget(Primary, SliderClass); - - if (UObject* StaticContextObject = Primary.StaticContextObject.Get()) - { - const FName GetterFunctionName = Primary.Getter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(GetterFunctionName)) - { - Data.OnGetterFloat.BindUFunction(StaticContextObject, GetterFunctionName); - } + BIND_SETTING(Primary, Data, OnGetterFloat, OnSetterFloat); +} - const FName SetterFunctionName = Primary.Setter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(SetterFunctionName)) - { - Data.OnSetterFloat.BindUFunction(StaticContextObject, SetterFunctionName); - } - } +// Bind simple text to own Get/Set delegates +void USettingsWidget::BindTextLine(const FSettingsPrimary& Primary, FSettingsTextLine& Data) +{ + BIND_SETTING(Primary, Data, OnGetterText, OnSetterText); +} - AddSlider(Primary, Data); +// Bind text input to own Get/Set delegates +void USettingsWidget::BindUserInput(const FSettingsPrimary& Primary, FSettingsUserInput& Data) +{ + BIND_SETTING(Primary, Data, OnGetterName, OnSetterName); } -// Add simple text on UI -void USettingsWidget::AddSettingTextLine(FSettingsPrimary& Primary, FSettingsTextLine& Data) +// Bind custom widget to own Get/Set delegates +void USettingsWidget::BindCustomWidget(const FSettingsPrimary& Primary, FSettingsCustomWidget& Data) { - const TSubclassOf& TextLineClass = USettingsDataAsset::Get().GetTextLineClass(); - CreateSettingSubWidget(Primary, TextLineClass); + BIND_SETTING(Primary, Data, OnGetterWidget, OnSetterWidget); +} - if (UObject* StaticContextObject = Primary.StaticContextObject.Get()) +// Attempts to rebind those Settings that failed to bind their Getter/Setter functions on initial construct +void USettingsWidget::TryRebindDeferredContexts() +{ + if (DeferredBindingsInternal.IsEmpty()) { - const FName GetterFunctionName = Primary.Getter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(GetterFunctionName)) - { - Data.OnGetterText.BindUFunction(StaticContextObject, GetterFunctionName); - } + // Nothing to rebind, we are done + return; + } - const FName SetterFunctionName = Primary.Setter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(SetterFunctionName)) + FGameplayTagContainer ReboundSettings; + for (const FGameplayTag& TagIt : DeferredBindingsInternal) + { + FSettingsPicker* FoundRowPtr = TagIt.IsValid() ? SettingsTableRowsInternal.Find(TagIt.GetTagName()) : nullptr; + if (FoundRowPtr + && BindSetting(*FoundRowPtr)) { - Data.OnSetterText.BindUFunction(StaticContextObject, SetterFunctionName); + ReboundSettings.AddTagFast(TagIt); } } - AddTextLine(Primary, Data); + if (!ReboundSettings.IsEmpty()) + { + // Some settings were successfully rebound, remove them from the deferred list and update them + DeferredBindingsInternal.RemoveTags(ReboundSettings); + UpdateSettings(ReboundSettings); + } } -// Add text input on UI -void USettingsWidget::AddSettingUserInput(FSettingsPrimary& Primary, FSettingsUserInput& Data) +// Add setting on UI. +void USettingsWidget::AddSetting(FSettingsPicker& Setting) { - const TSubclassOf& UserInputClass = USettingsDataAsset::Get().GetUserInputClass(); - CreateSettingSubWidget(Primary, UserInputClass); - - if (UObject* StaticContextObject = Primary.StaticContextObject.Get()) + FSettingsDataBase* ChosenData = Setting.GetChosenSettingsData(); + if (!ChosenData) { - const FName GetterFunctionName = Primary.Getter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(GetterFunctionName)) - { - Data.OnGetterName.BindUFunction(StaticContextObject, GetterFunctionName); - } - - const FName SetterFunctionName = Primary.Setter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(SetterFunctionName)) - { - Data.OnSetterName.BindUFunction(StaticContextObject, SetterFunctionName); - } + return; } - AddUserInput(Primary, Data); -} - -// Add custom widget on UI -void USettingsWidget::AddSettingCustomWidget(FSettingsPrimary& Primary, FSettingsCustomWidget& Data) -{ - CreateSettingSubWidget(Primary, Data.CustomWidgetClass); + FSettingsPrimary& PrimaryData = Setting.PrimaryData; - if (UObject* StaticContextObject = Primary.StaticContextObject.Get()) + if (Setting.PrimaryData.bStartOnNextColumn) { - const FName GetterFunctionName = Primary.Getter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(GetterFunctionName)) - { - Data.OnGetterWidget.BindUFunction(StaticContextObject, GetterFunctionName); - } - - const FName SetterFunctionName = Primary.Setter.FunctionName; - if (Primary.StaticContextFunctionList.Contains(SetterFunctionName)) - { - Data.OnSetterWidget.BindUFunction(StaticContextObject, SetterFunctionName); - } + StartNextColumn(); } - AddCustomWidget(Primary, Data); + CreateSettingSubWidget(PrimaryData, ChosenData->GetSubWidgetClass()); + ChosenData->AddSetting(*this, PrimaryData); } diff --git a/Source/SettingsWidgetConstructor/Public/Data/SettingArchetypesData.h b/Source/SettingsWidgetConstructor/Public/Data/SettingArchetypesData.h index acbad7e..ed829ad 100644 --- a/Source/SettingsWidgetConstructor/Public/Data/SettingArchetypesData.h +++ b/Source/SettingsWidgetConstructor/Public/Data/SettingArchetypesData.h @@ -9,6 +9,12 @@ //--- #include "SettingArchetypesData.generated.h" +struct FSettingTag; +struct FSettingsPrimary; + +class USettingsWidget; +class USettingSubWidget; + /** * The base archetype of any setting. * Properties of child archetypes are used by Settings Picker select a setting. @@ -18,10 +24,36 @@ * @see FFunctionPicker */ USTRUCT(BlueprintType, meta = ( - FunctionContextTemplate = "/Script/SettingsWidgetConstructor.SettingFunctionTemplate::OnStaticContext__DelegateSignature")) + FunctionContextTemplate = "/Script/SettingsWidgetConstructor.SettingFunctionTemplate::OnGetterObject__DelegateSignature")) struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsDataBase { GENERATED_BODY() + + virtual ~FSettingsDataBase() = default; + + /********************************************************************************************* + * Base methods to override + * + * Since FSettingsPicker contains many types of settings, base methods are used to call them indirectly: + * FSettingsPicker& Setting == ... ; + * FSettingsDataBase* ChosenData = Setting.GetChosenSettingsData(); + * ChosenData-> ... ; // call any of the following methods + ********************************************************************************************* */ + + /** Base method to get the sub-widget class of this setting type. */ + virtual TSubclassOf GetSubWidgetClass() const PURE_VIRTUAL(FSettingsDataBase::GetSubWidgetClass, return nullptr;); + + /** Base method to get the setting value, where appropriate Getter of Settings Widget will be called. */ + virtual void GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const PURE_VIRTUAL(FSettingsDataBase::GetSettingValue,); + + /** Base method to set the setting value, where string value will be converted to the setting type and appropriate Setter of Settings Widget will be called. */ + virtual void SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) PURE_VIRTUAL(FSettingsDataBase::SetSettingValue,); + + /** Base method to Bind setting to specified in table Get/Set delegates, so both methods will be called. */ + virtual void BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) PURE_VIRTUAL(FSettingsDataBase::BindSetting,); + + /** Base method to add the setting to the Settings Widget, where appropriate Add method of Settings Widget will be called. */ + virtual void AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) PURE_VIRTUAL(FSettingsDataBase::AddSetting,); }; /** @@ -33,6 +65,10 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsButton : public FSettingsDataBase { GENERATED_BODY() + /********************************************************************************************* + * Data + ********************************************************************************************* */ + /** Either Header, Content, or Footer. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) EMyVerticalAlignment VerticalAlignment = EMyVerticalAlignment::Content; @@ -41,8 +77,31 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsButton : public FSettingsDataBase UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) TEnumAsByte HorizontalAlignment = HAlign_Fill; + /********************************************************************************************* + * Bindings + ********************************************************************************************* */ + /** Cached bound delegate, is executed on pressing this button. */ USettingFunctionTemplate::FOnButtonPressed OnButtonPressed; + + /********************************************************************************************* + * Overrides + ********************************************************************************************* */ + + /** Returns the sub-widget class of this setting type. */ + virtual TSubclassOf GetSubWidgetClass() const override; + + /** Calls the Get function of the Settings Widget of this setting type. */ + virtual void GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const override { OutResult = FString(TEXT("")); } + + /** Calls the Set function of the Settings Widget of this setting type. */ + virtual void SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) override; + + /** Calls the Bind function of the Settings Widget of this setting type. */ + virtual void BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) override; + + /** Calls the Add function of the Settings Widget of this setting type. */ + virtual void AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) override; }; /** @@ -55,14 +114,41 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsCheckbox : public FSettingsDataBas { GENERATED_BODY() + /********************************************************************************************* + * Data + ********************************************************************************************* */ + /** The cached current checkbox state. */ bool bIsSet = false; + /********************************************************************************************* + * Bindings + ********************************************************************************************* */ + /** The cached bound delegate, is executed to get the current checkbox state. */ USettingFunctionTemplate::FOnGetterBool OnGetterBool; /** The cached bound delegate, is executed to set the current checkbox state. */ USettingFunctionTemplate::FOnSetterBool OnSetterBool; + + /********************************************************************************************* + * Overrides + ********************************************************************************************* */ + + /** Returns the sub-widget class of this setting type. */ + virtual TSubclassOf GetSubWidgetClass() const override; + + /** Calls the Get function of the Settings Widget of this setting type. */ + virtual void GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const override; + + /** Calls the Set function of the Settings Widget of this setting type. */ + virtual void SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) override; + + /** Calls the Bind function of the Settings Widget of this setting type. */ + virtual void BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) override; + + /** Calls the Add function of the Settings Widget of this setting type. */ + virtual void AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) override; }; /** @@ -75,6 +161,10 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsCombobox : public FSettingsDataBas { GENERATED_BODY() + /********************************************************************************************* + * Data + ********************************************************************************************* */ + /** The Setter function to be called to set all combobox members. */ UPROPERTY(EditDefaultsOnly, meta = (FunctionSetterTemplate = "/Script/SettingsWidgetConstructor.SettingFunctionTemplate::OnSetMembers__DelegateSignature")) FSettingFunctionPicker SetMembers = FSettingFunctionPicker::EmptySettingFunction; @@ -94,6 +184,10 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsCombobox : public FSettingsDataBas /** The cached chosen member index. */ int32 ChosenMemberIndex = INDEX_NONE; + /********************************************************************************************* + * Bindings + ********************************************************************************************* */ + /** The cached bound delegate, is executed to get the chosen member index. */ USettingFunctionTemplate::FOnGetterInt OnGetterInt; @@ -105,6 +199,25 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsCombobox : public FSettingsDataBas /** The cached bound delegate, is executed to set all combobox members. */ USettingFunctionTemplate::FOnSetMembers OnSetMembers; + + /********************************************************************************************* + * Overrides + ********************************************************************************************* */ + + /** Returns the sub-widget class of this setting type. */ + virtual TSubclassOf GetSubWidgetClass() const override; + + /** Calls the Get function of the Settings Widget of this setting type. */ + virtual void GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const override; + + /** Calls the Set function of the Settings Widget of this setting type. */ + virtual void SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) override; + + /** Calls the Bind function of the Settings Widget of this setting type. */ + virtual void BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) override; + + /** Calls the Add function of the Settings Widget of this setting type. */ + virtual void AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) override; }; /** @@ -117,14 +230,41 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsSlider : public FSettingsDataBase { GENERATED_BODY() + /********************************************************************************************* + * Data + ********************************************************************************************* */ + /** Cached slider value (0..1). */ double ChosenValue = INDEX_NONE; + /********************************************************************************************* + * Bindings + ********************************************************************************************* */ + /** The cached bound delegate, is executed to get the current slider value. */ USettingFunctionTemplate::FOnGetterFloat OnGetterFloat; /** The cached bound delegate, is executed to set the current slider value. */ USettingFunctionTemplate::FOnSetterFloat OnSetterFloat; + + /********************************************************************************************* + * Overrides + ********************************************************************************************* */ + + /** Returns the sub-widget class of this setting type. */ + virtual TSubclassOf GetSubWidgetClass() const override; + + /** Calls the Get function of the Settings Widget of this setting type. */ + virtual void GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const override; + + /** Calls the Set function of the Settings Widget of this setting type. */ + virtual void SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) override; + + /** Calls the Bind function of the Settings Widget of this setting type. */ + virtual void BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) override; + + /** Calls the Add function of the Settings Widget of this setting type. */ + virtual void AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) override; }; /** @@ -137,6 +277,10 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsTextLine : public FSettingsDataBas { GENERATED_BODY() + /********************************************************************************************* + * Data + ********************************************************************************************* */ + /** Either Header, Content, or Footer. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) EMyVerticalAlignment VerticalAlignment = EMyVerticalAlignment::Content; @@ -145,11 +289,34 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsTextLine : public FSettingsDataBas UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) TEnumAsByte HorizontalAlignment = HAlign_Fill; + /********************************************************************************************* + * Bindings + ********************************************************************************************* */ + /** The cached bound delegate, is executed to set the text caption. */ USettingFunctionTemplate::FOnGetterText OnGetterText; /** The cached bound delegate, is executed to get the text caption. */ USettingFunctionTemplate::FOnSetterText OnSetterText; + + /********************************************************************************************* + * Overrides + ********************************************************************************************* */ + + /** Returns the sub-widget class of this setting type. */ + virtual TSubclassOf GetSubWidgetClass() const override; + + /** Calls the Get function of the Settings Widget of this setting type. */ + virtual void GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const override; + + /** Calls the Set function of the Settings Widget of this setting type. */ + virtual void SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) override; + + /** Calls the Bind function of the Settings Widget of this setting type. */ + virtual void BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) override; + + /** Calls the Add function of the Settings Widget of this setting type. */ + virtual void AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) override; }; /** @@ -162,6 +329,10 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsUserInput : public FSettingsDataBa { GENERATED_BODY() + /********************************************************************************************* + * Data + ********************************************************************************************* */ + /** The maximal length of the player input that is allowed to type. * Set 0 to do not limit number of characters. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) @@ -170,11 +341,34 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsUserInput : public FSettingsDataBa /** The cached text shown left of the input box. */ FName UserInput = NAME_None; + /********************************************************************************************* + * Bindings + ********************************************************************************************* */ + /** The cached bound delegate, is executed to set the input text. */ USettingFunctionTemplate::FOnGetterName OnGetterName; /** The cached bound delegate, is executed to get the input text. */ USettingFunctionTemplate::FOnSetterName OnSetterName; + + /********************************************************************************************* + * Overrides + ********************************************************************************************* */ + + /** Returns the sub-widget class of this setting type. */ + virtual TSubclassOf GetSubWidgetClass() const override; + + /** Calls the Get function of the Settings Widget of this setting type. */ + virtual void GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const override; + + /** Calls the Set function of the Settings Widget of this setting type. */ + virtual void SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) override; + + /** Calls the Bind function of the Settings Widget of this setting type. */ + virtual void BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) override; + + /** Calls the Add function of the Settings Widget of this setting type. */ + virtual void AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) override; }; /** @@ -187,13 +381,40 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsCustomWidget : public FSettingsDat { GENERATED_BODY() + /********************************************************************************************* + * Data + ********************************************************************************************* */ + /** Contains created custom widget of the setting. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (ShowOnlyInnerProperties)) TSubclassOf CustomWidgetClass = nullptr; + /********************************************************************************************* + * Bindings + ********************************************************************************************* */ + /** The cached bound delegate, is executed to set the custom widget. */ USettingFunctionTemplate::FOnGetterWidget OnGetterWidget; /** The cached bound delegate, is executed to get the custom widget. */ USettingFunctionTemplate::FOnSetterWidget OnSetterWidget; + + /********************************************************************************************* + * Overrides + ********************************************************************************************* */ + + /** Returns the sub-widget class of this setting type. */ + virtual TSubclassOf GetSubWidgetClass() const override; + + /** Calls the Get function of the Settings Widget of this setting type. */ + virtual void GetSettingValue(const USettingsWidget& SettingsWidget, const FSettingTag& Tag, FString& OutResult) const override; + + /** Calls the Set function of the Settings Widget of this setting type. */ + virtual void SetSettingValue(USettingsWidget& SettingsWidget, const FSettingTag& Tag, const FString& Value) override {} + + /** Calls the Bind function of the Settings Widget of this setting type. */ + virtual void BindSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& PrimaryData) override; + + /** Calls the Add function of the Settings Widget of this setting type. */ + virtual void AddSetting(USettingsWidget& SettingsWidget, const FSettingsPrimary& InOutPrimaryData) override; }; diff --git a/Source/SettingsWidgetConstructor/Public/Data/SettingsDataAsset.h b/Source/SettingsWidgetConstructor/Public/Data/SettingsDataAsset.h index 6ceedef..b2b1b31 100644 --- a/Source/SettingsWidgetConstructor/Public/Data/SettingsDataAsset.h +++ b/Source/SettingsWidgetConstructor/Public/Data/SettingsDataAsset.h @@ -4,11 +4,13 @@ #include "Engine/DeveloperSettings.h" //--- -#include "Data/SettingsDataTable.h" #include "Data/SettingsThemeData.h" //--- #include "SettingsDataAsset.generated.h" +class USettingsDataTable; +class UDataRegistry; + /** * Contains common settings data of the Constructor Widget plugin. * Is set up in 'Project Settings' -> "Plugins" -> "Settings Widget Constructor". @@ -18,6 +20,9 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsDataAsset : public UDeveloperSettin { GENERATED_BODY() + /********************************************************************************************* + * Getters + ********************************************************************************************* */ public: /** Returns Project Settings Data of the Settings Widget Constructor plugin. */ static const FORCEINLINE USettingsDataAsset& Get() { return *GetDefault(); } @@ -32,9 +37,9 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsDataAsset : public UDeveloperSettin /** Gets the category for the settings, some high level grouping like, Editor, Engine, Game...etc. */ virtual FName GetCategoryName() const override { return TEXT("Plugins"); } - /** Returns the data table. */ + /** Returns the project's main Settings Data Table, it has to be set manually. */ UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor") - FORCEINLINE USettingsDataTable* GetSettingsDataTable() const { return SettingsDataTableInternal.LoadSynchronous(); } + const USettingsDataTable* GetSettingsDataTable() const; /** Returns the sub-widget of Button settings. */ UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor") @@ -64,6 +69,10 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsDataAsset : public UDeveloperSettin UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor") FORCEINLINE bool IsAutoConstruct() const { return bAutoConstructInternal; } + /** Returns true if should automatically focus the Settings Widget on UI each time it is opened. */ + UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor") + FORCEINLINE bool IsAutoFocusOnOpen() const { return bAutoFocusOnOpenInternal; } + /** Returns the width and height of the settings widget in percentages of an entire screen. */ UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor") const FORCEINLINE FVector2D& GetSettingsPercentSize() const { return SettingsPercentSizeInternal; } @@ -108,10 +117,18 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsDataAsset : public UDeveloperSettin UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor") const FORCEINLINE FMiscThemeData& GetMiscThemeData() const { return MiscThemeDataInternal; } + /** Returns the Settings Data Registry asset, is automatically set by default to which 'Settings Data Table' is added by itself. */ + UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor") + const UDataRegistry* GetSettingsDataRegistry() const; + const TSoftObjectPtr& GetSettingsDataRegistrySoft() const { return SettingsDataRegistryInternal; } + + /********************************************************************************************* + * Protected properties + ********************************************************************************************* */ protected: - /** The data table with all settings, is config property. */ + /** The project's main Settings Data Table, is config property and has to be set manually. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Config, Category = "Settings Widget Constructor", meta = (BlueprintProtected, DisplayName = "Settings Data Table", ShowOnlyInnerProperties)) - TSoftObjectPtr SettingsDataTableInternal; + TSoftObjectPtr SettingsDataTableInternal; /** The sub-widget class of Button settings, is config property. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Config, Category = "Settings Widget Constructor", meta = (BlueprintProtected, DisplayName = "Button Class", ShowOnlyInnerProperties)) @@ -141,6 +158,10 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsDataAsset : public UDeveloperSettin UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Config, Category = "Settings Widget Constructor", meta = (BlueprintProtected, DisplayName = "Auto Construct", ShowOnlyInnerProperties)) bool bAutoConstructInternal; + /** If true, the Setting Widget will be automatically focused on UI each time it is opened. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Config, Category = "Settings Widget Constructor", meta = (BlueprintProtected, DisplayName = "Auto Focus On Open", ShowOnlyInnerProperties)) + bool bAutoFocusOnOpenInternal; + /** The width and height of the settings widget in percentages of an entire screen. Is clamped between 0 and 1, is config property. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Config, Category = "Settings Widget Constructor", meta = (BlueprintProtected, DisplayName = "Settings Percent Size", ClampMin = "0", ClampMax = "1", ShowOnlyInnerProperties)) FVector2D SettingsPercentSizeInternal; @@ -184,4 +205,23 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsDataAsset : public UDeveloperSettin /** The misc theme data, is config property. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Config, Category = "Settings Widget Constructor", meta = (BlueprintProtected, DisplayName = "Misc Theme Data")) FMiscThemeData MiscThemeDataInternal; + + /** The Settings Data Registry asset, is automatically set by default to which 'Settings Data Table' is added by itself. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Config, Category = "Settings Widget Constructor", meta = (BlueprintProtected, DisplayName = "Settings Data Registry", ShowOnlyInnerProperties)) + TSoftObjectPtr SettingsDataRegistryInternal; + + /********************************************************************************************* + * Internal + ********************************************************************************************* */ +protected: + /** Overrides post init to register Settings Data Table by default on startup. */ + virtual void PostInitProperties() override; + + /** Is called once Engine is initialized, so we can register Settings Data Table by default on startup. */ + void OnPostEngineInit(); + +#if WITH_EDITOR + /** Overrides property change events to handle picking Settings Data Table. */ + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif }; diff --git a/Source/SettingsWidgetConstructor/Public/Data/SettingsDataTable.h b/Source/SettingsWidgetConstructor/Public/Data/SettingsDataTable.h index a764eed..c7845a6 100644 --- a/Source/SettingsWidgetConstructor/Public/Data/SettingsDataTable.h +++ b/Source/SettingsWidgetConstructor/Public/Data/SettingsDataTable.h @@ -4,8 +4,6 @@ #include "SettingsWidgetConstructor/Private/MyDataTable/SWCMyDataTable.h" //--- -#include "Data/SettingsRow.h" -//--- #include "SettingsDataTable.generated.h" /** @@ -24,7 +22,7 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsDataTable : public USWCMyDataTable /** Returns the table rows. * @see USettingsDataAsset::SettingsDataTableInternal */ UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor") - void GetSettingRows(TMap& OutRows) const { GetRows(OutRows); } + void GetSettingRows(TMap& OutRows) const { GetRows(OutRows); } protected: #if WITH_EDITOR diff --git a/Source/SettingsWidgetConstructor/Public/Data/SettingsRow.h b/Source/SettingsWidgetConstructor/Public/Data/SettingsRow.h index 5325e27..e462fd2 100644 --- a/Source/SettingsWidgetConstructor/Public/Data/SettingsRow.h +++ b/Source/SettingsWidgetConstructor/Public/Data/SettingsRow.h @@ -18,7 +18,7 @@ * ╚═══╦FSettingsPicker * ╠═══╦FSettingsPrimary * ║ ╠════FSettingTag - * ║ ╚════FSettingFunctionPicker (StaticContext, Setter, Getter) + * ║ ╚════FSettingFunctionPicker (Owner, Setter, Getter) * ╚════FSettingsDataBase */ @@ -41,24 +41,24 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsPrimary /** The static function to obtain object to call Setters and Getters. * The FunctionContextTemplate meta will contain a name of one UFunctionPickerTemplate delegate. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (FunctionContextTemplate)) - FSettingFunctionPicker StaticContext = FSettingFunctionPicker::EmptySettingFunction; + FSettingFunctionPicker Owner = FSettingFunctionPicker::EmptySettingFunction; - /** The Setter function to be called to set the setting value for the Static Context object. + /** The Setter function to be called to set the setting value for the Owner object. * The FunctionSetterTemplate meta will contain a name of one UFunctionPickerTemplate delegate. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (FunctionSetterTemplate)) FSettingFunctionPicker Setter = FSettingFunctionPicker::EmptySettingFunction; - /** The Getter function to be called to get the setting value from the Static Context object. + /** The Getter function to be called to get the setting value from the Owner object. * The FunctionGetterTemplate meta will contain a name of one UFunctionPickerTemplate delegate. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (FunctionGetterTemplate)) FSettingFunctionPicker Getter = FSettingFunctionPicker::EmptySettingFunction; /** The setting name. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (MultiLine = "true")) FText Caption = TEXT_NONE; /** The description to be shown as tooltip. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (MultiLine = "true")) FText Tooltip = TEXT_NONE; /** The padding of this setting. */ @@ -77,14 +77,18 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsPrimary UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (Categories = "Settings")) FGameplayTagContainer SettingsToUpdate = FGameplayTagContainer::EmptyContainer; + /** If set, it will override own position to be shown after specified setting. + * Is useful when should be shown after setting that is created in another Settings Data Table. + * All the next settings after ShowNextToSettingOverride in the same table will be also shown next to it. + * @see 'Data Registr'y category of 'Settings Data Asset'. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FSettingTag ShowNextToSettingOverride = FSettingTag::EmptySettingTag; + /** Created widget of the chosen setting (button, checkbox, combobox, slider, text line, user input). */ TWeakObjectPtr SettingSubWidget = nullptr; - /** The cached object obtained from the Static Context function. */ - TWeakObjectPtr StaticContextObject = nullptr; - - /** Contains all cached functions of the Static Context object. */ - TArray StaticContextFunctionList; + /** Contains all cached functions of the Owner object. */ + TArray OwnerFunctionList; /** Returns true if is valid. */ FORCEINLINE bool IsValid() const { return Tag.IsValid(); } @@ -96,6 +100,17 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsPrimary /** Creates a hash value. * @param Other the other object to create a hash value for. */ friend SETTINGSWIDGETCONSTRUCTOR_API uint32 GetTypeHash(const FSettingsPrimary& Other); + + /********************************************************************************************* + * Setting Owner + * Is living object that contains specific Setter and Getter functions. + ********************************************************************************************* */ +public: + /** Is executed to obtain holding object. */ + UObject* GetSettingOwner(const UObject* WorldContext) const; + + /** The cached bound delegate that returns holding object. */ + USettingFunctionTemplate::FOnGetterObject OwnerFunc; }; /** @@ -152,7 +167,7 @@ struct SETTINGSWIDGETCONSTRUCTOR_API FSettingsPicker /** Returns the pointer to one of the chosen in-game type. * It searches the member property of this struct by a value of SettingsType. * @see FSettingsPicker::SettingsType */ - const FSettingsDataBase* GetChosenSettingsData() const; + FSettingsDataBase* GetChosenSettingsData() const; /** Returns true if row is valid. */ FORCEINLINE bool IsValid() const { return !(*this == Empty); } diff --git a/Source/SettingsWidgetConstructor/Public/MyUtilsLibraries/SettingsUtilsLibrary.h b/Source/SettingsWidgetConstructor/Public/MyUtilsLibraries/SettingsUtilsLibrary.h new file mode 100644 index 0000000..d4340bb --- /dev/null +++ b/Source/SettingsWidgetConstructor/Public/MyUtilsLibraries/SettingsUtilsLibrary.h @@ -0,0 +1,45 @@ +// Copyright (c) Yevhenii Selivanov + +#pragma once + +#include "Kismet/BlueprintFunctionLibrary.h" +//--- +#include "SettingsUtilsLibrary.generated.h" + +/** + * The common functions library for Settings Widget Constructor. + */ +UCLASS() +class SETTINGSWIDGETCONSTRUCTOR_API USettingsUtilsLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** Returns the Settings widget from viewport. */ + UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor", meta = (WorldContext = "WorldContextObject")) + static class USettingsWidget* GetSettingsWidget(const UObject* WorldContextObject); + + /** Returns the Game User Settings object. */ + UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor", meta = (WorldContext = "OptionalWorldContext")) + static class UGameUserSettings* GetGameUserSettings(const UObject* OptionalWorldContext = nullptr); + + /** Returns all Settings Rows from project's Settings Data Table and all other additional Data Tables from 'SettingsDataTable' Data Registry. + * Note: Is expensive function, prefer to cache the result once! */ + UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor") + static void GetAllSettingRows(TMap& OutSettingRows); + + /********************************************************************************************* + * Multiple Data Tables support + * Allows to register additional Settings Data Tables if needed + * https://docs.google.com/document/d/1IXnOqrgaXTClP-0cIo28a9f6GHc9N1BCgTNnMk-X9VQ + ********************************************************************************************* */ +public: + /** Registers given Settings Data Table to the Settings Data Registry. Alternatively Game Feature Action can be used to register it. + * It's automatically called on startup for the 'Settings Data Table' set in the Project Settings. + * Can be called manually to register additional Settings Data Tables. */ + UFUNCTION(BlueprintCallable, Category = "C++") + static void RegisterDataTable(const TSoftObjectPtr SettingsDataTable); + + /** Returns all Settings Data Tables added to 'SettingsDataTable' Data Registry including Project's one. */ + static void GetAllSettingDataTables(TSet& OutDataTables); +}; diff --git a/Source/SettingsWidgetConstructor/Public/UI/SettingSubWidget.h b/Source/SettingsWidgetConstructor/Public/UI/SettingSubWidget.h index 3c3e5ef..7002545 100644 --- a/Source/SettingsWidgetConstructor/Public/UI/SettingSubWidget.h +++ b/Source/SettingsWidgetConstructor/Public/UI/SettingSubWidget.h @@ -5,7 +5,6 @@ #include "Blueprint/UserWidget.h" //--- #include "Data/SettingsRow.h" -#include "Widgets/Input/SComboBox.h" //--- #include "SettingSubWidget.generated.h" @@ -149,6 +148,9 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingCheckbox : public USettingSubWidget void OnCheckStateChanged(bool bIsChecked); }; +template +class SComboBox; + /** * The sub-widget of Combobox settings. */ diff --git a/Source/SettingsWidgetConstructor/Public/UI/SettingsWidget.h b/Source/SettingsWidgetConstructor/Public/UI/SettingsWidget.h index bd3886a..592a017 100644 --- a/Source/SettingsWidgetConstructor/Public/UI/SettingsWidget.h +++ b/Source/SettingsWidgetConstructor/Public/UI/SettingsWidget.h @@ -17,11 +17,10 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget { GENERATED_BODY() -public: /* --------------------------------------------------- * Public properties * --------------------------------------------------- */ - +public: DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnToggledSettings, bool, bIsVisible); /** Is called to notify listeners the Settings widget is opened or closed. */ @@ -31,7 +30,7 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget /* --------------------------------------------------- * Public functions * --------------------------------------------------- */ - +public: /** Constructs settings if viewport is ready otherwise wait until viewport become initialized. */ UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor") void TryConstructSettings(); @@ -56,6 +55,10 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor") void ToggleSettings(); + /** Is called on opening to focus the widget on UI if allowed. */ + UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor") + void TryFocusOnUI(); + /** Returns true when this widget is fully constructed and ready to be used. */ UFUNCTION(BlueprintPure, Category = "C++") FORCEINLINE bool IsSettingsWidgetConstructed() const { return SettingsTableRowsInternal.Num() > 0; } @@ -100,7 +103,7 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget /* --------------------------------------------------- * Style * --------------------------------------------------- */ - +public: /** Returns the size of the Settings widget on the screen. */ UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor") FVector2D GetSettingsSize() const; @@ -116,12 +119,12 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget /** Is blueprint-event called that returns the style brush by specified button state. */ UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor|Style") - FSlateBrush GetButtonBrush(ESettingsButtonState State) const; + static FSlateBrush GetButtonBrush(ESettingsButtonState State); /* --------------------------------------------------- * Setters by setting types * --------------------------------------------------- */ - +public: /** * Set value to the option by tag. * Common function to set setting of an any type by the string. @@ -177,7 +180,7 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget /* --------------------------------------------------- * Getters by setting types * --------------------------------------------------- */ - +public: /** Returns is a checkbox toggled. */ UFUNCTION(BlueprintPure, Category = "Settings Widget Constructor|Getters", meta = (AutoCreateRefTerm = "CheckboxTag")) bool GetCheckboxValue(const FSettingTag& CheckboxTag) const; @@ -214,7 +217,7 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget /* --------------------------------------------------- * Protected properties * --------------------------------------------------- */ - +protected: /** Contains all settings. */ UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Transient, Category = "Settings Widget Constructor", meta = (BlueprintProtected, DisplayName = "Settings Table Rows")) TMap SettingsTableRowsInternal; @@ -231,10 +234,15 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Transient, Category = "Settings Widget Constructor", meta = (BlueprintProtected, DisplayName = "Setting ScrollBoxes")) TArray> SettingScrollBoxesInternal; + /** Contains all Setting tags that failed to bind their Getter/Setter functions on initial construct, so it's stored to be rebound later. + * @see USettingsWidget::TryRebindDeferredContexts */ + UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Transient, Category = "Settings Widget Constructor", meta = (BlueprintProtected, DisplayName = "DeferredBindings")) + FGameplayTagContainer DeferredBindingsInternal; + /* --------------------------------------------------- * Bound widget properties * --------------------------------------------------- */ - +protected: /** The section in the top margin of Settings, usually contains a title. */ UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Settings Widget Constructor|Widgets", meta = (BlueprintProtected, BindWidget)) TObjectPtr HeaderVerticalBox = nullptr; @@ -250,7 +258,7 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget /* --------------------------------------------------- * Protected functions * --------------------------------------------------- */ - +protected: /** Called after the underlying slate widget is constructed. * May be called multiple times due to adding and removing from the hierarchy. */ virtual void NativeConstruct() override; @@ -271,11 +279,6 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor", meta = (BlueprintProtected)) void OnToggleSettings(bool bIsVisible); - /** Bind and set static object delegate. - * @see FSettingsPrimary::OnStaticContext */ - UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor", meta = (BlueprintProtected)) - void TryBindStaticContext(UPARAM(ref)FSettingsPrimary& Primary); - /** Starts adding settings on the next column. */ UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Settings Widget Constructor", meta = (BlueprintProtected)) void StartNextColumn(); @@ -285,9 +288,56 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget void UpdateScrollBoxesHeight(); /* --------------------------------------------------- - * Add by setting types + * Bind by setting types * --------------------------------------------------- */ +public: + /** Bind setting to specified in table Get/Set delegates, so both methods will be called. */ + UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor", meta = (BlueprintProtected)) + bool BindSetting(UPARAM(ref)FSettingsPicker& Setting); + + /** Bind button to own Get/Set delegates. */ + UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor|Binders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) + void BindButton(const FSettingsPrimary& Primary, FSettingsButton& Data); + + /** Bind checkbox to own Get/Set delegates. */ + UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor|Binders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) + void BindCheckbox(const FSettingsPrimary& Primary, FSettingsCheckbox& Data); + + /** Bind combobox to own Get/Set delegates. */ + UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor|Binders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) + void BindCombobox(const FSettingsPrimary& Primary, FSettingsCombobox& Data); + + /** Bind slider to own Get/Set delegates. */ + UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor|Binders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) + void BindSlider(const FSettingsPrimary& Primary, FSettingsSlider& Data); + + /** Bind simple text to own Get/Set delegates. */ + UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor|Binders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) + void BindTextLine(const FSettingsPrimary& Primary, FSettingsTextLine& Data); + + /** Bind text input to own Get/Set delegates. */ + UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor|Binders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) + void BindUserInput(const FSettingsPrimary& Primary, FSettingsUserInput& Data); + + /** Bind custom widget to own Get/Set delegates. */ + UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor|Binders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) + void BindCustomWidget(const FSettingsPrimary& Primary, FSettingsCustomWidget& Data); + +protected: + /** Bind and set static object delegate. + * @see FSettingsPrimary::OwnerFunc */ + UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor", meta = (BlueprintProtected)) + bool TryBindOwner(UPARAM(ref)FSettingsPrimary& Primary); + /** Attempts to rebind those Settings that failed to bind their Getter/Setter functions on initial construct. + * @see USettingsWidget::DeferredBindingsInternal */ + UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor", meta = (BlueprintProtected)) + void TryRebindDeferredContexts(); + + /* --------------------------------------------------- + * Add by setting types + * --------------------------------------------------- */ +public: /** Add setting on UI. */ UFUNCTION(BlueprintCallable, Category = "Settings Widget Constructor|Adders", meta = (BlueprintProtected)) void AddSetting(UPARAM(ref)FSettingsPicker& Setting); @@ -295,59 +345,54 @@ class SETTINGSWIDGETCONSTRUCTOR_API USettingsWidget : public UUserWidget /** Add button on UI. */ UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Adders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) void AddButton(const FSettingsPrimary& Primary, const FSettingsButton& Data); - void AddSettingButton(FSettingsPrimary& Primary, FSettingsButton& Data); /** Add checkbox on UI. */ UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Adders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) void AddCheckbox(const FSettingsPrimary& Primary, const FSettingsCheckbox& Data); - void AddSettingCheckbox(FSettingsPrimary& Primary, FSettingsCheckbox& Data); /** Add combobox on UI. */ UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Adders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) void AddCombobox(const FSettingsPrimary& Primary, const FSettingsCombobox& Data); - void AddSettingCombobox(FSettingsPrimary& Primary, FSettingsCombobox& Data); /** Add slider on UI. */ UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Adders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) void AddSlider(const FSettingsPrimary& Primary, const FSettingsSlider& Data); - void AddSettingSlider(FSettingsPrimary& Primary, FSettingsSlider& Data); /** Add simple text on UI. */ UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Adders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) void AddTextLine(const FSettingsPrimary& Primary, const FSettingsTextLine& Data); - void AddSettingTextLine(FSettingsPrimary& Primary, FSettingsTextLine& Data); /** Add text input on UI. */ UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Adders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) void AddUserInput(const FSettingsPrimary& Primary, const FSettingsUserInput& Data); - void AddSettingUserInput(FSettingsPrimary& Primary, FSettingsUserInput& Data); /** Add custom widget on UI. */ UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Adders", meta = (BlueprintProtected, AutoCreateRefTerm = "Primary,Data")) void AddCustomWidget(const FSettingsPrimary& Primary, const FSettingsCustomWidget& Data); - void AddSettingCustomWidget(FSettingsPrimary& Primary, FSettingsCustomWidget& Data); /* --------------------------------------------------- * Blueprint implementable setters + * + * Don't call these functions directly, use 'Set Setting X' functions instead. * --------------------------------------------------- */ - +protected: /** Internal blueprint function to toggle checkbox. */ - UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Setters", meta = (BlueprintProtected, AutoCreateRefTerm = "CheckboxTag")) + UFUNCTION(BlueprintImplementableEvent, Category = "Settings Widget Constructor|Setters", meta = (BlueprintProtected, AutoCreateRefTerm = "CheckboxTag")) void SetCheckbox(const FSettingTag& CheckboxTag, bool InValue); /** Internal blueprint function to set chosen member index for a combobox. */ - UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Setters", meta = (BlueprintProtected, AutoCreateRefTerm = "ComboboxTag")) + UFUNCTION(BlueprintImplementableEvent, Category = "Settings Widget Constructor|Setters", meta = (BlueprintProtected, AutoCreateRefTerm = "ComboboxTag")) void SetComboboxIndex(const FSettingTag& ComboboxTag, int32 InValue); /** Internal blueprint function to set new members for a combobox. */ - UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Setters", meta = (BlueprintProtected, AutoCreateRefTerm = "ComboboxTag,InValue")) + UFUNCTION(BlueprintImplementableEvent, Category = "Settings Widget Constructor|Setters", meta = (BlueprintProtected, AutoCreateRefTerm = "ComboboxTag,InValue")) void SetComboboxMembers(const FSettingTag& ComboboxTag, const TArray& InValue); /** Internal blueprint function to set current value for a slider [0...1]. */ - UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Setters", meta = (BlueprintProtected, AutoCreateRefTerm = "SliderTag")) + UFUNCTION(BlueprintImplementableEvent, Category = "Settings Widget Constructor|Setters", meta = (BlueprintProtected, AutoCreateRefTerm = "SliderTag")) void SetSlider(const FSettingTag& SliderTag, float InValue); /** Internal blueprint function to set new text for an input box. */ - UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Settings Widget Constructor|Setters", meta = (BlueprintProtected, AutoCreateRefTerm = "UserInputTag")) + UFUNCTION(BlueprintImplementableEvent, Category = "Settings Widget Constructor|Setters", meta = (BlueprintProtected, AutoCreateRefTerm = "UserInputTag")) void SetUserInput(const FSettingTag& UserInputTag, FName InValue); }; diff --git a/Source/SettingsWidgetConstructor/SettingsWidgetConstructor.Build.cs b/Source/SettingsWidgetConstructor/SettingsWidgetConstructor.Build.cs index 76e54fa..4461a0a 100644 --- a/Source/SettingsWidgetConstructor/SettingsWidgetConstructor.Build.cs +++ b/Source/SettingsWidgetConstructor/SettingsWidgetConstructor.Build.cs @@ -8,7 +8,8 @@ public SettingsWidgetConstructor(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; CppStandard = CppStandardVersion.Latest; - + bEnableNonInlinedGenCppWarnings = true; + PublicDependencyModuleNames.AddRange(new[] { "Core" @@ -21,6 +22,7 @@ public SettingsWidgetConstructor(ReadOnlyTargetRules Target) : base(Target) PrivateDependencyModuleNames.AddRange(new[] { "CoreUObject", "Engine", "Slate", "SlateCore" // Core + , "DataRegistry" // Multiple Data Tables support } ); diff --git a/Source/SettingsWidgetConstructorEditor/Private/FunctionPickerType/FunctionPickerCustomization.cpp b/Source/SettingsWidgetConstructorEditor/Private/FunctionPickerType/FunctionPickerCustomization.cpp index 07c22a0..489a2f7 100644 --- a/Source/SettingsWidgetConstructorEditor/Private/FunctionPickerType/FunctionPickerCustomization.cpp +++ b/Source/SettingsWidgetConstructorEditor/Private/FunctionPickerType/FunctionPickerCustomization.cpp @@ -213,7 +213,7 @@ bool FFunctionPickerCustomization::IsSignatureCompatible(const UFunction* Functi return true; } - if (!A || !B) //one of properties is null + if (!A || !B) { return false; } @@ -230,14 +230,18 @@ bool FFunctionPickerCustomization::IsSignatureCompatible(const UFunction* Functi if (!A->SameType(B)) // A->GetClass() == B->GetClass() { - // That part is implemented: if is return param with the same flags + // --- That part is implemented: if is return param with the same flags // Will return true for any derived UObject - if (A->PropertyFlags & B->PropertyFlags & CPF_ReturnParm - && A->IsA(B->GetClass())) + + if (!(A->PropertyFlags & B->PropertyFlags & CPF_OutParm)) { - return true; + return false; + } + + if (!A->IsA() || !B->IsA()) + { + return false; } - return false; } return true; @@ -261,7 +265,12 @@ bool FFunctionPickerCustomization::IsSignatureCompatible(const UFunction* Functi // Check the flags as well const uint64 PropertyMash = PropA->PropertyFlags ^ PropB->PropertyFlags; - if (!ArePropertiesTheSame(PropA, PropB) || ((PropertyMash & ~IgnoreFlags) != 0)) + if ((PropertyMash & ~IgnoreFlags) != 0) + { + return false; + } + + if (!ArePropertiesTheSame(PropA, PropB)) { // Type mismatch between an argument of A and B return false; diff --git a/Source/SettingsWidgetConstructorEditor/Private/MyAssets/AssetTypeActions_MyDataTable.cpp b/Source/SettingsWidgetConstructorEditor/Private/MyAssets/AssetTypeActions_MyDataTable.cpp index 4fef3ce..7e9bc83 100644 --- a/Source/SettingsWidgetConstructorEditor/Private/MyAssets/AssetTypeActions_MyDataTable.cpp +++ b/Source/SettingsWidgetConstructorEditor/Private/MyAssets/AssetTypeActions_MyDataTable.cpp @@ -103,17 +103,16 @@ void FAssetTypeActions_MyDataTable::OpenAssetEditor(const TArray& InOb { FTextBuilder DataTablesListText; DataTablesListText.Indent(); - for (UDataTable* Table : InvalidDataTables) + for (const UDataTable* Table : InvalidDataTables) { const FTopLevelAssetPath ResolvedRowStructName = Table->GetRowStructPathName(); DataTablesListText.AppendLineFormat(LOCTEXT("DataTable_MissingRowStructListEntry", "* {0} (Row Structure: {1})"), FText::FromString(Table->GetName()), FText::FromString(ResolvedRowStructName.ToString())); } - FText Title = LOCTEXT("DataTable_MissingRowStructTitle", "Continue?"); const EAppReturnType::Type DlgResult = FMessageDialog::Open( EAppMsgType::YesNoCancel, FText::Format(LOCTEXT("DataTable_MissingRowStructMsg", "The following Data Tables are missing their row structure and will not be editable.\n\n{0}\n\nDo you want to open these data tables?"), DataTablesListText.ToText()), - &Title + LOCTEXT("DataTable_MissingRowStructTitle", "Continue?") ); switch (DlgResult) diff --git a/Source/SettingsWidgetConstructorEditor/Public/SettingsPickerCustomization.h b/Source/SettingsWidgetConstructorEditor/Public/SettingsPickerCustomization.h index 853b1dc..7f66181 100644 --- a/Source/SettingsWidgetConstructorEditor/Public/SettingsPickerCustomization.h +++ b/Source/SettingsWidgetConstructorEditor/Public/SettingsPickerCustomization.h @@ -65,7 +65,7 @@ class SETTINGSWIDGETCONSTRUCTOREDITOR_API FSettingsPickerCustomization : public /** Contains property to by meta. * MetaName: SettingsFunctionContextTemplate, SettingsFunctionSetterTemplate, SettingsFunctionGetterTemplate. - * SettingsFunctionProperty: FSettingsPicker::StaticContext, FSettingsPicker::Setter, FSettingsPicker::Getter, */ + * SettingsFunctionProperty: FSettingsPicker::Owner, FSettingsPicker::Setter, FSettingsPicker::Getter, */ TMap SettingsFunctionProperties; /** Pointer to the FSettingsDataBase struct. */ diff --git a/Source/SettingsWidgetConstructorEditor/SettingsWidgetConstructorEditor.Build.cs b/Source/SettingsWidgetConstructorEditor/SettingsWidgetConstructorEditor.Build.cs index 30076a4..6cee28e 100644 --- a/Source/SettingsWidgetConstructorEditor/SettingsWidgetConstructorEditor.Build.cs +++ b/Source/SettingsWidgetConstructorEditor/SettingsWidgetConstructorEditor.Build.cs @@ -8,7 +8,8 @@ public SettingsWidgetConstructorEditor(ReadOnlyTargetRules Target) : base(Target { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; CppStandard = CppStandardVersion.Latest; - + bEnableNonInlinedGenCppWarnings = true; + PublicDependencyModuleNames.AddRange(new[] { "Core"