diff --git a/Source/HarmonyLinkEditor/HarmonyLinkEditor.Build.cs b/Source/HarmonyLinkEditor/HarmonyLinkEditor.Build.cs new file mode 100644 index 0000000..47917c7 --- /dev/null +++ b/Source/HarmonyLinkEditor/HarmonyLinkEditor.Build.cs @@ -0,0 +1,29 @@ +using UnrealBuildTool; + +public class HarmonyLinkEditor : ModuleRules +{ + public HarmonyLinkEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "InputCore", + + "HarmonyLinkSettings", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + } + ); + } +} diff --git a/Source/HarmonyLinkEditor/Private/HarmonyLinkEditor.cpp b/Source/HarmonyLinkEditor/Private/HarmonyLinkEditor.cpp new file mode 100644 index 0000000..4047778 --- /dev/null +++ b/Source/HarmonyLinkEditor/Private/HarmonyLinkEditor.cpp @@ -0,0 +1,44 @@ +#include "HarmonyLinkEditor.h" + +#include "ISettingsModule.h" +#include "Objects/HarmonyLinkSettings.h" +#include "Objects/HarmonyLinkSettingsCustomization.h" + +#define LOCTEXT_NAMESPACE "FHarmonyLinkEditorModule" + +void FHarmonyLinkEditorModule::StartupModule() +{ + FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); + PropertyModule.RegisterCustomClassLayout("HarmonyLinkSettings", FOnGetDetailCustomizationInstance::CreateStatic(&FHarmonyLinkSettingsCustomization::MakeInstance)); + + ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings"); + + if (SettingsModule) + { + SettingsModule->RegisterSettings("Project", "Plugins", "HarmonyLink", + LOCTEXT("HarmonyLinkSettingsName", "HarmonyLink Settings"), + LOCTEXT("HarmonyLinkSettingsDescription", "Configure the HarmonyLink plugin settings."), + GetMutableDefault() + ); + } +} + +void FHarmonyLinkEditorModule::ShutdownModule() +{ + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); + PropertyModule.UnregisterCustomClassLayout("HarmonyLinkSettings"); + } + + ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings"); + + if (SettingsModule) + { + SettingsModule->UnregisterSettings("Project", "Plugins", "HarmonyLink"); + } +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FHarmonyLinkEditorModule, HarmonyLinkEditor) diff --git a/Source/HarmonyLinkEditor/Private/Objects/HarmonyLinkSettings.cpp b/Source/HarmonyLinkEditor/Private/Objects/HarmonyLinkSettings.cpp new file mode 100644 index 0000000..2e1261e --- /dev/null +++ b/Source/HarmonyLinkEditor/Private/Objects/HarmonyLinkSettings.cpp @@ -0,0 +1,4 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Objects/HarmonyLinkSettings.h" diff --git a/Source/HarmonyLinkEditor/Private/Objects/HarmonyLinkSettingsCustomization.cpp b/Source/HarmonyLinkEditor/Private/Objects/HarmonyLinkSettingsCustomization.cpp new file mode 100644 index 0000000..b31ab50 --- /dev/null +++ b/Source/HarmonyLinkEditor/Private/Objects/HarmonyLinkSettingsCustomization.cpp @@ -0,0 +1,265 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Objects/HarmonyLinkSettingsCustomization.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "Objects/HarmonyLinkGraphics.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SSpinBox.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SButton.h" + +TArray> FHarmonyLinkSettingsCustomization::ComboBoxOptions; + +TSharedRef FHarmonyLinkSettingsCustomization::MakeInstance() +{ + return MakeShareable(new FHarmonyLinkSettingsCustomization); +} + +void FHarmonyLinkSettingsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + CustomizeSettings(DetailBuilder); +} + +void FHarmonyLinkSettingsCustomization::CustomizeSettings(IDetailLayoutBuilder& DetailBuilder) +{ + IDetailCategoryBuilder& Category = DetailBuilder.EditCategory("HarmonyLink Settings"); + + // Access the HarmonyLinkGraphics singleton instance + UHarmonyLinkGraphics* HarmonyLinkGraphics = UHarmonyLinkGraphics::GetSettings(); + if (!HarmonyLinkGraphics) + { + return; + } + + // Display Automatic Profile Switch + Category.AddCustomRow(FText::FromString("Automatic Profile Switch")) + .NameContent() + [ + SNew(STextBlock) + .Text(FText::FromString("Automatic Profile Switch")) + ] + .ValueContent() + [ + SNew(SCheckBox) + .IsChecked_Lambda([HarmonyLinkGraphics]() -> ECheckBoxState + { + return HarmonyLinkGraphics->GetAutomaticSwitching() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([HarmonyLinkGraphics](ECheckBoxState NewState) + { + HarmonyLinkGraphics->SetAutomaticSwitching(NewState == ECheckBoxState::Checked); + }) + ]; + + // Iterate over profiles and display their settings + const TMap& Profiles = HarmonyLinkGraphics->GetProfiles(); + for (const auto& ProfilePair : Profiles) + { + const EProfile ProfileEnum = ProfilePair.Key; + const FSettingsProfile& Profile = ProfilePair.Value; + + Category.AddCustomRow(FText::FromString(Profile.SectionName.ToString())) + .NameContent() + [ + SNew(STextBlock) + .Text(FText::FromString(Profile.SectionName.ToString())) + ]; + + // Create a box to contain the table + TSharedRef Table = SNew(SVerticalBox); + + // Add table header + Table->AddSlot() + .AutoHeight() + .Padding(2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Name")) + ] + + SHorizontalBox::Slot() + .Padding(2) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Value")) + ] + + SHorizontalBox::Slot() + .Padding(2) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Type")) + ] + ]; + + // Add rows for each setting in the profile + for (const auto& SettingPair : Profile.Settings) + { + const FName& SettingName = SettingPair.Key; + const FHLConfigValue& SettingValue = SettingPair.Value; + + TSharedPtr NameTextBox; + TSharedPtr ValueWidget; + TSharedPtr>> TypeComboBox; + + // Create the value widget based on the type + switch (SettingValue.GetType()) + { + case EConfigValueType::Int: + ValueWidget = SNew(SSpinBox) + .Value(SettingValue.GetValue()) + .OnValueCommitted_Lambda([HarmonyLinkGraphics, ProfileEnum, SettingName](int32 NewValue, ETextCommit::Type CommitType) + { + HarmonyLinkGraphics->SetSetting(ProfileEnum, SettingName, FHLConfigValue(NewValue)); + }); + break; + case EConfigValueType::Float: + ValueWidget = SNew(SSpinBox) + .Value(SettingValue.GetValue()) + .OnValueCommitted_Lambda([HarmonyLinkGraphics, ProfileEnum, SettingName](float NewValue, ETextCommit::Type CommitType) + { + HarmonyLinkGraphics->SetSetting(ProfileEnum, SettingName, FHLConfigValue(NewValue)); + }); + break; + case EConfigValueType::Bool: + ValueWidget = SNew(SCheckBox) + .IsChecked(SettingValue.GetValue() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged_Lambda([HarmonyLinkGraphics, ProfileEnum, SettingName](ECheckBoxState NewState) + { + bool NewValue = NewState == ECheckBoxState::Checked; + HarmonyLinkGraphics->SetSetting(ProfileEnum, SettingName, FHLConfigValue(NewValue)); + }); + break; + case EConfigValueType::String: + ValueWidget = SNew(SEditableTextBox) + .Text(FText::FromString(SettingValue.GetValue())) + .OnTextCommitted_Lambda([HarmonyLinkGraphics, ProfileEnum, SettingName](const FText& NewText, ETextCommit::Type CommitType) + { + FString NewValue = NewText.ToString(); + HarmonyLinkGraphics->SetSetting(ProfileEnum, SettingName, FHLConfigValue(NewValue)); + }); + break; + } + + // Add the row for the setting + Table->AddSlot() + .AutoHeight() + .Padding(2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2) + .VAlign(VAlign_Center) + [ + SAssignNew(NameTextBox, SEditableTextBox) + .Text(FText::FromString(SettingName.ToString())) + .OnTextCommitted_Lambda([HarmonyLinkGraphics, ProfileEnum, SettingName](const FText& NewText, ETextCommit::Type CommitType) + { + FString NewName = NewText.ToString(); + HarmonyLinkGraphics->RenameSetting(ProfileEnum, SettingName, FName(*NewName)); + }) + ] + + SHorizontalBox::Slot() + .Padding(2) + .VAlign(VAlign_Center) + [ + ValueWidget.ToSharedRef() + ] + + SHorizontalBox::Slot() + .Padding(2) + .VAlign(VAlign_Center) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(GetComboBoxOptions()) // Use static member + .OnGenerateWidget_Lambda([](TSharedPtr InOption) + { + return SNew(STextBlock).Text(FText::FromString(*InOption)); + }) + .OnSelectionChanged_Lambda([this, HarmonyLinkGraphics, ProfileEnum, SettingName](TSharedPtr NewSelection, ESelectInfo::Type SelectInfo) + { + if (NewSelection.IsValid()) + { + EConfigValueType NewType = this->GetConfigValueTypeFromString(*NewSelection); + HarmonyLinkGraphics->ChangeSettingType(ProfileEnum, SettingName, NewType); + } + }) + .Content() + [ + SNew(STextBlock) + .Text(FText::FromString(GetConfigValueTypeAsString(SettingValue.GetType()))) + ] + ] + ]; + } + + Category.AddCustomRow(FText::FromString(Profile.SectionName.ToString() + " Table")) + [ + Table + ]; + } +} + +TArray>* FHarmonyLinkSettingsCustomization::GetComboBoxOptions() +{ + if (ComboBoxOptions.Num() == 0) // Initialize only once + { + ComboBoxOptions.Add(MakeShareable(new FString("int"))); + ComboBoxOptions.Add(MakeShareable(new FString("float"))); + ComboBoxOptions.Add(MakeShareable(new FString("bool"))); + ComboBoxOptions.Add(MakeShareable(new FString("string"))); + } + return &ComboBoxOptions; +} + +EConfigValueType FHarmonyLinkSettingsCustomization::GetConfigValueTypeFromString(const FString& TypeString) +{ + if (TypeString == "int") + { + return EConfigValueType::Int; + } + + if (TypeString == "float") + { + return EConfigValueType::Float; + } + + if (TypeString == "bool") + { + return EConfigValueType::Bool; + } + + if (TypeString == "string") + { + return EConfigValueType::String; + } + + return EConfigValueType::None; +} + +FString FHarmonyLinkSettingsCustomization::GetConfigValueTypeAsString(EConfigValueType Type) +{ + switch (Type) + { + case EConfigValueType::Int: + return "int"; + case EConfigValueType::Float: + return "float"; + case EConfigValueType::Bool: + return "bool"; + case EConfigValueType::String: + return "string"; + default: + return "none"; + } +} diff --git a/Source/HarmonyLinkEditor/Public/HarmonyLinkEditor.h b/Source/HarmonyLinkEditor/Public/HarmonyLinkEditor.h new file mode 100644 index 0000000..b195f1c --- /dev/null +++ b/Source/HarmonyLinkEditor/Public/HarmonyLinkEditor.h @@ -0,0 +1,11 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FHarmonyLinkEditorModule : public IModuleInterface +{ +public: + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Source/HarmonyLinkEditor/Public/Objects/HarmonyLinkSettings.h b/Source/HarmonyLinkEditor/Public/Objects/HarmonyLinkSettings.h new file mode 100644 index 0000000..6ea589f --- /dev/null +++ b/Source/HarmonyLinkEditor/Public/Objects/HarmonyLinkSettings.h @@ -0,0 +1,18 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "HarmonyLinkSettings.generated.h" + +/** + * + */ +UCLASS(MinimalAPI) +class UHarmonyLinkSettings : public UObject +{ + GENERATED_BODY() + + +}; diff --git a/Source/HarmonyLinkEditor/Public/Objects/HarmonyLinkSettingsCustomization.h b/Source/HarmonyLinkEditor/Public/Objects/HarmonyLinkSettingsCustomization.h new file mode 100644 index 0000000..2369b0c --- /dev/null +++ b/Source/HarmonyLinkEditor/Public/Objects/HarmonyLinkSettingsCustomization.h @@ -0,0 +1,27 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "IDetailCustomization.h" +#include "Structs/HLConfigValue.h" + +/** + * + */ +class FHarmonyLinkSettingsCustomization : public IDetailCustomization +{ +public: + static TSharedRef MakeInstance(); + + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + +private: + void CustomizeSettings(IDetailLayoutBuilder& DetailBuilder); + + static TArray>* GetComboBoxOptions(); + static EConfigValueType GetConfigValueTypeFromString(const FString& TypeString); + static FString GetConfigValueTypeAsString(EConfigValueType Type); + + static TArray> ComboBoxOptions; // Static member for options +}; diff --git a/Source/HarmonyLinkSettings/Private/Objects/HarmonyLinkGraphics.cpp b/Source/HarmonyLinkSettings/Private/Objects/HarmonyLinkGraphics.cpp index fafa023..54cc87f 100644 --- a/Source/HarmonyLinkSettings/Private/Objects/HarmonyLinkGraphics.cpp +++ b/Source/HarmonyLinkSettings/Private/Objects/HarmonyLinkGraphics.cpp @@ -64,6 +64,11 @@ UHarmonyLinkGraphics::~UHarmonyLinkGraphics() FWorldDelegates::OnPreWorldFinishDestroy.RemoveAll(this); } +const TMap& UHarmonyLinkGraphics::GetProfiles() +{ + return _Profiles; +} + void UHarmonyLinkGraphics::LoadConfig(const bool bForceReload) { UE_LOG(LogHarmonyLinkSettings, Verbose, TEXT("LoadConfig called.")); @@ -259,6 +264,53 @@ UHarmonyLinkGraphics* UHarmonyLinkGraphics::GetSettings() return _INSTANCE; } +void UHarmonyLinkGraphics::RenameSetting(EProfile Profile, FName OldName, FName NewName) +{ + if (FSettingsProfile* SettingsProfile = _Profiles.Find(Profile)) + { + if (SettingsProfile->Settings.Contains(OldName)) + { + FHLConfigValue Value = SettingsProfile->Settings[OldName]; + SettingsProfile->Settings.Remove(OldName); + SettingsProfile->Settings.Add(NewName, Value); + SaveConfig(); + } + } +} + +void UHarmonyLinkGraphics::ChangeSettingType(EProfile Profile, FName SettingName, EConfigValueType NewType) +{ + if (FSettingsProfile* SettingsProfile = _Profiles.Find(Profile)) + { + if (SettingsProfile->Settings.Contains(SettingName)) + { + FHLConfigValue OldValue = SettingsProfile->Settings[SettingName]; + SettingsProfile->Settings.Remove(SettingName); + + FHLConfigValue NewValue; + switch (NewType) + { + case EConfigValueType::Int: + NewValue = FHLConfigValue(0); // Default to conversion from float + break; + case EConfigValueType::Float: + NewValue = FHLConfigValue(0.f); // Default to conversion from int + break; + case EConfigValueType::Bool: + NewValue = FHLConfigValue(false); // Default to conversion from int + break; + case EConfigValueType::String: + NewValue = FHLConfigValue(FString()); // Default to string + break; + default: + return; + } + SettingsProfile->Settings.Add(SettingName, NewValue); + SaveConfig(); + } + } +} + FSettingsProfile UHarmonyLinkGraphics::GetSettingProfile(const EProfile Profile) { UE_LOG(LogHarmonyLinkSettings, Verbose, TEXT("GetSettingProfile called.")); diff --git a/Source/HarmonyLinkSettings/Public/Objects/HarmonyLinkGraphics.h b/Source/HarmonyLinkSettings/Public/Objects/HarmonyLinkGraphics.h index 782fb98..6c24ae7 100644 --- a/Source/HarmonyLinkSettings/Public/Objects/HarmonyLinkGraphics.h +++ b/Source/HarmonyLinkSettings/Public/Objects/HarmonyLinkGraphics.h @@ -195,6 +195,11 @@ public: */ static void DestroySettings(); +protected: + static const TMap& GetProfiles(); + void RenameSetting(EProfile Profile, FName OldName, FName NewName); + void ChangeSettingType(EProfile Profile, FName SettingName, EConfigValueType NewType); + private: /** * @brief Initializes the UHarmonyLinkGraphics settings. diff --git a/Source/HarmonyLinkSettings/Public/Structs/HLConfigValue.h b/Source/HarmonyLinkSettings/Public/Structs/HLConfigValue.h index 2ce69f3..ad7c522 100644 --- a/Source/HarmonyLinkSettings/Public/Structs/HLConfigValue.h +++ b/Source/HarmonyLinkSettings/Public/Structs/HLConfigValue.h @@ -8,6 +8,7 @@ UENUM(BlueprintType) enum class EConfigValueType : uint8 { + None, Int, Float, Bool, @@ -42,11 +43,13 @@ private: public: FHLConfigValue() : Type(EConfigValueType::String), IntValue(0), FloatValue(0.0f), BoolValue(false), StringValue(TEXT("")) {} - FHLConfigValue(int32 Value) : Type(EConfigValueType::Int), IntValue(Value), FloatValue(0.0f), BoolValue(false), StringValue(TEXT("")) {} + FHLConfigValue(const EConfigValueType Type) : Type(Type), IntValue(0), FloatValue(0.0f), BoolValue(false), StringValue(TEXT("")) {} - FHLConfigValue(float Value) : Type(EConfigValueType::Float), IntValue(0), FloatValue(Value), BoolValue(false), StringValue(TEXT("")) {} + FHLConfigValue(const int32 Value) : Type(EConfigValueType::Int), IntValue(Value), FloatValue(0.0f), BoolValue(false), StringValue(TEXT("")) {} - FHLConfigValue(bool Value) : Type(EConfigValueType::Bool), IntValue(0), FloatValue(0.0f), BoolValue(Value), StringValue(TEXT("")) {} + FHLConfigValue(const float Value) : Type(EConfigValueType::Float), IntValue(0), FloatValue(Value), BoolValue(false), StringValue(TEXT("")) {} + + FHLConfigValue(const bool Value) : Type(EConfigValueType::Bool), IntValue(0), FloatValue(0.0f), BoolValue(Value), StringValue(TEXT("")) {} FHLConfigValue(const FString& Value) : Type(EConfigValueType::String), IntValue(0), FloatValue(0.0f), BoolValue(false), StringValue(Value) {}