diff --git a/.gitignore b/.gitignore index 6582eaf..6638888 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,8 @@ Plugins/*/Intermediate/* # Cache files for the editor to use DerivedDataCache/* + +# Unreal Plugin + +/Binaries/** +/Intermediate/** diff --git a/CommonTime.uplugin b/CommonTime.uplugin new file mode 100644 index 0000000..2c23db8 --- /dev/null +++ b/CommonTime.uplugin @@ -0,0 +1,25 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "CommonTime", + "Description": "", + "Category": "Editor", + "CreatedBy": "MrRobinOfficial", + "CreatedByURL": "https://github.com/MrRobinOfficial", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "EnabledByDefault": true, + "CanContainContent": true, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "CommonTime", + "Type": "Editor", + "LoadingPhase": "PostEngineInit" + } + ] +} \ No newline at end of file diff --git a/Resources/Icon128.png b/Resources/Icon128.png new file mode 100644 index 0000000..1231d4a Binary files /dev/null and b/Resources/Icon128.png differ diff --git a/Source/CommonTime/CommonTime.Build.cs b/Source/CommonTime/CommonTime.Build.cs new file mode 100644 index 0000000..440f934 --- /dev/null +++ b/Source/CommonTime/CommonTime.Build.cs @@ -0,0 +1,30 @@ +// Copyright 2023 MrRobin. All Rights Reserved. + +using UnrealBuildTool; + +public class CommonTime : ModuleRules +{ + public CommonTime(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] + { + "Core", + }); + + PublicDependencyModuleNames.AddRange(new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "DeveloperSettings", + "UnrealEd", + "PropertyEditor", + "ClassViewer", + "InputCore", + "BlueprintGraph", + }); + } +} diff --git a/Source/CommonTime/Private/CommonTimeModule.cpp b/Source/CommonTime/Private/CommonTimeModule.cpp new file mode 100644 index 0000000..f461531 --- /dev/null +++ b/Source/CommonTime/Private/CommonTimeModule.cpp @@ -0,0 +1,46 @@ +// Copyright 2023 MrRobin. All Rights Reserved. + +#include "CommonTimeModule.h" + +#include "Modules/ModuleManager.h" + +#include "DetailCustomizations/MyDateTimeDetailCustomization.h" +#include "DetailCustomizations/MyTimespanDetailCustomization.h" + +DEFINE_LOG_CATEGORY(LogCommonTime); + +class FCommonTimeModule : public IModuleInterface +{ +public: + void StartupModule() override + { + FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked(TEXT("PropertyEditor")); + + PropertyModule.RegisterCustomPropertyTypeLayout( + TEXT("Timespan"), + FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FMyTimespanDetailCustomization::MakeInstance) + ); + + PropertyModule.RegisterCustomPropertyTypeLayout( + TEXT("DateTime"), + FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FMyDateTimeDetailCustomization::MakeInstance) + ); + + PropertyModule.NotifyCustomizationModuleChanged(); + } + + void ShutdownModule() override + { + if (FModuleManager::Get().IsModuleLoaded(TEXT("PropertyEditor"))) + { + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + + PropertyModule.UnregisterCustomPropertyTypeLayout(TEXT("Timespan")); + PropertyModule.UnregisterCustomPropertyTypeLayout(TEXT("DateTime")); + + PropertyModule.NotifyCustomizationModuleChanged(); + } + } +}; + +IMPLEMENT_MODULE(FCommonTimeModule, CommonTime); \ No newline at end of file diff --git a/Source/CommonTime/Private/DetailCustomizations/MyDateTimeDetailCustomization.cpp b/Source/CommonTime/Private/DetailCustomizations/MyDateTimeDetailCustomization.cpp new file mode 100644 index 0000000..3b15b90 --- /dev/null +++ b/Source/CommonTime/Private/DetailCustomizations/MyDateTimeDetailCustomization.cpp @@ -0,0 +1,559 @@ +// Copyright 2023 MrRobin. All Rights Reserved. + +#include "DetailCustomizations/MyDateTimeDetailCustomization.h" + +#include "Containers/Array.h" +#include "Containers/UnrealString.h" +#include "DetailWidgetRow.h" +#include "Fonts/SlateFontInfo.h" +#include "HAL/PlatformCrt.h" +#include "Internationalization/Internationalization.h" +#include "Misc/Attribute.h" +#include "Misc/DateTime.h" +#include "PropertyHandle.h" +#include "Styling/AppStyle.h" +#include "Styling/ISlateStyle.h" +#include "UObject/NameTypes.h" +#include "UObject/UnrealType.h" +#include "Widgets/DeclarativeSyntaxSupport.h" + +#include "DetailLayoutBuilder.h" + +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Input/SVectorInputBox.h" + +#define LOCTEXT_NAMESPACE "MyDateTimeDetailCustomization" + +/* IDetailCustomization interface + *****************************************************************************/ + +void FMyDateTimeDetailCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + /* do nothing */ +} + +void FMyDateTimeDetailCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + PropertyHandle = StructPropertyHandle; + + const FSlateColor FgColor = FSlateColor(FColor::Yellow); + const FSlateColor BgColor = FSlateColor(FColor::Blue); + + HeaderRow + .NameContent() + [ + StructPropertyHandle->CreatePropertyNameWidget() + ] + .ValueContent() + .MaxDesiredWidth(0.f) + .MinDesiredWidth(125.0f * 3.0f) + //.HAlign(HAlign_Fill) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .HAlign(HAlign_Fill) + .AutoHeight() + .Padding(0.0f, 1.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 2.0f) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .MinValue(1) + .MaxValue(9999) + .MinSliderValue(1) + .MaxSliderValue(9999) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(this, &FMyDateTimeDetailCustomization::OnGetValue, 3) + .OnValueChanged(this, &FMyDateTimeDetailCustomization::OnValueChanged, 3) + .OnValueCommitted(this, &FMyDateTimeDetailCustomization::OnValueCommitted, 3) + .OnBeginSliderMovement(this, &FMyDateTimeDetailCustomization::OnBeginSliderMovement) + .OnEndSliderMovement(this, &FMyDateTimeDetailCustomization::OnEndSliderMovement) + .ToolTipText(this, &FMyDateTimeDetailCustomization::GetValueAsText, 3) + //.ToolTipText(MakeAttributeLambda([Value, TooltipText] + //{ + // if (Value.Get().IsSet()) + // { + // return FText::Format(TooltipText, Value.Get().GetValue()); + // } + // return NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values"); + //})) + //.UndeterminedString(NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values")) + //.ContextMenuExtender(OnContextMenuExtenderComponent) + //.TypeInterface(InArgs._TypeInterface) + //.MinValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinVector)) + //.MaxValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxVector)) + //.MinSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinSliderVector)) + //.MaxSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxSliderVector)) + .LinearDeltaSensitivity(1) + /*.Delta(InArgs._SpinDelta)*/ + /*.OnBeginSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnBeginSliderMovement, OnComponentBeginSliderMovement))*/ + /*.OnEndSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnEndSliderMovement, OnComponentEndSliderMovement))*/ + /*.DisplayToggle(InArgs._DisplayToggle) + .TogglePadding(InArgs._TogglePadding) + .ToggleChecked(ToggleChecked) + .OnToggleChanged(OnToggleChanged)*/ + ] + + SHorizontalBox::Slot() + .Padding(3.75f, 2.0f) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .MinValue(1) + .MaxValue(12) + .MinSliderValue(1) + .MaxSliderValue(12) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(this, &FMyDateTimeDetailCustomization::OnGetValue, 4) + .OnValueChanged(this, &FMyDateTimeDetailCustomization::OnValueChanged, 4) + .OnValueCommitted(this, &FMyDateTimeDetailCustomization::OnValueCommitted, 4) + .OnBeginSliderMovement(this, &FMyDateTimeDetailCustomization::OnBeginSliderMovement) + .OnEndSliderMovement(this, &FMyDateTimeDetailCustomization::OnEndSliderMovement) + .ToolTipText(this, &FMyDateTimeDetailCustomization::GetValueAsText, 4) + //.OnValueChanged(CreatePerComponentChanged(ComponentIndex, OnComponentChanged, InArgs._ConstrainVector)) + //.OnValueCommitted(CreatePerComponentCommitted(ComponentIndex, OnComponentCommitted, InArgs._ConstrainVector)) + //.ToolTipText(MakeAttributeLambda([Value, TooltipText] + //{ + // if (Value.Get().IsSet()) + // { + // return FText::Format(TooltipText, Value.Get().GetValue()); + // } + // return NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values"); + //})) + //.UndeterminedString(NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values")) + //.ContextMenuExtender(OnContextMenuExtenderComponent) + //.TypeInterface(InArgs._TypeInterface) + //.MinValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinVector)) + //.MaxValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxVector)) + //.MinSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinSliderVector)) + //.MaxSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxSliderVector)) + .LinearDeltaSensitivity(1) + /*.Delta(InArgs._SpinDelta)*/ + /*.OnBeginSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnBeginSliderMovement, OnComponentBeginSliderMovement))*/ + /*.OnEndSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnEndSliderMovement, OnComponentEndSliderMovement))*/ + /*.DisplayToggle(InArgs._DisplayToggle) + .TogglePadding(InArgs._TogglePadding) + .ToggleChecked(ToggleChecked) + .OnToggleChanged(OnToggleChanged)*/ + ] + + SHorizontalBox::Slot() + .Padding(0.0f, 2.0f) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .MinValue(1) + .MaxValue(31) + .MinSliderValue(1) + .MaxSliderValue(31) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(this, &FMyDateTimeDetailCustomization::OnGetValue, 5) + .OnValueChanged(this, &FMyDateTimeDetailCustomization::OnValueChanged, 5) + .OnValueCommitted(this, &FMyDateTimeDetailCustomization::OnValueCommitted, 5) + .OnBeginSliderMovement(this, &FMyDateTimeDetailCustomization::OnBeginSliderMovement) + .OnEndSliderMovement(this, &FMyDateTimeDetailCustomization::OnEndSliderMovement) + .ToolTipText(this, &FMyDateTimeDetailCustomization::GetValueAsText, 5) + //.OnValueChanged(CreatePerComponentChanged(ComponentIndex, OnComponentChanged, InArgs._ConstrainVector)) + //.OnValueCommitted(CreatePerComponentCommitted(ComponentIndex, OnComponentCommitted, InArgs._ConstrainVector)) + //.ToolTipText(MakeAttributeLambda([Value, TooltipText] + //{ + // if (Value.Get().IsSet()) + // { + // return FText::Format(TooltipText, Value.Get().GetValue()); + // } + // return NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values"); + //})) + //.UndeterminedString(NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values")) + //.ContextMenuExtender(OnContextMenuExtenderComponent) + //.TypeInterface(InArgs._TypeInterface) + //.MinValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinVector)) + //.MaxValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxVector)) + //.MinSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinSliderVector)) + //.MaxSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxSliderVector)) + .LinearDeltaSensitivity(1) + /*.Delta(InArgs._SpinDelta)*/ + /*.OnBeginSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnBeginSliderMovement, OnComponentBeginSliderMovement))*/ + /*.OnEndSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnEndSliderMovement, OnComponentEndSliderMovement))*/ + /*.DisplayToggle(InArgs._DisplayToggle) + .TogglePadding(InArgs._TogglePadding) + .ToggleChecked(ToggleChecked) + .OnToggleChanged(OnToggleChanged)*/ + ] + ] + + SVerticalBox::Slot() + .HAlign(HAlign_Fill) + .AutoHeight() + .Padding(0.0f, 1.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 2.0f) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .MinValue(0) + .MaxValue(23) + .MinSliderValue(0) + .MaxSliderValue(23) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(this, &FMyDateTimeDetailCustomization::OnGetValue, 0) + .OnValueChanged(this, &FMyDateTimeDetailCustomization::OnValueChanged, 0) + .OnValueCommitted(this, &FMyDateTimeDetailCustomization::OnValueCommitted, 0) + .OnBeginSliderMovement(this, &FMyDateTimeDetailCustomization::OnBeginSliderMovement) + .OnEndSliderMovement(this, &FMyDateTimeDetailCustomization::OnEndSliderMovement) + .ToolTipText(this, &FMyDateTimeDetailCustomization::GetValueAsText, 0) + //.OnValueChanged(CreatePerComponentChanged(ComponentIndex, OnComponentChanged, InArgs._ConstrainVector)) + //.OnValueCommitted(CreatePerComponentCommitted(ComponentIndex, OnComponentCommitted, InArgs._ConstrainVector)) + //.ToolTipText(MakeAttributeLambda([Value, TooltipText] + //{ + // if (Value.Get().IsSet()) + // { + // return FText::Format(TooltipText, Value.Get().GetValue()); + // } + // return NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values"); + //})) + //.UndeterminedString(NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values")) + //.ContextMenuExtender(OnContextMenuExtenderComponent) + //.TypeInterface(InArgs._TypeInterface) + //.MinValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinVector)) + //.MaxValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxVector)) + //.MinSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinSliderVector)) + //.MaxSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxSliderVector)) + .LinearDeltaSensitivity(1) + /*.Delta(InArgs._SpinDelta)*/ + /*.OnBeginSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnBeginSliderMovement, OnComponentBeginSliderMovement))*/ + /*.OnEndSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnEndSliderMovement, OnComponentEndSliderMovement))*/ + /*.DisplayToggle(InArgs._DisplayToggle) + .TogglePadding(InArgs._TogglePadding) + .ToggleChecked(ToggleChecked) + .OnToggleChanged(OnToggleChanged)*/ + ] + + SHorizontalBox::Slot() + .Padding(3.75f, 2.0f) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .MinValue(0) + .MaxValue(59) + .MinSliderValue(0) + .MaxSliderValue(59) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(this, &FMyDateTimeDetailCustomization::OnGetValue, 1) + .OnValueChanged(this, &FMyDateTimeDetailCustomization::OnValueChanged, 1) + .OnValueCommitted(this, &FMyDateTimeDetailCustomization::OnValueCommitted, 1) + .OnBeginSliderMovement(this, &FMyDateTimeDetailCustomization::OnBeginSliderMovement) + .OnEndSliderMovement(this, &FMyDateTimeDetailCustomization::OnEndSliderMovement) + .ToolTipText(this, &FMyDateTimeDetailCustomization::GetValueAsText, 1) + //.OnValueChanged(CreatePerComponentChanged(ComponentIndex, OnComponentChanged, InArgs._ConstrainVector)) + //.OnValueCommitted(CreatePerComponentCommitted(ComponentIndex, OnComponentCommitted, InArgs._ConstrainVector)) + //.ToolTipText(MakeAttributeLambda([Value, TooltipText] + //{ + // if (Value.Get().IsSet()) + // { + // return FText::Format(TooltipText, Value.Get().GetValue()); + // } + // return NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values"); + //})) + //.UndeterminedString(NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values")) + //.ContextMenuExtender(OnContextMenuExtenderComponent) + //.TypeInterface(InArgs._TypeInterface) + //.MinValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinVector)) + //.MaxValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxVector)) + //.MinSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinSliderVector)) + //.MaxSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxSliderVector)) + .LinearDeltaSensitivity(1) + /*.Delta(InArgs._SpinDelta)*/ + /*.OnBeginSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnBeginSliderMovement, OnComponentBeginSliderMovement))*/ + /*.OnEndSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnEndSliderMovement, OnComponentEndSliderMovement))*/ + /*.DisplayToggle(InArgs._DisplayToggle) + .TogglePadding(InArgs._TogglePadding) + .ToggleChecked(ToggleChecked) + .OnToggleChanged(OnToggleChanged)*/ + ] + + SHorizontalBox::Slot() + .Padding(0.0f, 2.0f) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .MinValue(0) + .MaxValue(59) + .MinSliderValue(0) + .MaxSliderValue(59) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(this, &FMyDateTimeDetailCustomization::OnGetValue, 2) + .OnValueChanged(this, &FMyDateTimeDetailCustomization::OnValueChanged, 2) + .OnValueCommitted(this, &FMyDateTimeDetailCustomization::OnValueCommitted, 2) + .OnBeginSliderMovement(this, &FMyDateTimeDetailCustomization::OnBeginSliderMovement) + .OnEndSliderMovement(this, &FMyDateTimeDetailCustomization::OnEndSliderMovement) + .ToolTipText(this, &FMyDateTimeDetailCustomization::GetValueAsText, 2) + //.OnValueChanged(CreatePerComponentChanged(ComponentIndex, OnComponentChanged, InArgs._ConstrainVector)) + //.OnValueCommitted(CreatePerComponentCommitted(ComponentIndex, OnComponentCommitted, InArgs._ConstrainVector)) + //.ToolTipText(MakeAttributeLambda([Value, TooltipText] + //{ + // if (Value.Get().IsSet()) + // { + // return FText::Format(TooltipText, Value.Get().GetValue()); + // } + // return NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values"); + //})) + //.UndeterminedString(NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values")) + //.ContextMenuExtender(OnContextMenuExtenderComponent) + //.TypeInterface(InArgs._TypeInterface) + //.MinValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinVector)) + //.MaxValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxVector)) + //.MinSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinSliderVector)) + //.MaxSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxSliderVector)) + .LinearDeltaSensitivity(1) + /*.Delta(InArgs._SpinDelta)*/ + /*.OnBeginSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnBeginSliderMovement, OnComponentBeginSliderMovement))*/ + /*.OnEndSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnEndSliderMovement, OnComponentEndSliderMovement))*/ + /*.DisplayToggle(InArgs._DisplayToggle) + .TogglePadding(InArgs._TogglePadding) + .ToggleChecked(ToggleChecked) + .OnToggleChanged(OnToggleChanged)*/ + ] + ] + ]; +} + + +/* FMyDateTimeDetailCustomization callbacks + *****************************************************************************/ + +TOptional FMyDateTimeDetailCustomization::OnGetValue(int32 Index) const +{ + TArray RawData; + PropertyHandle->AccessRawData(RawData); + + if (RawData.Num() != 1) + return TOptional(); + + if (RawData[0] == nullptr) + return TOptional(); + + auto* DateTime = ((FDateTime*)RawData[0]); + + switch (Index) + { + case 0: + return TOptional(DateTime->GetHour()); + + case 1: + return TOptional(DateTime->GetMinute()); + + case 2: + return TOptional(DateTime->GetSecond()); + + case 3: + return TOptional(DateTime->GetYear()); + + case 4: + return TOptional(DateTime->GetMonth()); + + case 5: + return TOptional(DateTime->GetDay()); + + default: + return TOptional(); + } +} + +FText FMyDateTimeDetailCustomization::GetValueAsText(int32 Index) const +{ + const TOptional& Value = OnGetValue(Index); + + FFormatNamedArguments Args; + + switch (Index) + { + case 0: + Args.Add(TEXT("Key"), INVTEXT("Hour")); + Args.Add(TEXT("Desc"), INVTEXT("A hour between 0-23")); + break; + + case 1: + Args.Add(TEXT("Key"), INVTEXT("Minute")); + Args.Add(TEXT("Desc"), INVTEXT("A minute between 0-59")); + break; + + case 2: + Args.Add(TEXT("Key"), INVTEXT("Second")); + Args.Add(TEXT("Desc"), INVTEXT("A second between 0-59")); + break; + + case 3: + Args.Add(TEXT("Key"), INVTEXT("Year")); + Args.Add(TEXT("Desc"), INVTEXT("A year between 1-9999")); + break; + + case 4: + Args.Add(TEXT("Key"), INVTEXT("Month")); + Args.Add(TEXT("Desc"), INVTEXT("A month between 1-12")); + break; + + case 5: + Args.Add(TEXT("Key"), INVTEXT("Day")); + Args.Add(TEXT("Desc"), INVTEXT("A day between 1-31")); + break; + } + + + if (Value.IsSet() == true) + { + Args.Add(TEXT("Value"), Value.GetValue()); + return FText::Format(LOCTEXT("DateTime", "{Key}: {Value} - {Desc}"), Args); + } + + return FText::Format(LOCTEXT("DateTime", "{Key} - {Desc}"), Args); +} + +void FMyDateTimeDetailCustomization::OnBeginSliderMovement() +{ + bIsUsingSlider = true; + + GEditor->BeginTransaction( + FText::Format( + NSLOCTEXT("MyDateTimeDetailCustomization", "SetDateTimeProperty", "Edit {0}"), + PropertyHandle->GetPropertyDisplayName()) + ); +} + +void FMyDateTimeDetailCustomization::OnEndSliderMovement(int32 NewValue) +{ + bIsUsingSlider = false; + + GEditor->EndTransaction(); +} + +void FMyDateTimeDetailCustomization::OnValueCommitted(int32 NewValue, ETextCommit::Type CommitType, int32 Index) +{ + TArray RawData; + + PropertyHandle->AccessRawData(RawData); + PropertyHandle->NotifyPreChange(); + + for (auto RawDataInstance : RawData) + { + auto* DateTime = (FDateTime*)RawDataInstance; + + int32 Year = DateTime->GetYear(); + int32 Month = DateTime->GetMonth(); + int32 Day = DateTime->GetDay(); + + int32 Hour = DateTime->GetHour(); + int32 Minute = DateTime->GetMinute(); + int32 Second = DateTime->GetSecond(); + + int32 MaxDays = FDateTime::DaysInMonth(Year, Month); + + switch (Index) + { + case 0: + *DateTime = FDateTime(Year, Month, Day > MaxDays ? MaxDays : Day, NewValue, Minute, Second); + break; + + case 1: + *DateTime = FDateTime(Year, Month, Day > MaxDays ? MaxDays : Day, Hour, NewValue, Second); + break; + + case 2: + *DateTime = FDateTime(Year, Month, Day > MaxDays ? MaxDays : Day, Hour, Minute, NewValue); + break; + + case 3: + { + MaxDays = FDateTime::DaysInMonth(NewValue, Month); + *DateTime = FDateTime(NewValue, Month, Day > MaxDays ? MaxDays : Day, Hour, Minute, Second); + } + break; + + case 4: + { + MaxDays = FDateTime::DaysInMonth(Year, NewValue); + *DateTime = FDateTime(Year, NewValue, Day > MaxDays ? MaxDays : Day, Hour, Minute, Second); + } + break; + + case 5: + *DateTime = FDateTime(Year, Month, NewValue > MaxDays ? MaxDays : NewValue, Hour, Minute, Second); + break; + + default: + break; + } + } + + PropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + PropertyHandle->NotifyFinishedChangingProperties(); +} + +void FMyDateTimeDetailCustomization::OnValueChanged(int32 NewValue, int32 Index) +{ + if (!bIsUsingSlider) + return; + + TArray RawData; + + PropertyHandle->AccessRawData(RawData); + PropertyHandle->NotifyPreChange(); + + for (auto RawDataInstance : RawData) + { + auto* DateTime = (FDateTime*)RawDataInstance; + + int32 Year = DateTime->GetYear(); + int32 Month = DateTime->GetMonth(); + int32 Day = DateTime->GetDay(); + + int32 Hour = DateTime->GetHour(); + int32 Minute = DateTime->GetMinute(); + int32 Second = DateTime->GetSecond(); + + int32 MaxDays = FDateTime::DaysInMonth(Year, Month); + + switch (Index) + { + case 0: + *DateTime = FDateTime(Year, Month, Day > MaxDays ? MaxDays : Day, NewValue, Minute, Second); + break; + + case 1: + *DateTime = FDateTime(Year, Month, Day > MaxDays ? MaxDays : Day, Hour, NewValue, Second); + break; + + case 2: + *DateTime = FDateTime(Year, Month, Day > MaxDays ? MaxDays : Day, Hour, Minute, NewValue); + break; + + case 3: + { + MaxDays = FDateTime::DaysInMonth(NewValue, Month); + *DateTime = FDateTime(NewValue, Month, Day > MaxDays ? MaxDays : Day, Hour, Minute, Second); + } + break; + + case 4: + { + MaxDays = FDateTime::DaysInMonth(Year, NewValue); + *DateTime = FDateTime(Year, NewValue, Day > MaxDays ? MaxDays : Day, Hour, Minute, Second); + } + break; + + case 5: + *DateTime = FDateTime(Year, Month, NewValue > MaxDays ? MaxDays : NewValue, Hour, Minute, Second); + break; + + default: + break; + } + } + + PropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + PropertyHandle->NotifyFinishedChangingProperties(); + + //EPropertyValueSetFlags::Type Flags = EPropertyValueSetFlags::InteractiveChange; + //PropertyHandle->SetValue(NewValue, Flags); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/CommonTime/Private/DetailCustomizations/MyTimespanDetailCustomization.cpp b/Source/CommonTime/Private/DetailCustomizations/MyTimespanDetailCustomization.cpp new file mode 100644 index 0000000..253b0ab --- /dev/null +++ b/Source/CommonTime/Private/DetailCustomizations/MyTimespanDetailCustomization.cpp @@ -0,0 +1,325 @@ +// Copyright 2023 MrRobin. All Rights Reserved. + +#include "DetailCustomizations/MyTimespanDetailCustomization.h" + +#include "Containers/Array.h" +#include "Containers/UnrealString.h" +#include "DetailWidgetRow.h" +#include "Fonts/SlateFontInfo.h" +#include "HAL/PlatformCrt.h" +#include "Internationalization/Internationalization.h" +#include "Misc/Attribute.h" +#include "Misc/Timespan.h" +#include "PropertyHandle.h" +#include "Styling/AppStyle.h" +#include "Styling/ISlateStyle.h" +#include "UObject/NameTypes.h" +#include "UObject/UnrealType.h" +#include "Widgets/DeclarativeSyntaxSupport.h" + +#include "DetailLayoutBuilder.h" +#include "Widgets/Input/SNumericEntryBox.h" + +#define LOCTEXT_NAMESPACE "MyTimespanDetailCustomization" + +/* IDetailCustomization interface + *****************************************************************************/ + +void FMyTimespanDetailCustomization::CustomizeChildren( + TSharedRef StructPropertyHandle, + class IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + /* do nothing */ +} + +void FMyTimespanDetailCustomization::CustomizeHeader( + TSharedRef StructPropertyHandle, + class FDetailWidgetRow& HeaderRow, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + PropertyHandle = StructPropertyHandle; + + HeaderRow + .NameContent() + [ + StructPropertyHandle->CreatePropertyNameWidget() + ] + .ValueContent() + .MaxDesiredWidth(0.f) + .MinDesiredWidth(125.0f * 3.0f) + //.HAlign(HAlign_Fill) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 2.0f) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .MinValue(0) + .MaxValue(23) + .MinSliderValue(0) + .MaxSliderValue(23) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(this, &FMyTimespanDetailCustomization::OnGetValue, 0) + .OnValueChanged(this, &FMyTimespanDetailCustomization::OnValueChanged, 0) + .OnValueCommitted(this, &FMyTimespanDetailCustomization::OnValueCommitted, 0) + .OnBeginSliderMovement(this, &FMyTimespanDetailCustomization::OnBeginSliderMovement) + .OnEndSliderMovement(this, &FMyTimespanDetailCustomization::OnEndSliderMovement) + //.OnValueChanged(CreatePerComponentChanged(ComponentIndex, OnComponentChanged, InArgs._ConstrainVector)) + //.OnValueCommitted(CreatePerComponentCommitted(ComponentIndex, OnComponentCommitted, InArgs._ConstrainVector)) + //.ToolTipText(MakeAttributeLambda([Value, TooltipText] + //{ + // if (Value.Get().IsSet()) + // { + // return FText::Format(TooltipText, Value.Get().GetValue()); + // } + // return NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values"); + //})) + //.UndeterminedString(NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values")) + //.ContextMenuExtender(OnContextMenuExtenderComponent) + //.TypeInterface(InArgs._TypeInterface) + //.MinValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinVector)) + //.MaxValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxVector)) + //.MinSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinSliderVector)) + //.MaxSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxSliderVector)) + .LinearDeltaSensitivity(1) + /*.Delta(InArgs._SpinDelta)*/ + /*.OnBeginSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnBeginSliderMovement, OnComponentBeginSliderMovement))*/ + /*.OnEndSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnEndSliderMovement, OnComponentEndSliderMovement))*/ + /*.DisplayToggle(InArgs._DisplayToggle) + .TogglePadding(InArgs._TogglePadding) + .ToggleChecked(ToggleChecked) + .OnToggleChanged(OnToggleChanged)*/ + + //SNew(SNumericEntryBox) + // .AllowSpin(true) + // .Font(IDetailLayoutBuilder::GetDetailFont()) + // .Value(this, &FMyTimespanDetailCustomization::OnGetValue, 0) + // .MinValue(0) + // .MaxValue(23) + // .MinSliderValue(0) + // .MaxSliderValue(23) + // .LabelPadding(FMargin(3.f)) + // .LabelLocation(SNumericEntryBox::ELabelLocation::Inside) + // .UndeterminedString(NSLOCTEXT("PropertyEditor", "MultipleValues", "Multiple Values")) + // .OnValueCommitted(const_cast(this),&FMyTimespanDetailCustomization::OnValueCommitted, 0) + // .OnValueChanged(const_cast(this),&FMyTimespanDetailCustomization::OnValueChanged, 0) + // .OnBeginSliderMovement(const_cast(this), &FMyTimespanDetailCustomization::OnBeginSliderMovement) + // .OnEndSliderMovement(const_cast(this), &FMyTimespanDetailCustomization::OnEndSliderMovement) + ] + + SHorizontalBox::Slot() + .Padding(3.75f, 2.0f) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .MinValue(0) + .MaxValue(59) + .MinSliderValue(0) + .MaxSliderValue(59) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(this, &FMyTimespanDetailCustomization::OnGetValue, 1) + .OnValueChanged(this, &FMyTimespanDetailCustomization::OnValueChanged, 1) + .OnValueCommitted(this, &FMyTimespanDetailCustomization::OnValueCommitted, 1) + .OnBeginSliderMovement(this, &FMyTimespanDetailCustomization::OnBeginSliderMovement) + .OnEndSliderMovement(this, &FMyTimespanDetailCustomization::OnEndSliderMovement) + //.OnValueChanged(CreatePerComponentChanged(ComponentIndex, OnComponentChanged, InArgs._ConstrainVector)) + //.OnValueCommitted(CreatePerComponentCommitted(ComponentIndex, OnComponentCommitted, InArgs._ConstrainVector)) + //.ToolTipText(MakeAttributeLambda([Value, TooltipText] + //{ + // if (Value.Get().IsSet()) + // { + // return FText::Format(TooltipText, Value.Get().GetValue()); + // } + // return NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values"); + //})) + //.UndeterminedString(NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values")) + //.ContextMenuExtender(OnContextMenuExtenderComponent) + //.TypeInterface(InArgs._TypeInterface) + //.MinValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinVector)) + //.MaxValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxVector)) + //.MinSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinSliderVector)) + //.MaxSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxSliderVector)) + .LinearDeltaSensitivity(1) + /*.Delta(InArgs._SpinDelta)*/ + /*.OnBeginSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnBeginSliderMovement, OnComponentBeginSliderMovement))*/ + /*.OnEndSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnEndSliderMovement, OnComponentEndSliderMovement))*/ + /*.DisplayToggle(InArgs._DisplayToggle) + .TogglePadding(InArgs._TogglePadding) + .ToggleChecked(ToggleChecked) + .OnToggleChanged(OnToggleChanged)*/ + ] + + SHorizontalBox::Slot() + .Padding(0.0f, 2.0f) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .MinValue(0) + .MaxValue(59) + .MinSliderValue(0) + .MaxSliderValue(59) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(this, &FMyTimespanDetailCustomization::OnGetValue, 2) + .OnValueChanged(this, &FMyTimespanDetailCustomization::OnValueChanged, 2) + .OnValueCommitted(this, &FMyTimespanDetailCustomization::OnValueCommitted, 2) + .OnBeginSliderMovement(this, &FMyTimespanDetailCustomization::OnBeginSliderMovement) + .OnEndSliderMovement(this, &FMyTimespanDetailCustomization::OnEndSliderMovement) + //.OnValueChanged(CreatePerComponentChanged(ComponentIndex, OnComponentChanged, InArgs._ConstrainVector)) + //.OnValueCommitted(CreatePerComponentCommitted(ComponentIndex, OnComponentCommitted, InArgs._ConstrainVector)) + //.ToolTipText(MakeAttributeLambda([Value, TooltipText] + //{ + // if (Value.Get().IsSet()) + // { + // return FText::Format(TooltipText, Value.Get().GetValue()); + // } + // return NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values"); + //})) + //.UndeterminedString(NSLOCTEXT("SVectorInputBox", "MultipleValues", "Multiple Values")) + //.ContextMenuExtender(OnContextMenuExtenderComponent) + //.TypeInterface(InArgs._TypeInterface) + //.MinValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinVector)) + //.MaxValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxVector)) + //.MinSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MinSliderVector)) + //.MaxSliderValue(CreatePerComponentGetter(ComponentIndex, TOptional(), InArgs._MaxSliderVector)) + .LinearDeltaSensitivity(1) + /*.Delta(InArgs._SpinDelta)*/ + /*.OnBeginSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnBeginSliderMovement, OnComponentBeginSliderMovement))*/ + /*.OnEndSliderMovement(CreatePerComponentSliderMovementEvent(InArgs._OnEndSliderMovement, OnComponentEndSliderMovement))*/ + /*.DisplayToggle(InArgs._DisplayToggle) + .TogglePadding(InArgs._TogglePadding) + .ToggleChecked(ToggleChecked) + .OnToggleChanged(OnToggleChanged)*/ + ] + ]; +} + + +/* FMyTimespanDetailCustomization callbacks + *****************************************************************************/ + +TOptional FMyTimespanDetailCustomization::OnGetValue(int32 Index) const +{ + TArray RawData; + PropertyHandle->AccessRawData(RawData); + + if (RawData.Num() != 1) + return TOptional(); + + if (RawData[0] == nullptr) + return TOptional(); + + auto* Timespan = ((FTimespan*)RawData[0]); + + switch (Index) + { + case 0: + return TOptional(Timespan->GetHours()); + + case 1: + return TOptional(Timespan->GetMinutes()); + + case 2: + return TOptional(Timespan->GetSeconds()); + + default: + return TOptional(); + } +} + +void FMyTimespanDetailCustomization::OnBeginSliderMovement() +{ + bIsUsingSlider = true; + + GEditor->BeginTransaction(FText::Format(NSLOCTEXT("MyTimespanDetailCustomization", "SetTimespanProperty", "Edit {0}"), PropertyHandle->GetPropertyDisplayName())); +} + +void FMyTimespanDetailCustomization::OnEndSliderMovement(int32 NewValue) +{ + bIsUsingSlider = false; + + GEditor->EndTransaction(); +} + +void FMyTimespanDetailCustomization::OnValueCommitted(int32 NewValue, ETextCommit::Type CommitType, int32 Index) +{ + TArray RawData; + + PropertyHandle->AccessRawData(RawData); + PropertyHandle->NotifyPreChange(); + + for (auto RawDataInstance : RawData) + { + auto* Timespan = (FTimespan*)RawDataInstance; + + int32 Hours = Timespan->GetHours(); + int32 Minutes = Timespan->GetMinutes(); + int32 Seconds = Timespan->GetSeconds(); + + switch (Index) + { + case 0: + *Timespan = FTimespan(NewValue, Minutes, Seconds); + break; + + case 1: + *Timespan = FTimespan(Hours, NewValue, Seconds); + break; + + case 2: + *Timespan = FTimespan(Hours, Minutes, NewValue); + break; + + default: + break; + } + } + + PropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + PropertyHandle->NotifyFinishedChangingProperties(); +} + +void FMyTimespanDetailCustomization::OnValueChanged(int32 NewValue, int32 Index) +{ + if (!bIsUsingSlider) + return; + + //EPropertyValueSetFlags::Type Flags = EPropertyValueSetFlags::InteractiveChange; + //PropertyHandle->SetValue(NewValue, Flags); + + TArray RawData; + + PropertyHandle->AccessRawData(RawData); + PropertyHandle->NotifyPreChange(); + + for (auto RawDataInstance : RawData) + { + auto* Timespan = (FTimespan*)RawDataInstance; + + int32 Hours = Timespan->GetHours(); + int32 Minutes = Timespan->GetMinutes(); + int32 Seconds = Timespan->GetSeconds(); + + switch (Index) + { + case 0: + *Timespan = FTimespan(NewValue, Minutes, Seconds); + break; + + case 1: + *Timespan = FTimespan(Hours, NewValue, Seconds); + break; + + case 2: + *Timespan = FTimespan(Hours, Minutes, NewValue); + break; + + default: + break; + } + } + + PropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet); + PropertyHandle->NotifyFinishedChangingProperties(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/CommonTime/Public/CommonTimeModule.h b/Source/CommonTime/Public/CommonTimeModule.h new file mode 100644 index 0000000..efc4b93 --- /dev/null +++ b/Source/CommonTime/Public/CommonTimeModule.h @@ -0,0 +1,7 @@ +// Copyright 2023 MrRobin. All Rights Reserved. + +#pragma once + +#include "Logging/LogMacros.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogCommonTime, Log, All); \ No newline at end of file diff --git a/Source/CommonTime/Public/DetailCustomizations/MyDateTimeDetailCustomization.h b/Source/CommonTime/Public/DetailCustomizations/MyDateTimeDetailCustomization.h new file mode 100644 index 0000000..0a59a44 --- /dev/null +++ b/Source/CommonTime/Public/DetailCustomizations/MyDateTimeDetailCustomization.h @@ -0,0 +1,53 @@ +// Copyright 2023 MrRobin. All Rights Reserved. + +#pragma once + +#include "IPropertyTypeCustomization.h" +#include "Internationalization/Text.h" +#include "Styling/SlateColor.h" +#include "Templates/SharedPointer.h" +#include "Types/SlateEnums.h" + +class IPropertyHandle; +class SEditableTextBox; + +/** + * Implements a details view customization for the FTimespan structure. + */ +class FMyDateTimeDetailCustomization + : public IPropertyTypeCustomization +{ +public: +public: + /** + * Creates an instance of this class. + * + * @return The new instance. + */ + static TSharedRef MakeInstance() + { + return MakeShareable(new FMyDateTimeDetailCustomization()); + } + +public: + // IPropertyTypeCustomization interface + void CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + + void CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + +private: + TOptional OnGetValue(int32 Index) const; + /** @return the value being observed by the Numeric Entry Box as a FText */ + FText GetValueAsText(int32 Index) const; + void OnValueCommitted(int32 NewValue, ETextCommit::Type CommitType, int32 Index); + void OnValueChanged(int32 NewValue, int32 Index); + void OnBeginSliderMovement(); + void OnEndSliderMovement(int32 NewValue); + +private: + /** Holds a handle to the property being edited. */ + TSharedPtr PropertyHandle; + + /** True if a value is being changed by dragging a slider */ + bool bIsUsingSlider; +}; diff --git a/Source/CommonTime/Public/DetailCustomizations/MyTimespanDetailCustomization.h b/Source/CommonTime/Public/DetailCustomizations/MyTimespanDetailCustomization.h new file mode 100644 index 0000000..f3fd509 --- /dev/null +++ b/Source/CommonTime/Public/DetailCustomizations/MyTimespanDetailCustomization.h @@ -0,0 +1,50 @@ +// Copyright 2023 MrRobin. All Rights Reserved. + +#pragma once + +#include "IPropertyTypeCustomization.h" +#include "Internationalization/Text.h" +#include "Styling/SlateColor.h" +#include "Templates/SharedPointer.h" +#include "Types/SlateEnums.h" + +class IPropertyHandle; +class SEditableTextBox; + +/** + * Implements a details view customization for the FTimespan structure. + */ +class FMyTimespanDetailCustomization + : public IPropertyTypeCustomization +{ +public: + /** + * Creates an instance of this class. + * + * @return The new instance. + */ + static TSharedRef MakeInstance() + { + return MakeShareable(new FMyTimespanDetailCustomization()); + } + +public: + // IPropertyTypeCustomization interface + void CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + + void CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + +private: + TOptional OnGetValue(int32 Index) const; + void OnValueCommitted(int32 NewValue, ETextCommit::Type CommitType, int32 Index); + void OnValueChanged(int32 NewValue, int32 Index); + void OnBeginSliderMovement(); + void OnEndSliderMovement(int32 NewValue); + +private: + /** Holds a handle to the property being edited. */ + TSharedPtr PropertyHandle; + + /** True if a value is being changed by dragging a slider */ + bool bIsUsingSlider; +};