From 527bc84abb069dffabe0908b8af0d85d65d68fd2 Mon Sep 17 00:00:00 2001 From: Luanrobs Date: Fri, 17 Oct 2025 02:43:56 -0300 Subject: [PATCH 1/6] Added DynamicRange and MinMaxRange attributes Introduces DynamicRangeAttribute and MinMaxRangeAttribute for flexible range constraints in inspector fields. Adds corresponding drawers and sample usage, as well as a utility for drawing min-max sliders in the editor. --- Editor.Extras/Drawers/DynamicRangeDrawer.cs | 150 +++++++++++++++++ .../Drawers/DynamicRangeDrawer.cs.meta | 2 + Editor.Extras/Drawers/MinMaxRangeDrawer.cs | 154 ++++++++++++++++++ .../Drawers/MinMaxRangeDrawer.cs.meta | 2 + .../Decorators_DynamicRangeSample.cs | 19 +++ .../Decorators_DynamicRangeSample.cs.meta | 2 + .../Decorators_MinMaxRangeSample.cs | 23 +++ .../Decorators_MinMaxRangeSample.cs.meta | 2 + Editor/Utilities/TriEditorGUI.MinMaxSlider.cs | 40 +++++ .../TriEditorGUI.MinMaxSlider.cs.meta | 2 + Editor/Utilities/TriEditorGUI.cs | 4 +- .../Decorators/DynamicRangeAttribute.cs | 50 ++++++ .../Decorators/DynamicRangeAttribute.cs.meta | 2 + .../Decorators/MinMaxRangeAttribute.cs | 46 ++++++ .../Decorators/MinMaxRangeAttribute.cs.meta | 2 + 15 files changed, 498 insertions(+), 2 deletions(-) create mode 100644 Editor.Extras/Drawers/DynamicRangeDrawer.cs create mode 100644 Editor.Extras/Drawers/DynamicRangeDrawer.cs.meta create mode 100644 Editor.Extras/Drawers/MinMaxRangeDrawer.cs create mode 100644 Editor.Extras/Drawers/MinMaxRangeDrawer.cs.meta create mode 100644 Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs create mode 100644 Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs.meta create mode 100644 Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs create mode 100644 Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs.meta create mode 100644 Editor/Utilities/TriEditorGUI.MinMaxSlider.cs create mode 100644 Editor/Utilities/TriEditorGUI.MinMaxSlider.cs.meta create mode 100644 Runtime/Attributes/Decorators/DynamicRangeAttribute.cs create mode 100644 Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta create mode 100644 Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs create mode 100644 Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs.meta diff --git a/Editor.Extras/Drawers/DynamicRangeDrawer.cs b/Editor.Extras/Drawers/DynamicRangeDrawer.cs new file mode 100644 index 0000000..da79ae2 --- /dev/null +++ b/Editor.Extras/Drawers/DynamicRangeDrawer.cs @@ -0,0 +1,150 @@ +using System; +using TriInspector; +using TriInspector.Drawers; +using TriInspector.Resolvers; +using UnityEditor; +using UnityEngine; + +[assembly: RegisterTriAttributeDrawer(typeof(DynamicRangeAttributeDrawer), TriDrawerOrder.Decorator, ApplyOnArrayElement = true)] + +namespace TriInspector.Drawers +{ + public class DynamicRangeAttributeDrawer : TriAttributeDrawer + { + private ValueResolver _minFloatResolver; + private ValueResolver _minIntResolver; + private ValueResolver _maxFloatResolver; + private ValueResolver _maxIntResolver; + private ValueResolver _minMaxVector2Resolver; + private ValueResolver _minMaxVector2IntResolver; + + private Type _valueType; + + private bool IsNumericType(Type type) + { + if (type == null) return false; + return typeof(IConvertible).IsAssignableFrom(type) && + type != typeof(string) && + type != typeof(bool) && + type != typeof(char); + } + + public override TriExtensionInitializationResult Initialize(TriPropertyDefinition propertyDefinition) + { + _valueType = propertyDefinition.FieldType; + if (!IsNumericType(_valueType)) + { + return "[Range] attribute can only be used on numeric fields (like int, float, double, etc.)."; + } + + if (!string.IsNullOrEmpty(Attribute.MinMaxMemberName)) + { + _minMaxVector2Resolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMaxMemberName); + if (_minMaxVector2Resolver.TryGetErrorString(out var vector2Error)) + { + _minMaxVector2Resolver = null; + _minMaxVector2IntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMaxMemberName); + if (_minMaxVector2IntResolver.TryGetErrorString(out var vector2IntError)) + { + return $"Failed to resolve MinMaxMemberName '{Attribute.MinMaxMemberName}'.\n" + + $"Vector2 Error: {vector2Error}\n" + + $"Vector2Int Error: {vector2IntError}"; + } + } + return TriExtensionInitializationResult.Ok; + } + + if (!string.IsNullOrEmpty(Attribute.MinMemberName)) + { + _minFloatResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMemberName); + if (_minFloatResolver.TryGetErrorString(out var floatError)) + { + _minFloatResolver = null; + _minIntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMemberName); + if (_minIntResolver.TryGetErrorString(out var intError)) + { + return $"Failed to resolve MinMemberName '{Attribute.MinMemberName}'.\n" + + $"Float Error: {floatError}\n" + + $"Int Error: {intError}"; + } + } + } + + if (!string.IsNullOrEmpty(Attribute.MaxMemberName)) + { + _maxFloatResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MaxMemberName); + if (_maxFloatResolver.TryGetErrorString(out var floatError)) + { + _maxFloatResolver = null; + _maxIntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MaxMemberName); + if (_maxIntResolver.TryGetErrorString(out var intError)) + { + return $"Failed to resolve MaxMemberName '{Attribute.MaxMemberName}'.\n" + + $"Float Error: {floatError}\n" + + $"Int Error: {intError}"; + } + } + } + + return TriExtensionInitializationResult.Ok; + } + + public override void OnGUI(Rect position, TriProperty property, TriElement next) + { + double minLimit = _minMaxVector2Resolver?.GetValue(property, Vector2.zero).x ?? + _minMaxVector2IntResolver?.GetValue(property, Vector2Int.zero).x ?? + _minFloatResolver?.GetValue(property, Attribute.MinFixed) ?? + _minIntResolver?.GetValue(property, (int) Attribute.MinFixed) ?? + Attribute.MinFixed; + + double maxLimit = _minMaxVector2Resolver?.GetValue(property, Vector2.zero).y ?? + _minMaxVector2IntResolver?.GetValue(property, Vector2Int.zero).y ?? + _maxFloatResolver?.GetValue(property, Attribute.MaxFixed) ?? + _maxIntResolver?.GetValue(property, (int) Attribute.MaxFixed) ?? + Attribute.MaxFixed; + + if (minLimit > maxLimit) (minLimit, maxLimit) = (maxLimit, minLimit); + + var label = property.DisplayNameContent; + + double currentValue; + try + { + currentValue = Convert.ToDouble(property.Value); + } + catch (Exception) + { + EditorGUI.LabelField(position, label.text, "Cannot convert value to a number."); + return; + } + + if (property.FieldType == typeof(int)) + { + minLimit = Mathf.RoundToInt((float)minLimit); + maxLimit = Mathf.RoundToInt((float)maxLimit); + } + + // If clamping changed the value, update the property immediately. + double clampedValue = Math.Clamp(currentValue, minLimit, maxLimit); + if (Math.Abs(clampedValue - currentValue) > double.Epsilon) + { + property.SetValue(Convert.ChangeType(clampedValue, _valueType)); + currentValue = clampedValue; + } + + EditorGUI.BeginChangeCheck(); + float sliderValue = EditorGUI.Slider(position, label, (float) currentValue, (float) minLimit, (float) maxLimit); + + if (EditorGUI.EndChangeCheck()) + { + object finalValue = Convert.ChangeType(sliderValue, _valueType); + property.SetValue(finalValue); + } + } + + public override float GetHeight(float width, TriProperty property, TriElement next) + { + return EditorGUIUtility.singleLineHeight; + } + } +} \ No newline at end of file diff --git a/Editor.Extras/Drawers/DynamicRangeDrawer.cs.meta b/Editor.Extras/Drawers/DynamicRangeDrawer.cs.meta new file mode 100644 index 0000000..d13424f --- /dev/null +++ b/Editor.Extras/Drawers/DynamicRangeDrawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a8ed54e1bd18b884ba17ff17b9c232d7 \ No newline at end of file diff --git a/Editor.Extras/Drawers/MinMaxRangeDrawer.cs b/Editor.Extras/Drawers/MinMaxRangeDrawer.cs new file mode 100644 index 0000000..82feecd --- /dev/null +++ b/Editor.Extras/Drawers/MinMaxRangeDrawer.cs @@ -0,0 +1,154 @@ +using System; +using TriInspector; +using TriInspector.Drawers; +using TriInspector.Resolvers; +using TriInspector.Utilities; +using UnityEditor; +using UnityEngine; + +[assembly: RegisterTriAttributeDrawer(typeof(MinMaxRangeAttributeDrawer), TriDrawerOrder.Decorator, ApplyOnArrayElement = true)] + +namespace TriInspector.Drawers +{ + public class MinMaxRangeAttributeDrawer : TriAttributeDrawer + { + private ValueResolver _minFloatResolver; + private ValueResolver _minIntResolver; + private ValueResolver _maxFloatResolver; + private ValueResolver _maxIntResolver; + private ValueResolver _minMaxVector2Resolver; + private ValueResolver _minMaxVector2IntResolver; + + private bool _isVector2Int; + + public override TriExtensionInitializationResult Initialize(TriPropertyDefinition propertyDefinition) + { + var fieldType = propertyDefinition.FieldType; + if (fieldType != typeof(Vector2) && fieldType != typeof(Vector2Int)) + { + return "[MinMaxRange] attribute can only be used on Vector2 or Vector2Int fields."; + } + _isVector2Int = fieldType == typeof(Vector2Int); + + if (!string.IsNullOrEmpty(Attribute.MinMaxMemberName)) + { + _minMaxVector2Resolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMaxMemberName); + if (_minMaxVector2Resolver.TryGetErrorString(out var vector2Error)) + { + _minMaxVector2Resolver = null; + _minMaxVector2IntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMaxMemberName); + if (_minMaxVector2IntResolver.TryGetErrorString(out var vector2IntError)) + { + return $"Failed to resolve MinMaxMemberName '{Attribute.MinMaxMemberName}'.\n" + + $"Vector2 Error: {vector2Error}\n" + + $"Vector2Int Error: {vector2IntError}"; + } + } + return TriExtensionInitializationResult.Ok; + } + + if (!string.IsNullOrEmpty(Attribute.MinMemberName)) + { + _minFloatResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMemberName); + if (_minFloatResolver.TryGetErrorString(out _)) + { + _minFloatResolver = null; + _minIntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMemberName); + if (_minIntResolver.TryGetErrorString(out var error)) return error; + } + } + + if (!string.IsNullOrEmpty(Attribute.MaxMemberName)) + { + _maxFloatResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MaxMemberName); + if (_maxFloatResolver.TryGetErrorString(out _)) + { + _maxFloatResolver = null; + _maxIntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MaxMemberName); + if (_maxIntResolver.TryGetErrorString(out var error)) return error; + } + } + + return TriExtensionInitializationResult.Ok; + } + + public override void OnGUI(Rect position, TriProperty property, TriElement next) + { + float minLimit = _minMaxVector2Resolver?.GetValue(property, Vector2.zero).x ?? + _minMaxVector2IntResolver?.GetValue(property, Vector2Int.zero).x ?? + _minFloatResolver?.GetValue(property, Attribute.MinFixed) ?? + _minIntResolver?.GetValue(property, (int) Attribute.MinFixed) ?? + Attribute.MinFixed; + + float maxLimit = _minMaxVector2Resolver?.GetValue(property, Vector2.zero).y ?? + _minMaxVector2IntResolver?.GetValue(property, Vector2Int.zero).y ?? + _maxFloatResolver?.GetValue(property, Attribute.MaxFixed) ?? + _maxIntResolver?.GetValue(property, (int) Attribute.MaxFixed) ?? + Attribute.MaxFixed; + + if (minLimit > maxLimit) (minLimit, maxLimit) = (maxLimit, minLimit); + + float xValue, yValue; + if (_isVector2Int) + { + var val = (Vector2Int) property.Value; + xValue = val.x; + yValue = val.y; + minLimit = Mathf.RoundToInt(minLimit); + maxLimit = Mathf.RoundToInt(maxLimit); + } + else + { + var val = (Vector2) property.Value; + xValue = val.x; + yValue = val.y; + } + + float clampedX = xValue; + float clampedY = yValue; + + if (clampedX > clampedY) (clampedX, clampedY) = (clampedY, clampedX); + clampedX = Mathf.Clamp(clampedX, minLimit, clampedY); + clampedY = Mathf.Clamp(clampedY, clampedX, maxLimit); + + // If clamping changed the value, update the property immediately. + if (Math.Abs(clampedX - xValue) > 0.001f || Math.Abs(clampedY - yValue) > 0.001f) + { + xValue = clampedX; + yValue = clampedY; + + if (_isVector2Int) + property.SetValue(new Vector2Int(Mathf.RoundToInt(xValue), Mathf.RoundToInt(yValue))); + else + property.SetValue(new Vector2(xValue, yValue)); + } + + var label = property.DisplayNameContent; + //EditorGUI.BeginChangeCheck(); + var controlRect = EditorGUI.PrefixLabel(position, label); + + TriEditorGUI.DrawMinMaxSlider(controlRect, ref xValue, ref yValue, minLimit, maxLimit); + + if (EditorGUI.EndChangeCheck()) + { + // The slider itself ensures values are within the min/max limits. + // We just need to apply the final result. + if (_isVector2Int) + { + var newVec = new Vector2Int(Mathf.RoundToInt(xValue), Mathf.RoundToInt(yValue)); + property.SetValue(newVec); + } + else + { + var newVec = new Vector2(xValue, yValue); + property.SetValue(newVec); + } + } + } + + public override float GetHeight(float width, TriProperty property, TriElement next) + { + return EditorGUIUtility.singleLineHeight; + } + } +} \ No newline at end of file diff --git a/Editor.Extras/Drawers/MinMaxRangeDrawer.cs.meta b/Editor.Extras/Drawers/MinMaxRangeDrawer.cs.meta new file mode 100644 index 0000000..8b95184 --- /dev/null +++ b/Editor.Extras/Drawers/MinMaxRangeDrawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 65150d33a2dca50489053b8f89eddc94 \ No newline at end of file diff --git a/Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs b/Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs new file mode 100644 index 0000000..b475164 --- /dev/null +++ b/Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs @@ -0,0 +1,19 @@ +using UnityEngine; +using TriInspector; + +public class Decorators_DynamicRangeSample : ScriptableObject +{ + [DynamicRange(nameof(_min), nameof(_max))] + public int dynamicIntRange = -5; + + [DynamicRange(0, nameof(GetMax))] + public float dynamicMaxFloatRange = 4.6f; + + [DynamicRange(nameof(minMax))] + public float dynamicFloatRange = 1.05f; + + public Vector2 minMax = new(-10, 10); + private int _min = -20; + private int _max = 20; + public float GetMax() => 10; +} diff --git a/Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs.meta b/Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs.meta new file mode 100644 index 0000000..e2cde77 --- /dev/null +++ b/Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9dadc5c8cabd71f4391e3019b60ebde9 \ No newline at end of file diff --git a/Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs b/Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs new file mode 100644 index 0000000..bb2ea0a --- /dev/null +++ b/Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs @@ -0,0 +1,23 @@ +using UnityEngine; +using TriInspector; + +public class Decorators_MinMaxRangeSample : ScriptableObject +{ + [MinMaxRange(0f, 10f)] + public Vector2 fixedMinMaxRange = new(2f, 4f); + + [MinMaxRange(nameof(_min), nameof(_max))] + public Vector2Int dynamicIntMinMaxRange = new(-8, 0); + + [MinMaxRange(-20, nameof(GetMax))] + public Vector2 dynamicFloatMaxRange = new(-7.7f, -1.7f); + + [MinMaxRange(nameof(minMax))] + public Vector2Int dynamicFloatMinMaxRange = new(0, 4); + + + public Vector2 minMax = new(-10, 10); + private int _min = -20; + private int _max = 20; + public float GetMax() => 10; +} diff --git a/Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs.meta b/Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs.meta new file mode 100644 index 0000000..f04101c --- /dev/null +++ b/Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 99e5bca5e5c0c5f41a3e5e008f69bbcf \ No newline at end of file diff --git a/Editor/Utilities/TriEditorGUI.MinMaxSlider.cs b/Editor/Utilities/TriEditorGUI.MinMaxSlider.cs new file mode 100644 index 0000000..8f6399b --- /dev/null +++ b/Editor/Utilities/TriEditorGUI.MinMaxSlider.cs @@ -0,0 +1,40 @@ +using UnityEditor; +using UnityEngine; + +namespace TriInspector.Utilities +{ + public static partial class TriEditorGUI + { + public static void DrawMinMaxSlider(Rect rect, ref float xValue, ref float yValue, float minValue, float maxValue) + { + var fieldWidth = EditorGUIUtility.fieldWidth; + var minFieldRect = new Rect(rect.xMin, rect.y, fieldWidth, rect.height); + var maxFieldRect = new Rect(rect.xMax - fieldWidth, rect.y, fieldWidth, rect.height); + + //set slider rect between min and max fields + additional padding + const float spacing = 8.0f; + var sliderRect = Rect.MinMaxRect(minFieldRect.xMax + spacing, + rect.yMin, + maxFieldRect.xMin - spacing, + rect.yMax); + + EditorGUI.BeginChangeCheck(); + xValue = EditorGUI.FloatField(minFieldRect, xValue); + yValue = EditorGUI.FloatField(maxFieldRect, yValue); + EditorGUI.MinMaxSlider(sliderRect, ref xValue, ref yValue, minValue, maxValue); + + //values validation (xValue can't be higher than yValue etc.) + xValue = Mathf.Clamp(xValue, minValue, Mathf.Min(maxValue, yValue)); + yValue = Mathf.Clamp(yValue, Mathf.Max(minValue, xValue), maxValue); + } + public static void DrawMinMaxSlider(Rect rect, string label, ref float xValue, ref float yValue, float minValue, float maxValue) + { + DrawMinMaxSlider(rect, new GUIContent(label), ref xValue, ref yValue, minValue, maxValue); + } + public static void DrawMinMaxSlider(Rect rect, GUIContent label, ref float xValue, ref float yValue, float minValue, float maxValue) + { + rect = EditorGUI.PrefixLabel(rect, label); + DrawMinMaxSlider(rect, ref xValue, ref yValue, minValue, maxValue); + } + } +} diff --git a/Editor/Utilities/TriEditorGUI.MinMaxSlider.cs.meta b/Editor/Utilities/TriEditorGUI.MinMaxSlider.cs.meta new file mode 100644 index 0000000..3cd8817 --- /dev/null +++ b/Editor/Utilities/TriEditorGUI.MinMaxSlider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 547d2e05782b5f849b6bea8585320d7e \ No newline at end of file diff --git a/Editor/Utilities/TriEditorGUI.cs b/Editor/Utilities/TriEditorGUI.cs index fc65575..d9d1963 100644 --- a/Editor/Utilities/TriEditorGUI.cs +++ b/Editor/Utilities/TriEditorGUI.cs @@ -1,9 +1,9 @@ -using UnityEditor; +using UnityEditor; using UnityEngine; namespace TriInspector.Utilities { - public static class TriEditorGUI + public static partial class TriEditorGUI { public static void Foldout(Rect rect, TriProperty property) { diff --git a/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs b/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs new file mode 100644 index 0000000..0162fb6 --- /dev/null +++ b/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs @@ -0,0 +1,50 @@ +using System; +using System.Diagnostics; +using UnityEngine; + +namespace TriInspector +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] + [Conditional("UNITY_EDITOR")] + public class DynamicRangeAttribute : Attribute + { + public float MinFixed { get; } + public float MaxFixed { get; } + + public string MinMemberName { get; } + public string MaxMemberName { get; } + public string MinMaxMemberName { get; } + + public DynamicRangeAttribute() : this(0f, 1f) { } + + public DynamicRangeAttribute(float min, float max) + { + MinFixed = min; + MaxFixed = max; + } + + public DynamicRangeAttribute(string minMemberName, string maxMemberName) : this() + { + MinMemberName = minMemberName; + MaxMemberName = maxMemberName; + } + + public DynamicRangeAttribute(float min, string maxMemberName) + { + MinFixed = min; + MaxFixed = 1; + MaxMemberName = maxMemberName; + } + + public DynamicRangeAttribute(string minMemberName, float max) + { + MinFixed = 0; + MaxFixed = max; + MinMemberName = minMemberName; + } + public DynamicRangeAttribute(string minMaxMemberName) : this() + { + MinMaxMemberName = minMaxMemberName; + } + } +} \ No newline at end of file diff --git a/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta b/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta new file mode 100644 index 0000000..bb20bf4 --- /dev/null +++ b/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 347e65740a885e14ca4044800a6d4f1d \ No newline at end of file diff --git a/Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs b/Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs new file mode 100644 index 0000000..f0f3a55 --- /dev/null +++ b/Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs @@ -0,0 +1,46 @@ +using System; +using System.Diagnostics; +using UnityEngine; + +namespace TriInspector +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] + [Conditional("UNITY_EDITOR")] + public class MinMaxRangeAttribute : PropertyAttribute + { + public float MinFixed { get; } + public float MaxFixed { get; } + + public string MinMemberName { get; } + public string MaxMemberName { get; } + public string MinMaxMemberName { get; } + + public MinMaxRangeAttribute() : this(0f, 1f) { } + public MinMaxRangeAttribute(float min, float max) + { + MinFixed = min; + MaxFixed = max; + } + public MinMaxRangeAttribute(string minMemberName, string maxMemberName) : this() + { + MinMemberName = minMemberName; + MaxMemberName = maxMemberName; + } + public MinMaxRangeAttribute(float min, string maxMemberName) + { + MinFixed = min; + MaxFixed = 1; + MaxMemberName = maxMemberName; + } + public MinMaxRangeAttribute(string minMemberName, float max) + { + MinFixed = 0; + MaxFixed = max; + MinMemberName = minMemberName; + } + public MinMaxRangeAttribute(string minMaxMemberName) : this() + { + MinMaxMemberName = minMaxMemberName; + } + } +} \ No newline at end of file diff --git a/Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs.meta b/Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs.meta new file mode 100644 index 0000000..f2c526c --- /dev/null +++ b/Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 952c36bb2cece244484d37d82b228ac1 \ No newline at end of file From 63bd03e263cc6e7bb00b7e7cd7062c49bd18878a Mon Sep 17 00:00:00 2001 From: Luanrobs Date: Fri, 17 Oct 2025 02:57:29 -0300 Subject: [PATCH 2/6] Update readme --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index fd17466..06a9bcc 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,26 @@ private IEnumerable> GetVectorValues() } ``` +#### DynamicRange + +![DynamicRange](https://github.com/user-attachments/assets/9ad79364-715e-439c-8772-3a4b5b1aea5e) + +```csharp +[DynamicRange(nameof(_min), nameof(_max))] +public int dynamicIntRange = -5; + +[DynamicRange(0, nameof(GetMax))] +public float dynamicMaxFloatRange = 4.6f; + +[DynamicRange(nameof(minMax))] +public float dynamicFloatRange = 1.05f; + +public Vector2 minMax = new(-10, 10); +private int _min = -20; +private int _max = 20; +public float GetMax() => 10; +``` + #### Scene ![Scene](https://user-images.githubusercontent.com/26966368/179394466-a9397212-e3bc-40f1-b721-8f7c43aa3048.png) @@ -267,6 +287,30 @@ private IEnumerable> GetVectorValues() public Material mat; ``` +#### MinMaxRange + +![MinMaxRange](https://github.com/user-attachments/assets/d36966ef-9433-45c1-be61-b02a85682553) + +```csharp +[MinMaxRange(0f, 10f)] +public Vector2 fixedMinMaxRange = new(2f, 4f); + +[MinMaxRange(nameof(_min), nameof(_max))] +public Vector2Int dynamicIntMinMaxRange = new(-8, 0); + +[MinMaxRange(-20, nameof(GetMax))] +public Vector2 dynamicFloatMaxRange = new(-7.7f, -1.7f); + +[MinMaxRange(nameof(minMax))] +public Vector2Int dynamicFloatMinMaxRange = new(0, 4); + + +public Vector2 minMax = new(-10, 10); +private int _min = -20; +private int _max = 20; +public float GetMax() => 10; +``` + #### DisplayAsString ![DisplayAsString](https://user-images.githubusercontent.com/26966368/224530522-8aa24fbe-4bc7-4290-89d1-d88c5c502e2b.png) From 4f86a9de6afc787f161e0705b605db2bf29a0527 Mon Sep 17 00:00:00 2001 From: Luanrobs Date: Fri, 17 Oct 2025 03:42:53 -0300 Subject: [PATCH 3/6] Simplify error messages in range drawer resolvers Error messages in DynamicRangeDrawer and MinMaxRangeDrawer now return only the specific resolver error instead of concatenated multi-type error strings. This makes error outputs more concise and focused. --- Editor.Extras/Drawers/DynamicRangeDrawer.cs | 12 +++--------- Editor.Extras/Drawers/MinMaxRangeDrawer.cs | 4 +--- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Editor.Extras/Drawers/DynamicRangeDrawer.cs b/Editor.Extras/Drawers/DynamicRangeDrawer.cs index da79ae2..7ae2c88 100644 --- a/Editor.Extras/Drawers/DynamicRangeDrawer.cs +++ b/Editor.Extras/Drawers/DynamicRangeDrawer.cs @@ -46,9 +46,7 @@ public override TriExtensionInitializationResult Initialize(TriPropertyDefinitio _minMaxVector2IntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMaxMemberName); if (_minMaxVector2IntResolver.TryGetErrorString(out var vector2IntError)) { - return $"Failed to resolve MinMaxMemberName '{Attribute.MinMaxMemberName}'.\n" + - $"Vector2 Error: {vector2Error}\n" + - $"Vector2Int Error: {vector2IntError}"; + return vector2IntError; } } return TriExtensionInitializationResult.Ok; @@ -63,9 +61,7 @@ public override TriExtensionInitializationResult Initialize(TriPropertyDefinitio _minIntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMemberName); if (_minIntResolver.TryGetErrorString(out var intError)) { - return $"Failed to resolve MinMemberName '{Attribute.MinMemberName}'.\n" + - $"Float Error: {floatError}\n" + - $"Int Error: {intError}"; + return intError; } } } @@ -79,9 +75,7 @@ public override TriExtensionInitializationResult Initialize(TriPropertyDefinitio _maxIntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MaxMemberName); if (_maxIntResolver.TryGetErrorString(out var intError)) { - return $"Failed to resolve MaxMemberName '{Attribute.MaxMemberName}'.\n" + - $"Float Error: {floatError}\n" + - $"Int Error: {intError}"; + return intError; } } } diff --git a/Editor.Extras/Drawers/MinMaxRangeDrawer.cs b/Editor.Extras/Drawers/MinMaxRangeDrawer.cs index 82feecd..0e5235a 100644 --- a/Editor.Extras/Drawers/MinMaxRangeDrawer.cs +++ b/Editor.Extras/Drawers/MinMaxRangeDrawer.cs @@ -39,9 +39,7 @@ public override TriExtensionInitializationResult Initialize(TriPropertyDefinitio _minMaxVector2IntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMaxMemberName); if (_minMaxVector2IntResolver.TryGetErrorString(out var vector2IntError)) { - return $"Failed to resolve MinMaxMemberName '{Attribute.MinMaxMemberName}'.\n" + - $"Vector2 Error: {vector2Error}\n" + - $"Vector2Int Error: {vector2IntError}"; + return vector2IntError; } } return TriExtensionInitializationResult.Ok; From 2d568b823f5537adfd15febb7e543ac2d61289d1 Mon Sep 17 00:00:00 2001 From: Luanrobs Date: Fri, 17 Oct 2025 12:01:29 -0300 Subject: [PATCH 4/6] Rename and refactor range attributes and drawers Renamed MinMaxRangeAttribute to MinMaxSliderAttribute and DynamicRangeAttribute to inherit from a new SliderAttribute. Updated corresponding drawers and sample files to use the new naming. Refactored attribute implementations for consistency and simplified inheritance. Updated README and sample code to reflect these changes and improve clarity. --- ...axRangeDrawer.cs => MinMaxSliderDrawer.cs} | 4 +- ...wer.cs.meta => MinMaxSliderDrawer.cs.meta} | 0 ...{DynamicRangeDrawer.cs => SliderDrawer.cs} | 4 +- ...ngeDrawer.cs.meta => SliderDrawer.cs.meta} | 0 .../Decorators_DynamicRangeSample.cs | 19 ----- .../Decorators_MinMaxRangeSample.cs | 23 ------ .../Decorators_MinMaxSliderSample.cs | 23 ++++++ ... => Decorators_MinMaxSliderSample.cs.meta} | 0 ...rators_SliderSampleOrDynamicRangeSample.cs | 22 ++++++ ..._SliderSampleOrDynamicRangeSample.cs.meta} | 0 Editor.Samples/TriSamplesWindow.cs | 29 ++++++-- README.md | 72 ++++++++++--------- .../Decorators/DynamicRangeAttribute.cs | 54 +++----------- .../Decorators/DynamicRangeAttribute.cs.meta | 2 +- .../Decorators/MinMaxSliderAttribute.cs | 46 ++++++++++++ ....cs.meta => MinMaxSliderAttribute.cs.meta} | 0 ...axRangeAttribute.cs => SliderAttribute.cs} | 18 +++-- .../Decorators/SliderAttribute.cs.meta | 2 + 18 files changed, 177 insertions(+), 141 deletions(-) rename Editor.Extras/Drawers/{MinMaxRangeDrawer.cs => MinMaxSliderDrawer.cs} (96%) rename Editor.Extras/Drawers/{MinMaxRangeDrawer.cs.meta => MinMaxSliderDrawer.cs.meta} (100%) rename Editor.Extras/Drawers/{DynamicRangeDrawer.cs => SliderDrawer.cs} (96%) rename Editor.Extras/Drawers/{DynamicRangeDrawer.cs.meta => SliderDrawer.cs.meta} (100%) delete mode 100644 Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs delete mode 100644 Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs create mode 100644 Editor.Samples/Decorators/Decorators_MinMaxSliderSample.cs rename Editor.Samples/Decorators/{Decorators_MinMaxRangeSample.cs.meta => Decorators_MinMaxSliderSample.cs.meta} (100%) create mode 100644 Editor.Samples/Decorators/Decorators_SliderSampleOrDynamicRangeSample.cs rename Editor.Samples/Decorators/{Decorators_DynamicRangeSample.cs.meta => Decorators_SliderSampleOrDynamicRangeSample.cs.meta} (100%) create mode 100644 Runtime/Attributes/Decorators/MinMaxSliderAttribute.cs rename Runtime/Attributes/Decorators/{MinMaxRangeAttribute.cs.meta => MinMaxSliderAttribute.cs.meta} (100%) rename Runtime/Attributes/Decorators/{MinMaxRangeAttribute.cs => SliderAttribute.cs} (66%) create mode 100644 Runtime/Attributes/Decorators/SliderAttribute.cs.meta diff --git a/Editor.Extras/Drawers/MinMaxRangeDrawer.cs b/Editor.Extras/Drawers/MinMaxSliderDrawer.cs similarity index 96% rename from Editor.Extras/Drawers/MinMaxRangeDrawer.cs rename to Editor.Extras/Drawers/MinMaxSliderDrawer.cs index 0e5235a..8ea989e 100644 --- a/Editor.Extras/Drawers/MinMaxRangeDrawer.cs +++ b/Editor.Extras/Drawers/MinMaxSliderDrawer.cs @@ -6,11 +6,11 @@ using UnityEditor; using UnityEngine; -[assembly: RegisterTriAttributeDrawer(typeof(MinMaxRangeAttributeDrawer), TriDrawerOrder.Decorator, ApplyOnArrayElement = true)] +[assembly: RegisterTriAttributeDrawer(typeof(MinMaxSliderAttributeDrawer), TriDrawerOrder.Decorator, ApplyOnArrayElement = true)] namespace TriInspector.Drawers { - public class MinMaxRangeAttributeDrawer : TriAttributeDrawer + public class MinMaxSliderAttributeDrawer : TriAttributeDrawer { private ValueResolver _minFloatResolver; private ValueResolver _minIntResolver; diff --git a/Editor.Extras/Drawers/MinMaxRangeDrawer.cs.meta b/Editor.Extras/Drawers/MinMaxSliderDrawer.cs.meta similarity index 100% rename from Editor.Extras/Drawers/MinMaxRangeDrawer.cs.meta rename to Editor.Extras/Drawers/MinMaxSliderDrawer.cs.meta diff --git a/Editor.Extras/Drawers/DynamicRangeDrawer.cs b/Editor.Extras/Drawers/SliderDrawer.cs similarity index 96% rename from Editor.Extras/Drawers/DynamicRangeDrawer.cs rename to Editor.Extras/Drawers/SliderDrawer.cs index 7ae2c88..94a98aa 100644 --- a/Editor.Extras/Drawers/DynamicRangeDrawer.cs +++ b/Editor.Extras/Drawers/SliderDrawer.cs @@ -5,11 +5,11 @@ using UnityEditor; using UnityEngine; -[assembly: RegisterTriAttributeDrawer(typeof(DynamicRangeAttributeDrawer), TriDrawerOrder.Decorator, ApplyOnArrayElement = true)] +[assembly: RegisterTriAttributeDrawer(typeof(SliderAttributeDrawer), TriDrawerOrder.Decorator, ApplyOnArrayElement = true)] namespace TriInspector.Drawers { - public class DynamicRangeAttributeDrawer : TriAttributeDrawer + public class SliderAttributeDrawer : TriAttributeDrawer { private ValueResolver _minFloatResolver; private ValueResolver _minIntResolver; diff --git a/Editor.Extras/Drawers/DynamicRangeDrawer.cs.meta b/Editor.Extras/Drawers/SliderDrawer.cs.meta similarity index 100% rename from Editor.Extras/Drawers/DynamicRangeDrawer.cs.meta rename to Editor.Extras/Drawers/SliderDrawer.cs.meta diff --git a/Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs b/Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs deleted file mode 100644 index b475164..0000000 --- a/Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs +++ /dev/null @@ -1,19 +0,0 @@ -using UnityEngine; -using TriInspector; - -public class Decorators_DynamicRangeSample : ScriptableObject -{ - [DynamicRange(nameof(_min), nameof(_max))] - public int dynamicIntRange = -5; - - [DynamicRange(0, nameof(GetMax))] - public float dynamicMaxFloatRange = 4.6f; - - [DynamicRange(nameof(minMax))] - public float dynamicFloatRange = 1.05f; - - public Vector2 minMax = new(-10, 10); - private int _min = -20; - private int _max = 20; - public float GetMax() => 10; -} diff --git a/Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs b/Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs deleted file mode 100644 index bb2ea0a..0000000 --- a/Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs +++ /dev/null @@ -1,23 +0,0 @@ -using UnityEngine; -using TriInspector; - -public class Decorators_MinMaxRangeSample : ScriptableObject -{ - [MinMaxRange(0f, 10f)] - public Vector2 fixedMinMaxRange = new(2f, 4f); - - [MinMaxRange(nameof(_min), nameof(_max))] - public Vector2Int dynamicIntMinMaxRange = new(-8, 0); - - [MinMaxRange(-20, nameof(GetMax))] - public Vector2 dynamicFloatMaxRange = new(-7.7f, -1.7f); - - [MinMaxRange(nameof(minMax))] - public Vector2Int dynamicFloatMinMaxRange = new(0, 4); - - - public Vector2 minMax = new(-10, 10); - private int _min = -20; - private int _max = 20; - public float GetMax() => 10; -} diff --git a/Editor.Samples/Decorators/Decorators_MinMaxSliderSample.cs b/Editor.Samples/Decorators/Decorators_MinMaxSliderSample.cs new file mode 100644 index 0000000..d232269 --- /dev/null +++ b/Editor.Samples/Decorators/Decorators_MinMaxSliderSample.cs @@ -0,0 +1,23 @@ +using UnityEngine; +using TriInspector; + +public class Decorators_MinMaxSliderSample : ScriptableObject +{ + [MinMaxSlider(0f, 10f)] + public Vector2 fixedMinMaxSlider = new(2f, 4f); + + [MinMaxSlider(nameof(_min), nameof(_max))] + public Vector2Int dynamicIntMinMaxSlider = new(-8, 0); + + [MinMaxSlider(-20, nameof(GetMax))] + public Vector2 dynamicFloatMaxSlider = new(-7.7f, -1.7f); + + [MinMaxSlider(nameof(minMax))] + public Vector2Int dynamicFloatMinMaxSlider = new(0, 4); + + + public Vector2 minMax = new(-10, 10); + private int _min = -20; + private int _max = 20; + public float GetMax() => 10; +} diff --git a/Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs.meta b/Editor.Samples/Decorators/Decorators_MinMaxSliderSample.cs.meta similarity index 100% rename from Editor.Samples/Decorators/Decorators_MinMaxRangeSample.cs.meta rename to Editor.Samples/Decorators/Decorators_MinMaxSliderSample.cs.meta diff --git a/Editor.Samples/Decorators/Decorators_SliderSampleOrDynamicRangeSample.cs b/Editor.Samples/Decorators/Decorators_SliderSampleOrDynamicRangeSample.cs new file mode 100644 index 0000000..76e35a9 --- /dev/null +++ b/Editor.Samples/Decorators/Decorators_SliderSampleOrDynamicRangeSample.cs @@ -0,0 +1,22 @@ +using UnityEngine; +using TriInspector; + +public class Decorators_SliderSampleOrDynamicRangeSample : ScriptableObject +{ + [Slider(nameof(_min), nameof(_max))] + public int dynamicIntSlider = -5; + + [Slider(0, nameof(GetMax))] + public float dynamicMaxFloatSlider = 4.6f; + + [Slider(nameof(minMax))] + public float dynamicFloatSlider = 1.05f; + + [DynamicRange(nameof(minMax))] + public float dynamicFloatRange = 1.05f; + + public Vector2 minMax = new(-10, 10); + private int _min = -20; + private int _max = 20; + public float GetMax() => 10; +} diff --git a/Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs.meta b/Editor.Samples/Decorators/Decorators_SliderSampleOrDynamicRangeSample.cs.meta similarity index 100% rename from Editor.Samples/Decorators/Decorators_DynamicRangeSample.cs.meta rename to Editor.Samples/Decorators/Decorators_SliderSampleOrDynamicRangeSample.cs.meta diff --git a/Editor.Samples/TriSamplesWindow.cs b/Editor.Samples/TriSamplesWindow.cs index f8df986..64e24ba 100644 --- a/Editor.Samples/TriSamplesWindow.cs +++ b/Editor.Samples/TriSamplesWindow.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using TriInspector.Utilities; @@ -163,19 +163,34 @@ private void ChangeCurrentSample(Type type) private static string GetTypeNiceName(Type type) { var name = type.Name; + const string suffix = "Sample"; + // 1. Remove the prefix (e.g., "Decorators_") if (name.Contains('_')) { var index = name.IndexOf('_'); name = name.Substring(index + 1); } - if (name.EndsWith("Sample")) - { - name = name.Remove(name.Length - "Sample".Length); - } - - return name; + // 2. Define the delimiter + var delimiter = $"{suffix}Or"; // "SampleOr" + + // 3. Split by "SampleOr" + // "SliderSampleOrDynamicRangeSample" -> ["Slider", "DynamicRangeSample"] + // "SliderSample" -> ["SliderSample"] + var parts = name.Split(new[] { delimiter }, StringSplitOptions.None); + + // 4. Clean the "Sample" suffix from EACH part + var cleanedParts = parts.Select(part => + part.EndsWith(suffix) + ? part.Remove(part.Length - suffix.Length) + : part + ); + + // 5. Join with "/" + // ["Slider", "DynamicRange"] -> "Slider/DynamicRange" + // ["Slider"] -> "Slider" + return string.Join("/", cleanedParts); } private class MenuTree : TreeView diff --git a/README.md b/README.md index 06a9bcc..5e9595c 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,18 @@ _Advanced inspector attributes for Unity_ - [Roadmap](#Roadmap-) - [Samples](#Samples) - [Attributes](#Attributes) - - [Misc](#Misc) - - [Validation](#Validation) - - [Decorators](#Decorators) - - [Styling](#Styling) - - [Collections](#Collections) - - [Conditionals](#Conditionals) - - [Buttons](#Buttons) - - [Debug](#Debug) - - [Groups](#Groups) + - [Misc](#Misc) + - [Validation](#Validation) + - [Decorators](#Decorators) + - [Styling](#Styling) + - [Collections](#Collections) + - [Conditionals](#Conditionals) + - [Buttons](#Buttons) + - [Debug](#Debug) + - [Groups](#Groups) - [Integrations](#Integrations) - - [Odin Inspector](#Odin-Inspector) - - [Odin Validator](#Odin-Validator) + - [Odin Inspector](#Odin-Inspector) + - [Odin Validator](#Odin-Validator) - [License](#License) ## How to Install @@ -33,7 +33,8 @@ Library distributed as git package ([How to install package from git URL](https: > Git URL: https://github.com/codewriter-packages/Unity-Localization-Stub-for-Tri-Inspector.git ## Roadmap ![GitHub Repo stars](https://img.shields.io/github/stars/codewriter-packages/Tri-Inspector?style=social) -Each star ★ on the project page brings new features closer. + +Each star ★ on the project page brings new features closer. You can suggest new features in the [Discussions](https://github.com/codewriter-packages/Tri-Inspector/discussions). ## Samples @@ -121,7 +122,7 @@ Invokes callback on property modification. ```csharp [OnValueChanged(nameof(OnMaterialChanged))] -public Material mat; +public Material mat; private void OnMaterialChanged() { @@ -250,16 +251,19 @@ private IEnumerable> GetVectorValues() } ``` -#### DynamicRange +#### Slider/DynamicRange -![DynamicRange](https://github.com/user-attachments/assets/9ad79364-715e-439c-8772-3a4b5b1aea5e) +![Slider/DynamicRange](https://github.com/user-attachments/assets/9ad79364-715e-439c-8772-3a4b5b1aea5e) ```csharp -[DynamicRange(nameof(_min), nameof(_max))] -public int dynamicIntRange = -5; +[Slider(nameof(_min), nameof(_max))] +public int dynamicIntSlider = -5; + +[Slider(0, nameof(GetMax))] +public float dynamicMaxFloatSlider = 4.6f; -[DynamicRange(0, nameof(GetMax))] -public float dynamicMaxFloatRange = 4.6f; +[Slider(nameof(minMax))] +public float dynamicFloatSlider = 1.05f; [DynamicRange(nameof(minMax))] public float dynamicFloatRange = 1.05f; @@ -287,22 +291,22 @@ public float GetMax() => 10; public Material mat; ``` -#### MinMaxRange +#### MinMaxSlider -![MinMaxRange](https://github.com/user-attachments/assets/d36966ef-9433-45c1-be61-b02a85682553) +![MinMaxSlider](https://github.com/user-attachments/assets/d36966ef-9433-45c1-be61-b02a85682553) ```csharp -[MinMaxRange(0f, 10f)] -public Vector2 fixedMinMaxRange = new(2f, 4f); +[MinMaxSlider(0f, 10f)] +public Vector2 fixedMinMaxSlider = new(2f, 4f); -[MinMaxRange(nameof(_min), nameof(_max))] -public Vector2Int dynamicIntMinMaxRange = new(-8, 0); +[MinMaxSlider(nameof(_min), nameof(_max))] +public Vector2Int dynamicIntMinMaxSlider = new(-8, 0); -[MinMaxRange(-20, nameof(GetMax))] +[MinMaxSlider(-20, nameof(GetMax))] public Vector2 dynamicFloatMaxRange = new(-7.7f, -1.7f); -[MinMaxRange(nameof(minMax))] -public Vector2Int dynamicFloatMinMaxRange = new(0, 4); +[MinMaxSlider(nameof(minMax))] +public Vector2Int dynamicFloatMinMaxSlider = new(0, 4); public Vector2 minMax = new(-10, 10); @@ -743,7 +747,7 @@ public class FoldoutGroupSample : ScriptableObject { [Group("foldout")] public int a; [Group("foldout")] public bool b; - + public string DynamicTitle => "My Foldout"; } ``` @@ -759,7 +763,7 @@ public class ToggleGroupSample : ScriptableObject [Group("toggle")] public bool enabled; [Group("toggle")] public int a; [Group("toggle")] public bool b; - + public string DynamicTitle => "My Toggle"; } ``` @@ -818,11 +822,11 @@ public class VerticalGroupSample : ScriptableObject ### Odin Inspector -Tri Inspector is able to work in compatibility mode with Odin Inspector. -In this mode, the primary interface will be drawn by the Odin Inspector. However, +Tri Inspector is able to work in compatibility mode with Odin Inspector. +In this mode, the primary interface will be drawn by the Odin Inspector. However, parts of the interface can be rendered by the Tri Inspector. -In order for the interface to be rendered by Tri instead of Odin, +In order for the interface to be rendered by Tri instead of Odin, it is necessary to mark classes with `[DrawWithTriInspector]` attribute. Alternatively, you can mark the entire assembly with an attribute `[assembly:DrawWithTriInspector]` @@ -831,7 +835,7 @@ to draw all types in the assembly using the Tri Inspector. ### Odin Validator Tri Inspector is integrated with the Odin Validator -so all validation results from Tri attributes will be shown +so all validation results from Tri attributes will be shown in the Odin Validator window. ![Odin-Validator-Integration](https://user-images.githubusercontent.com/26966368/169645537-d8f0b50f-46af-4804-95e8-337ff3b5ae83.png) diff --git a/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs b/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs index 0162fb6..763e5d1 100644 --- a/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs +++ b/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs @@ -1,50 +1,12 @@ -using System; -using System.Diagnostics; -using UnityEngine; +using TriInspector; -namespace TriInspector +public class DynamicRangeAttribute : SliderAttribute { - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] - [Conditional("UNITY_EDITOR")] - public class DynamicRangeAttribute : Attribute - { - public float MinFixed { get; } - public float MaxFixed { get; } + public DynamicRangeAttribute() : base() { } + public DynamicRangeAttribute(float min, float max) : base(min, max) { } + public DynamicRangeAttribute(string minMemberName, string maxMemberName) : base(minMemberName, maxMemberName) { } + public DynamicRangeAttribute(float min, string maxMemberName) : base(min, maxMemberName) { } + public DynamicRangeAttribute(string minMemberName, float max) : base(minMemberName, max) { } + public DynamicRangeAttribute(string minMaxMemberName) : base(minMaxMemberName) { } - public string MinMemberName { get; } - public string MaxMemberName { get; } - public string MinMaxMemberName { get; } - - public DynamicRangeAttribute() : this(0f, 1f) { } - - public DynamicRangeAttribute(float min, float max) - { - MinFixed = min; - MaxFixed = max; - } - - public DynamicRangeAttribute(string minMemberName, string maxMemberName) : this() - { - MinMemberName = minMemberName; - MaxMemberName = maxMemberName; - } - - public DynamicRangeAttribute(float min, string maxMemberName) - { - MinFixed = min; - MaxFixed = 1; - MaxMemberName = maxMemberName; - } - - public DynamicRangeAttribute(string minMemberName, float max) - { - MinFixed = 0; - MaxFixed = max; - MinMemberName = minMemberName; - } - public DynamicRangeAttribute(string minMaxMemberName) : this() - { - MinMaxMemberName = minMaxMemberName; - } - } } \ No newline at end of file diff --git a/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta b/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta index bb20bf4..0f845e7 100644 --- a/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta +++ b/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta @@ -1,2 +1,2 @@ fileFormatVersion: 2 -guid: 347e65740a885e14ca4044800a6d4f1d \ No newline at end of file +guid: 360cd7f99f7103947806c4bc4f33803e \ No newline at end of file diff --git a/Runtime/Attributes/Decorators/MinMaxSliderAttribute.cs b/Runtime/Attributes/Decorators/MinMaxSliderAttribute.cs new file mode 100644 index 0000000..4fb32e6 --- /dev/null +++ b/Runtime/Attributes/Decorators/MinMaxSliderAttribute.cs @@ -0,0 +1,46 @@ +using System; +using System.Diagnostics; +using UnityEngine; + +namespace TriInspector +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] + [Conditional("UNITY_EDITOR")] + public class MinMaxSliderAttribute : PropertyAttribute + { + public float MinFixed { get; } + public float MaxFixed { get; } + + public string MinMemberName { get; } + public string MaxMemberName { get; } + public string MinMaxMemberName { get; } + + public MinMaxSliderAttribute() : this(0f, 1f) { } + public MinMaxSliderAttribute(float min, float max) + { + MinFixed = min; + MaxFixed = max; + } + public MinMaxSliderAttribute(string minMemberName, string maxMemberName) : this() + { + MinMemberName = minMemberName; + MaxMemberName = maxMemberName; + } + public MinMaxSliderAttribute(float min, string maxMemberName) + { + MinFixed = min; + MaxFixed = 1; + MaxMemberName = maxMemberName; + } + public MinMaxSliderAttribute(string minMemberName, float max) + { + MinFixed = 0; + MaxFixed = max; + MinMemberName = minMemberName; + } + public MinMaxSliderAttribute(string minMaxMemberName) : this() + { + MinMaxMemberName = minMaxMemberName; + } + } +} \ No newline at end of file diff --git a/Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs.meta b/Runtime/Attributes/Decorators/MinMaxSliderAttribute.cs.meta similarity index 100% rename from Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs.meta rename to Runtime/Attributes/Decorators/MinMaxSliderAttribute.cs.meta diff --git a/Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs b/Runtime/Attributes/Decorators/SliderAttribute.cs similarity index 66% rename from Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs rename to Runtime/Attributes/Decorators/SliderAttribute.cs index f0f3a55..df96a57 100644 --- a/Runtime/Attributes/Decorators/MinMaxRangeAttribute.cs +++ b/Runtime/Attributes/Decorators/SliderAttribute.cs @@ -6,7 +6,7 @@ namespace TriInspector { [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] [Conditional("UNITY_EDITOR")] - public class MinMaxRangeAttribute : PropertyAttribute + public class SliderAttribute : Attribute { public float MinFixed { get; } public float MaxFixed { get; } @@ -15,30 +15,34 @@ public class MinMaxRangeAttribute : PropertyAttribute public string MaxMemberName { get; } public string MinMaxMemberName { get; } - public MinMaxRangeAttribute() : this(0f, 1f) { } - public MinMaxRangeAttribute(float min, float max) + public SliderAttribute() : this(0f, 1f) { } + + public SliderAttribute(float min, float max) { MinFixed = min; MaxFixed = max; } - public MinMaxRangeAttribute(string minMemberName, string maxMemberName) : this() + + public SliderAttribute(string minMemberName, string maxMemberName) : this() { MinMemberName = minMemberName; MaxMemberName = maxMemberName; } - public MinMaxRangeAttribute(float min, string maxMemberName) + + public SliderAttribute(float min, string maxMemberName) { MinFixed = min; MaxFixed = 1; MaxMemberName = maxMemberName; } - public MinMaxRangeAttribute(string minMemberName, float max) + + public SliderAttribute(string minMemberName, float max) { MinFixed = 0; MaxFixed = max; MinMemberName = minMemberName; } - public MinMaxRangeAttribute(string minMaxMemberName) : this() + public SliderAttribute(string minMaxMemberName) : this() { MinMaxMemberName = minMaxMemberName; } diff --git a/Runtime/Attributes/Decorators/SliderAttribute.cs.meta b/Runtime/Attributes/Decorators/SliderAttribute.cs.meta new file mode 100644 index 0000000..77f30fb --- /dev/null +++ b/Runtime/Attributes/Decorators/SliderAttribute.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 34c7f819ef0eb924796a9601f7ad726f \ No newline at end of file From 538e39b8fbca13e19a35d8afeb21deb9f5505b30 Mon Sep 17 00:00:00 2001 From: Luanrobs Date: Sun, 19 Oct 2025 03:13:07 -0300 Subject: [PATCH 5/6] Refactor slider attributes and add validators Introduces attribute validators for SliderAttribute and MinMaxSliderAttribute, refactors drawer logic to use shared helpers, and adds auto-clamp support. Removes DynamicRangeAttribute in favor of unified SliderAttribute usage. Updates sample scripts and improves type handling and error reporting for slider decorators. --- Editor.Extras/Drawers/MinMaxSliderDrawer.cs | 169 ++++++------- Editor.Extras/Drawers/SliderDrawer.cs | 232 +++++++++++------- .../MinMaxSliderAttributeValidator.cs | 66 +++++ .../MinMaxSliderAttributeValidator.cs.meta | 2 + .../Validators/SliderAttributeValidator.cs | 52 ++++ .../SliderAttributeValidator.cs.meta | 2 + .../Decorators_MinMaxSliderSample.cs | 8 +- ...geSample.cs => Decorators_SliderSample.cs} | 13 +- ...s.meta => Decorators_SliderSample.cs.meta} | 0 Editor.Samples/TriSamplesWindow.cs | 27 +- Editor/Utilities/TriEditorGUI.MinMaxSlider.cs | 16 +- .../Decorators/DynamicRangeAttribute.cs | 12 - .../Decorators/DynamicRangeAttribute.cs.meta | 2 - .../Decorators/MinMaxSliderAttribute.cs | 23 +- .../Attributes/Decorators/SliderAttribute.cs | 25 +- 15 files changed, 399 insertions(+), 250 deletions(-) create mode 100644 Editor.Extras/Validators/MinMaxSliderAttributeValidator.cs create mode 100644 Editor.Extras/Validators/MinMaxSliderAttributeValidator.cs.meta create mode 100644 Editor.Extras/Validators/SliderAttributeValidator.cs create mode 100644 Editor.Extras/Validators/SliderAttributeValidator.cs.meta rename Editor.Samples/Decorators/{Decorators_SliderSampleOrDynamicRangeSample.cs => Decorators_SliderSample.cs} (57%) rename Editor.Samples/Decorators/{Decorators_SliderSampleOrDynamicRangeSample.cs.meta => Decorators_SliderSample.cs.meta} (100%) delete mode 100644 Runtime/Attributes/Decorators/DynamicRangeAttribute.cs delete mode 100644 Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta diff --git a/Editor.Extras/Drawers/MinMaxSliderDrawer.cs b/Editor.Extras/Drawers/MinMaxSliderDrawer.cs index 8ea989e..1d6e139 100644 --- a/Editor.Extras/Drawers/MinMaxSliderDrawer.cs +++ b/Editor.Extras/Drawers/MinMaxSliderDrawer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using TriInspector; using TriInspector.Drawers; using TriInspector.Resolvers; @@ -12,88 +13,30 @@ namespace TriInspector.Drawers { public class MinMaxSliderAttributeDrawer : TriAttributeDrawer { - private ValueResolver _minFloatResolver; - private ValueResolver _minIntResolver; - private ValueResolver _maxFloatResolver; - private ValueResolver _maxIntResolver; - private ValueResolver _minMaxVector2Resolver; - private ValueResolver _minMaxVector2IntResolver; + private MinMaxSliderAttributeHelpers.SliderResolvers _resolvers; - private bool _isVector2Int; public override TriExtensionInitializationResult Initialize(TriPropertyDefinition propertyDefinition) { - var fieldType = propertyDefinition.FieldType; - if (fieldType != typeof(Vector2) && fieldType != typeof(Vector2Int)) - { - return "[MinMaxRange] attribute can only be used on Vector2 or Vector2Int fields."; - } - _isVector2Int = fieldType == typeof(Vector2Int); - - if (!string.IsNullOrEmpty(Attribute.MinMaxMemberName)) - { - _minMaxVector2Resolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMaxMemberName); - if (_minMaxVector2Resolver.TryGetErrorString(out var vector2Error)) - { - _minMaxVector2Resolver = null; - _minMaxVector2IntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMaxMemberName); - if (_minMaxVector2IntResolver.TryGetErrorString(out var vector2IntError)) - { - return vector2IntError; - } - } - return TriExtensionInitializationResult.Ok; - } + _resolvers = MinMaxSliderAttributeHelpers.Initialize(Attribute, propertyDefinition, out var errorResult); - if (!string.IsNullOrEmpty(Attribute.MinMemberName)) + //MinMaxSliderAttributeValidator is expected to return an error result if initialization fails. + if (errorResult.IsError) { - _minFloatResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMemberName); - if (_minFloatResolver.TryGetErrorString(out _)) - { - _minFloatResolver = null; - _minIntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMemberName); - if (_minIntResolver.TryGetErrorString(out var error)) return error; - } - } - - if (!string.IsNullOrEmpty(Attribute.MaxMemberName)) - { - _maxFloatResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MaxMemberName); - if (_maxFloatResolver.TryGetErrorString(out _)) - { - _maxFloatResolver = null; - _maxIntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MaxMemberName); - if (_maxIntResolver.TryGetErrorString(out var error)) return error; - } + return TriExtensionInitializationResult.Skip; } - return TriExtensionInitializationResult.Ok; } - public override void OnGUI(Rect position, TriProperty property, TriElement next) { - float minLimit = _minMaxVector2Resolver?.GetValue(property, Vector2.zero).x ?? - _minMaxVector2IntResolver?.GetValue(property, Vector2Int.zero).x ?? - _minFloatResolver?.GetValue(property, Attribute.MinFixed) ?? - _minIntResolver?.GetValue(property, (int) Attribute.MinFixed) ?? - Attribute.MinFixed; - - float maxLimit = _minMaxVector2Resolver?.GetValue(property, Vector2.zero).y ?? - _minMaxVector2IntResolver?.GetValue(property, Vector2Int.zero).y ?? - _maxFloatResolver?.GetValue(property, Attribute.MaxFixed) ?? - _maxIntResolver?.GetValue(property, (int) Attribute.MaxFixed) ?? - Attribute.MaxFixed; - - if (minLimit > maxLimit) (minLimit, maxLimit) = (maxLimit, minLimit); + var (minLimit, maxLimit) = MinMaxSliderAttributeHelpers.GetLimits(property, Attribute, _resolvers); float xValue, yValue; - if (_isVector2Int) + if (property.FieldType == typeof(Vector2Int)) { var val = (Vector2Int) property.Value; xValue = val.x; yValue = val.y; - minLimit = Mathf.RoundToInt(minLimit); - maxLimit = Mathf.RoundToInt(maxLimit); } else { @@ -102,51 +45,87 @@ public override void OnGUI(Rect position, TriProperty property, TriElement next) yValue = val.y; } - float clampedX = xValue; - float clampedY = yValue; + if (Attribute.AutoClamp) + { + float clampedX = xValue; + float clampedY = yValue; - if (clampedX > clampedY) (clampedX, clampedY) = (clampedY, clampedX); - clampedX = Mathf.Clamp(clampedX, minLimit, clampedY); - clampedY = Mathf.Clamp(clampedY, clampedX, maxLimit); + if (clampedX > clampedY) (clampedX, clampedY) = (clampedY, clampedX); + clampedX = Mathf.Clamp(clampedX, (float)minLimit, clampedY); + clampedY = Mathf.Clamp(clampedY, clampedX, (float)maxLimit); - // If clamping changed the value, update the property immediately. - if (Math.Abs(clampedX - xValue) > 0.001f || Math.Abs(clampedY - yValue) > 0.001f) - { - xValue = clampedX; - yValue = clampedY; + const float epsilon = 1e-5f; + if (Math.Abs(clampedX - xValue) > epsilon || Math.Abs(clampedY - yValue) > epsilon) + { + xValue = clampedX; + yValue = clampedY; - if (_isVector2Int) - property.SetValue(new Vector2Int(Mathf.RoundToInt(xValue), Mathf.RoundToInt(yValue))); - else - property.SetValue(new Vector2(xValue, yValue)); + MinMaxSliderAttributeHelpers.SetValue(property, xValue, yValue); + } } var label = property.DisplayNameContent; - //EditorGUI.BeginChangeCheck(); var controlRect = EditorGUI.PrefixLabel(position, label); - TriEditorGUI.DrawMinMaxSlider(controlRect, ref xValue, ref yValue, minLimit, maxLimit); - + EditorGUI.BeginChangeCheck(); + TriEditorGUI.DrawMinMaxSlider(controlRect, ref xValue, ref yValue, (float)minLimit, (float)maxLimit); if (EditorGUI.EndChangeCheck()) { - // The slider itself ensures values are within the min/max limits. - // We just need to apply the final result. - if (_isVector2Int) - { - var newVec = new Vector2Int(Mathf.RoundToInt(xValue), Mathf.RoundToInt(yValue)); - property.SetValue(newVec); - } - else - { - var newVec = new Vector2(xValue, yValue); - property.SetValue(newVec); - } + MinMaxSliderAttributeHelpers.SetValue(property, xValue, yValue); } } - public override float GetHeight(float width, TriProperty property, TriElement next) { return EditorGUIUtility.singleLineHeight; } } + + internal static class MinMaxSliderAttributeHelpers + { + internal class SliderResolvers : SliderAttributeHelpers.SliderResolvers + { + internal SliderResolvers(ref HashSet errors, TriPropertyDefinition propertyDefinition, MinMaxSliderAttribute attribute) + : base(ref errors, propertyDefinition, attribute.MinMemberName, attribute.MaxMemberName, attribute.MinMaxMemberName) + { + } + } + public static SliderResolvers Initialize(MinMaxSliderAttribute attribute, + TriPropertyDefinition propertyDefinition, out TriExtensionInitializationResult errorResult) + { + var errors = new HashSet(); + + if (propertyDefinition.FieldType != typeof(Vector2) && propertyDefinition.FieldType != typeof(Vector2Int)) + { + errors.Add("[MinMaxRange] attribute can only be used on Vector2 or Vector2Int fields."); + } + + var resolvers = new SliderResolvers(ref errors, propertyDefinition, attribute); + + if (errors.Count > 0) + { + errorResult = string.Join(Environment.NewLine, errors); + return null; + } + + errorResult = TriExtensionInitializationResult.Ok; + return resolvers; + } + public static (double min, double max) GetLimits(TriProperty property, MinMaxSliderAttribute attribute, SliderResolvers resolvers) + { + return SliderAttributeHelpers.GetLimits(property, attribute.MinFixed, attribute.MaxFixed, resolvers); + } + public static void SetValue(TriProperty property, float x, float y) + { + if (property.ValueType == typeof(Vector2Int)) + { + var newVec = new Vector2Int(Mathf.RoundToInt(x), Mathf.RoundToInt(y)); + property.SetValue(newVec); + } + else + { + var newVec = new Vector2(x, y); + property.SetValue(newVec); + } + } + } } \ No newline at end of file diff --git a/Editor.Extras/Drawers/SliderDrawer.cs b/Editor.Extras/Drawers/SliderDrawer.cs index 94a98aa..6b62da0 100644 --- a/Editor.Extras/Drawers/SliderDrawer.cs +++ b/Editor.Extras/Drawers/SliderDrawer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using TriInspector; using TriInspector.Drawers; using TriInspector.Resolvers; @@ -11,134 +12,185 @@ namespace TriInspector.Drawers { public class SliderAttributeDrawer : TriAttributeDrawer { - private ValueResolver _minFloatResolver; - private ValueResolver _minIntResolver; - private ValueResolver _maxFloatResolver; - private ValueResolver _maxIntResolver; - private ValueResolver _minMaxVector2Resolver; - private ValueResolver _minMaxVector2IntResolver; + private SliderAttributeHelpers.SliderResolvers _resolvers; - private Type _valueType; - private bool IsNumericType(Type type) + public override TriExtensionInitializationResult Initialize(TriPropertyDefinition propertyDefinition) { - if (type == null) return false; - return typeof(IConvertible).IsAssignableFrom(type) && - type != typeof(string) && - type != typeof(bool) && - type != typeof(char); - } + _resolvers = SliderAttributeHelpers.Initialize(Attribute, propertyDefinition, out var errorResult); - public override TriExtensionInitializationResult Initialize(TriPropertyDefinition propertyDefinition) + //SliderAttributeValidator is expected to return an error result if initialization fails. + if (errorResult.IsError) + { + return TriExtensionInitializationResult.Skip; + } + return TriExtensionInitializationResult.Ok; + } + public override void OnGUI(Rect position, TriProperty property, TriElement next) { - _valueType = propertyDefinition.FieldType; - if (!IsNumericType(_valueType)) + var label = property.DisplayNameContent; + double currentValue; + try { - return "[Range] attribute can only be used on numeric fields (like int, float, double, etc.)."; + currentValue = Convert.ToDouble(property.Value); } + catch (Exception) + { + EditorGUI.LabelField(position, label.text, "Cannot convert value to a number."); + return; + } + + var (minLimit, maxLimit) = SliderAttributeHelpers.GetLimits(property, Attribute, _resolvers); - if (!string.IsNullOrEmpty(Attribute.MinMaxMemberName)) + if (Attribute.AutoClamp) { - _minMaxVector2Resolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMaxMemberName); - if (_minMaxVector2Resolver.TryGetErrorString(out var vector2Error)) + double clampedValue = Math.Clamp(currentValue, minLimit, maxLimit); + const double epsilon = 1e-9; + if (Math.Abs(clampedValue - currentValue) > epsilon) { - _minMaxVector2Resolver = null; - _minMaxVector2IntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMaxMemberName); - if (_minMaxVector2IntResolver.TryGetErrorString(out var vector2IntError)) - { - return vector2IntError; - } + property.SetValue(Convert.ChangeType(clampedValue, property.ValueType)); + currentValue = clampedValue; } - return TriExtensionInitializationResult.Ok; } - if (!string.IsNullOrEmpty(Attribute.MinMemberName)) + EditorGUI.BeginChangeCheck(); + float sliderValue = EditorGUI.Slider(position, label, (float) currentValue, (float) minLimit, (float) maxLimit); + if (EditorGUI.EndChangeCheck()) + { + var finalValue = Convert.ChangeType(sliderValue, property.ValueType); + property.SetValue(finalValue); + } + } + public override float GetHeight(float width, TriProperty property, TriElement next) + { + return EditorGUIUtility.singleLineHeight; + } + } + + internal static class SliderAttributeHelpers + { + internal class SliderResolvers + { + public ValueResolver minFloatResolver; + public ValueResolver minIntResolver; + public ValueResolver maxFloatResolver; + public ValueResolver maxIntResolver; + public ValueResolver minMaxVector2Resolver; + public ValueResolver minMaxVector2IntResolver; + + internal SliderResolvers(ref HashSet errors, TriPropertyDefinition propertyDefinition, SliderAttribute attribute) + : this(ref errors, propertyDefinition, attribute.MinMemberName, attribute.MaxMemberName, attribute.MinMaxMemberName) { - _minFloatResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMemberName); - if (_minFloatResolver.TryGetErrorString(out var floatError)) + } + protected SliderResolvers(ref HashSet errors, TriPropertyDefinition propertyDefinition, string minMemberName, string maxMemberName, string minMaxMemberName) + { + var resolverErrors = new HashSet(); + + bool hasMinMaxMember = !string.IsNullOrEmpty(minMaxMemberName); + if (hasMinMaxMember) { - _minFloatResolver = null; - _minIntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MinMemberName); - if (_minIntResolver.TryGetErrorString(out var intError)) + minMaxVector2Resolver = ValueResolver.Resolve(propertyDefinition, minMaxMemberName); + if (minMaxVector2Resolver.TryGetErrorString(out var vector2Error)) { - return intError; + minMaxVector2Resolver = null; + minMaxVector2IntResolver = ValueResolver.Resolve(propertyDefinition, minMaxMemberName); + if (minMaxVector2IntResolver.TryGetErrorString(out var vector2IntError)) + { + errors.Add(vector2Error); + errors.Add(vector2IntError); + } } } - } - if (!string.IsNullOrEmpty(Attribute.MaxMemberName)) - { - _maxFloatResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MaxMemberName); - if (_maxFloatResolver.TryGetErrorString(out var floatError)) + bool hasMinMember = !string.IsNullOrEmpty(minMemberName); + if (hasMinMember && !hasMinMaxMember) { - _maxFloatResolver = null; - _maxIntResolver = ValueResolver.Resolve(propertyDefinition, Attribute.MaxMemberName); - if (_maxIntResolver.TryGetErrorString(out var intError)) + minFloatResolver = ValueResolver.Resolve(propertyDefinition, minMemberName); + if (minFloatResolver.TryGetErrorString(out var floatError)) { - return intError; + minFloatResolver = null; + minIntResolver = ValueResolver.Resolve(propertyDefinition, minMemberName); + if (minIntResolver.TryGetErrorString(out var intError)) + { + errors.Add(floatError); + errors.Add(intError); + } } } - } - return TriExtensionInitializationResult.Ok; + bool hasMaxMember = !string.IsNullOrEmpty(maxMemberName); + if (hasMaxMember && !hasMinMaxMember) + { + maxFloatResolver = ValueResolver.Resolve(propertyDefinition, maxMemberName); + if (maxFloatResolver.TryGetErrorString(out var floatError)) + { + maxFloatResolver = null; + maxIntResolver = ValueResolver.Resolve(propertyDefinition, maxMemberName); + if (maxIntResolver.TryGetErrorString(out var intError)) + { + errors.Add(floatError); + errors.Add(intError); + } + } + } + } } - - public override void OnGUI(Rect position, TriProperty property, TriElement next) + private static bool IsNumericType(Type type) { - double minLimit = _minMaxVector2Resolver?.GetValue(property, Vector2.zero).x ?? - _minMaxVector2IntResolver?.GetValue(property, Vector2Int.zero).x ?? - _minFloatResolver?.GetValue(property, Attribute.MinFixed) ?? - _minIntResolver?.GetValue(property, (int) Attribute.MinFixed) ?? - Attribute.MinFixed; - - double maxLimit = _minMaxVector2Resolver?.GetValue(property, Vector2.zero).y ?? - _minMaxVector2IntResolver?.GetValue(property, Vector2Int.zero).y ?? - _maxFloatResolver?.GetValue(property, Attribute.MaxFixed) ?? - _maxIntResolver?.GetValue(property, (int) Attribute.MaxFixed) ?? - Attribute.MaxFixed; - - if (minLimit > maxLimit) (minLimit, maxLimit) = (maxLimit, minLimit); - - var label = property.DisplayNameContent; + if (type == null) return false; + return typeof(IConvertible).IsAssignableFrom(type) && + type != typeof(string) && + type != typeof(bool) && + type != typeof(char); + } + public static SliderResolvers Initialize(SliderAttribute attribute, + TriPropertyDefinition propertyDefinition, out TriExtensionInitializationResult errorResult) + { + var errors = new HashSet(); - double currentValue; - try + if (!IsNumericType(propertyDefinition.FieldType)) { - currentValue = Convert.ToDouble(property.Value); - } - catch (Exception) - { - EditorGUI.LabelField(position, label.text, "Cannot convert value to a number."); - return; + errors.Add("[Slider] attribute can only be used on numeric fields (like int, float, double, etc.)."); } - if (property.FieldType == typeof(int)) - { - minLimit = Mathf.RoundToInt((float)minLimit); - maxLimit = Mathf.RoundToInt((float)maxLimit); - } + var resolvers = new SliderResolvers(ref errors, propertyDefinition, attribute); - // If clamping changed the value, update the property immediately. - double clampedValue = Math.Clamp(currentValue, minLimit, maxLimit); - if (Math.Abs(clampedValue - currentValue) > double.Epsilon) + if (errors.Count > 0) { - property.SetValue(Convert.ChangeType(clampedValue, _valueType)); - currentValue = clampedValue; + errorResult = string.Join(Environment.NewLine, errors); + return null; } - EditorGUI.BeginChangeCheck(); - float sliderValue = EditorGUI.Slider(position, label, (float) currentValue, (float) minLimit, (float) maxLimit); + errorResult = TriExtensionInitializationResult.Ok; + return resolvers; + } + public static (double min, double max) GetLimits(TriProperty property, SliderAttribute attribute, SliderResolvers resolvers) + { + return GetLimits(property, attribute.MinFixed, attribute.MaxFixed, resolvers); + } + public static (double min, double max) GetLimits(TriProperty property, float minFixed, float maxFixed, SliderResolvers resolvers) + { + double minLimit = resolvers.minMaxVector2Resolver?.GetValue(property, Vector2.zero).x ?? + resolvers.minMaxVector2IntResolver?.GetValue(property, Vector2Int.zero).x ?? + resolvers.minFloatResolver?.GetValue(property, minFixed) ?? + resolvers.minIntResolver?.GetValue(property, (int) minFixed) ?? + minFixed; + + double maxLimit = resolvers.minMaxVector2Resolver?.GetValue(property, Vector2.zero).y ?? + resolvers.minMaxVector2IntResolver?.GetValue(property, Vector2Int.zero).y ?? + resolvers.maxFloatResolver?.GetValue(property, maxFixed) ?? + resolvers.maxIntResolver?.GetValue(property, (int) maxFixed) ?? + maxFixed; - if (EditorGUI.EndChangeCheck()) + if (minLimit > maxLimit) (minLimit, maxLimit) = (maxLimit, minLimit); + + if (property.FieldType == typeof(int) || property.FieldType == typeof(Vector2Int)) { - object finalValue = Convert.ChangeType(sliderValue, _valueType); - property.SetValue(finalValue); + minLimit = Mathf.RoundToInt((float) minLimit); + maxLimit = Mathf.RoundToInt((float) maxLimit); } - } - public override float GetHeight(float width, TriProperty property, TriElement next) - { - return EditorGUIUtility.singleLineHeight; + return (minLimit, maxLimit); } } } \ No newline at end of file diff --git a/Editor.Extras/Validators/MinMaxSliderAttributeValidator.cs b/Editor.Extras/Validators/MinMaxSliderAttributeValidator.cs new file mode 100644 index 0000000..59ba3c9 --- /dev/null +++ b/Editor.Extras/Validators/MinMaxSliderAttributeValidator.cs @@ -0,0 +1,66 @@ +using System; +using TriInspector; +using TriInspector.Drawers; +using TriInspector.Validators; +using UnityEngine; + +[assembly: RegisterTriAttributeValidator(typeof(MinMaxSliderAttributeValidator), ApplyOnArrayElement = true)] + +namespace TriInspector.Validators +{ + public class MinMaxSliderAttributeValidator : TriAttributeValidator + { + private MinMaxSliderAttributeHelpers.SliderResolvers _resolvers; + + public override TriExtensionInitializationResult Initialize(TriPropertyDefinition propertyDefinition) + { + _resolvers = MinMaxSliderAttributeHelpers.Initialize(Attribute, propertyDefinition, out var errorResult); + return errorResult; + } + + public override TriValidationResult Validate(TriProperty property) + { + if (Attribute.AutoClamp) + { + return TriValidationResult.Valid; + } + + var (minLimit, maxLimit) = MinMaxSliderAttributeHelpers.GetLimits(property, Attribute, _resolvers); + + float xValue, yValue; + if (property.ValueType == typeof(Vector2Int)) + { + var val = (Vector2Int) property.Value; + xValue = val.x; + yValue = val.y; + } + else + { + var val = (Vector2) property.Value; + xValue = val.x; + yValue = val.y; + } + + bool isInvalid = xValue < minLimit || yValue > maxLimit || xValue > yValue; + + if (isInvalid) + { + return TriValidationResult.Warning($"Value is out of range [{minLimit:0.##}, {maxLimit:0.##}].") + .WithFix(() => + { + float clampedX = xValue; + float clampedY = yValue; + + if (clampedX > clampedY) (clampedX, clampedY) = (clampedY, clampedX); + + clampedX = Mathf.Clamp(clampedX, (float) minLimit, clampedY); + clampedY = Mathf.Clamp(clampedY, clampedX, (float) maxLimit); + + MinMaxSliderAttributeHelpers.SetValue(property, clampedX, clampedY); + }, "Clamp"); + } + + return TriValidationResult.Valid; + } + } +} \ No newline at end of file diff --git a/Editor.Extras/Validators/MinMaxSliderAttributeValidator.cs.meta b/Editor.Extras/Validators/MinMaxSliderAttributeValidator.cs.meta new file mode 100644 index 0000000..46b9e12 --- /dev/null +++ b/Editor.Extras/Validators/MinMaxSliderAttributeValidator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b5ac6cbd1c38275489141d862ed1d5f7 \ No newline at end of file diff --git a/Editor.Extras/Validators/SliderAttributeValidator.cs b/Editor.Extras/Validators/SliderAttributeValidator.cs new file mode 100644 index 0000000..5269d12 --- /dev/null +++ b/Editor.Extras/Validators/SliderAttributeValidator.cs @@ -0,0 +1,52 @@ +using System; +using TriInspector; +using TriInspector.Drawers; +using TriInspector.Validators; + +[assembly: RegisterTriAttributeValidator(typeof(SliderAttributeValidator), ApplyOnArrayElement = true)] + +namespace TriInspector.Validators +{ + public class SliderAttributeValidator : TriAttributeValidator + { + private SliderAttributeHelpers.SliderResolvers _resolvers; + + public override TriExtensionInitializationResult Initialize(TriPropertyDefinition propertyDefinition) + { + _resolvers = SliderAttributeHelpers.Initialize(Attribute, propertyDefinition, out var errorResult); + return errorResult; + } + + public override TriValidationResult Validate(TriProperty property) + { + if (Attribute.AutoClamp) + { + return TriValidationResult.Valid; + } + + var (minLimit, maxLimit) = SliderAttributeHelpers.GetLimits(property, Attribute, _resolvers); + + double currentValue; + try + { + currentValue = Convert.ToDouble(property.Value); + } + catch (Exception) + { + return TriValidationResult.Error("Cannot convert value to a number."); + } + + if (currentValue < minLimit || currentValue > maxLimit) + { + return TriValidationResult.Warning($"Value is out of range [{minLimit:0.##}, {maxLimit:0.##}].") + .WithFix(() => + { + double clampedValue = Math.Clamp(currentValue, minLimit, maxLimit); + property.SetValue(Convert.ChangeType(clampedValue, property.ValueType)); + }, "Clamp"); + } + + return TriValidationResult.Valid; + } + } +} \ No newline at end of file diff --git a/Editor.Extras/Validators/SliderAttributeValidator.cs.meta b/Editor.Extras/Validators/SliderAttributeValidator.cs.meta new file mode 100644 index 0000000..5761824 --- /dev/null +++ b/Editor.Extras/Validators/SliderAttributeValidator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 46f18c386c9cc0644b76addcc9b960a0 \ No newline at end of file diff --git a/Editor.Samples/Decorators/Decorators_MinMaxSliderSample.cs b/Editor.Samples/Decorators/Decorators_MinMaxSliderSample.cs index d232269..3d622e0 100644 --- a/Editor.Samples/Decorators/Decorators_MinMaxSliderSample.cs +++ b/Editor.Samples/Decorators/Decorators_MinMaxSliderSample.cs @@ -12,11 +12,15 @@ public class Decorators_MinMaxSliderSample : ScriptableObject [MinMaxSlider(-20, nameof(GetMax))] public Vector2 dynamicFloatMaxSlider = new(-7.7f, -1.7f); + public Vector2 minMax = new(-10, 10); + [MinMaxSlider(nameof(minMax))] - public Vector2Int dynamicFloatMinMaxSlider = new(0, 4); + public Vector2 dynamicFloatMinMaxSlider = new(0, 4); + + [MinMaxSlider(nameof(minMax), autoClamp: true)] + public Vector2Int dynamicIntMinMaxSliderClamped = new(2, 6); - public Vector2 minMax = new(-10, 10); private int _min = -20; private int _max = 20; public float GetMax() => 10; diff --git a/Editor.Samples/Decorators/Decorators_SliderSampleOrDynamicRangeSample.cs b/Editor.Samples/Decorators/Decorators_SliderSample.cs similarity index 57% rename from Editor.Samples/Decorators/Decorators_SliderSampleOrDynamicRangeSample.cs rename to Editor.Samples/Decorators/Decorators_SliderSample.cs index 76e35a9..4b85cea 100644 --- a/Editor.Samples/Decorators/Decorators_SliderSampleOrDynamicRangeSample.cs +++ b/Editor.Samples/Decorators/Decorators_SliderSample.cs @@ -1,21 +1,22 @@ using UnityEngine; using TriInspector; -public class Decorators_SliderSampleOrDynamicRangeSample : ScriptableObject +public class Decorators_SliderSample : ScriptableObject { [Slider(nameof(_min), nameof(_max))] - public int dynamicIntSlider = -5; + public int dynamicIntSlider = -6; [Slider(0, nameof(GetMax))] public float dynamicMaxFloatSlider = 4.6f; + public Vector2 minMax = new(-10, 10); + [Slider(nameof(minMax))] - public float dynamicFloatSlider = 1.05f; + public float dynamicFloatSlider = 1.83f; - [DynamicRange(nameof(minMax))] - public float dynamicFloatRange = 1.05f; + [Slider(nameof(minMax), autoClamp: true)] + public int dynamicIntSliderClamped = 4; - public Vector2 minMax = new(-10, 10); private int _min = -20; private int _max = 20; public float GetMax() => 10; diff --git a/Editor.Samples/Decorators/Decorators_SliderSampleOrDynamicRangeSample.cs.meta b/Editor.Samples/Decorators/Decorators_SliderSample.cs.meta similarity index 100% rename from Editor.Samples/Decorators/Decorators_SliderSampleOrDynamicRangeSample.cs.meta rename to Editor.Samples/Decorators/Decorators_SliderSample.cs.meta diff --git a/Editor.Samples/TriSamplesWindow.cs b/Editor.Samples/TriSamplesWindow.cs index 64e24ba..18f3e06 100644 --- a/Editor.Samples/TriSamplesWindow.cs +++ b/Editor.Samples/TriSamplesWindow.cs @@ -163,34 +163,19 @@ private void ChangeCurrentSample(Type type) private static string GetTypeNiceName(Type type) { var name = type.Name; - const string suffix = "Sample"; - // 1. Remove the prefix (e.g., "Decorators_") if (name.Contains('_')) { var index = name.IndexOf('_'); name = name.Substring(index + 1); } - // 2. Define the delimiter - var delimiter = $"{suffix}Or"; // "SampleOr" - - // 3. Split by "SampleOr" - // "SliderSampleOrDynamicRangeSample" -> ["Slider", "DynamicRangeSample"] - // "SliderSample" -> ["SliderSample"] - var parts = name.Split(new[] { delimiter }, StringSplitOptions.None); - - // 4. Clean the "Sample" suffix from EACH part - var cleanedParts = parts.Select(part => - part.EndsWith(suffix) - ? part.Remove(part.Length - suffix.Length) - : part - ); - - // 5. Join with "/" - // ["Slider", "DynamicRange"] -> "Slider/DynamicRange" - // ["Slider"] -> "Slider" - return string.Join("/", cleanedParts); + if (name.EndsWith("Sample")) + { + name = name.Remove(name.Length - "Sample".Length); + } + + return name; } private class MenuTree : TreeView diff --git a/Editor/Utilities/TriEditorGUI.MinMaxSlider.cs b/Editor/Utilities/TriEditorGUI.MinMaxSlider.cs index 8f6399b..1e2c3dc 100644 --- a/Editor/Utilities/TriEditorGUI.MinMaxSlider.cs +++ b/Editor/Utilities/TriEditorGUI.MinMaxSlider.cs @@ -1,3 +1,18 @@ +// Copyright (c) 2018-2022 A. R. (arimger) +// +// This code was originally sourced from: +// https://github.com/arimger/Unity-Editor-Toolbox/blob/master/Assets/Editor%20Toolbox/Editor/ToolboxEditorGui.cs +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + using UnityEditor; using UnityEngine; @@ -18,7 +33,6 @@ public static void DrawMinMaxSlider(Rect rect, ref float xValue, ref float yValu maxFieldRect.xMin - spacing, rect.yMax); - EditorGUI.BeginChangeCheck(); xValue = EditorGUI.FloatField(minFieldRect, xValue); yValue = EditorGUI.FloatField(maxFieldRect, yValue); EditorGUI.MinMaxSlider(sliderRect, ref xValue, ref yValue, minValue, maxValue); diff --git a/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs b/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs deleted file mode 100644 index 763e5d1..0000000 --- a/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -using TriInspector; - -public class DynamicRangeAttribute : SliderAttribute -{ - public DynamicRangeAttribute() : base() { } - public DynamicRangeAttribute(float min, float max) : base(min, max) { } - public DynamicRangeAttribute(string minMemberName, string maxMemberName) : base(minMemberName, maxMemberName) { } - public DynamicRangeAttribute(float min, string maxMemberName) : base(min, maxMemberName) { } - public DynamicRangeAttribute(string minMemberName, float max) : base(minMemberName, max) { } - public DynamicRangeAttribute(string minMaxMemberName) : base(minMaxMemberName) { } - -} \ No newline at end of file diff --git a/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta b/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta deleted file mode 100644 index 0f845e7..0000000 --- a/Runtime/Attributes/Decorators/DynamicRangeAttribute.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 360cd7f99f7103947806c4bc4f33803e \ No newline at end of file diff --git a/Runtime/Attributes/Decorators/MinMaxSliderAttribute.cs b/Runtime/Attributes/Decorators/MinMaxSliderAttribute.cs index 4fb32e6..4a49c09 100644 --- a/Runtime/Attributes/Decorators/MinMaxSliderAttribute.cs +++ b/Runtime/Attributes/Decorators/MinMaxSliderAttribute.cs @@ -8,39 +8,44 @@ namespace TriInspector [Conditional("UNITY_EDITOR")] public class MinMaxSliderAttribute : PropertyAttribute { - public float MinFixed { get; } - public float MaxFixed { get; } + public float MinFixed { get; } = 0f; + public float MaxFixed { get; } = 1f; public string MinMemberName { get; } public string MaxMemberName { get; } public string MinMaxMemberName { get; } - public MinMaxSliderAttribute() : this(0f, 1f) { } + public bool AutoClamp { get; } + + + public MinMaxSliderAttribute() { } public MinMaxSliderAttribute(float min, float max) { MinFixed = min; MaxFixed = max; } - public MinMaxSliderAttribute(string minMemberName, string maxMemberName) : this() + public MinMaxSliderAttribute(string minMemberName, string maxMemberName, bool autoClamp = false) : this() { MinMemberName = minMemberName; MaxMemberName = maxMemberName; + AutoClamp = autoClamp; } - public MinMaxSliderAttribute(float min, string maxMemberName) + public MinMaxSliderAttribute(float min, string maxMemberName, bool autoClamp = false) { MinFixed = min; - MaxFixed = 1; MaxMemberName = maxMemberName; + AutoClamp = autoClamp; } - public MinMaxSliderAttribute(string minMemberName, float max) + public MinMaxSliderAttribute(string minMemberName, float max, bool autoClamp = false) { - MinFixed = 0; MaxFixed = max; MinMemberName = minMemberName; + AutoClamp = autoClamp; } - public MinMaxSliderAttribute(string minMaxMemberName) : this() + public MinMaxSliderAttribute(string minMaxMemberName, bool autoClamp = false) : this() { MinMaxMemberName = minMaxMemberName; + AutoClamp = autoClamp; } } } \ No newline at end of file diff --git a/Runtime/Attributes/Decorators/SliderAttribute.cs b/Runtime/Attributes/Decorators/SliderAttribute.cs index df96a57..5242a15 100644 --- a/Runtime/Attributes/Decorators/SliderAttribute.cs +++ b/Runtime/Attributes/Decorators/SliderAttribute.cs @@ -8,43 +8,44 @@ namespace TriInspector [Conditional("UNITY_EDITOR")] public class SliderAttribute : Attribute { - public float MinFixed { get; } - public float MaxFixed { get; } + public float MinFixed { get; } = 0f; + public float MaxFixed { get; } = 1f; public string MinMemberName { get; } public string MaxMemberName { get; } public string MinMaxMemberName { get; } - public SliderAttribute() : this(0f, 1f) { } + public bool AutoClamp { get; } + + public SliderAttribute() { } public SliderAttribute(float min, float max) { MinFixed = min; MaxFixed = max; } - - public SliderAttribute(string minMemberName, string maxMemberName) : this() + public SliderAttribute(string minMemberName, string maxMemberName, bool autoClamp = false) : this() { MinMemberName = minMemberName; MaxMemberName = maxMemberName; + AutoClamp = autoClamp; } - - public SliderAttribute(float min, string maxMemberName) + public SliderAttribute(float min, string maxMemberName, bool autoClamp = false) { MinFixed = min; - MaxFixed = 1; MaxMemberName = maxMemberName; + AutoClamp = autoClamp; } - - public SliderAttribute(string minMemberName, float max) + public SliderAttribute(string minMemberName, float max, bool autoClamp = false) { - MinFixed = 0; MaxFixed = max; MinMemberName = minMemberName; + AutoClamp = autoClamp; } - public SliderAttribute(string minMaxMemberName) : this() + public SliderAttribute(string minMaxMemberName, bool autoClamp = false) : this() { MinMaxMemberName = minMaxMemberName; + AutoClamp = autoClamp; } } } \ No newline at end of file From 6be5037d40169e880aa9bb9de1531648bfd6c314 Mon Sep 17 00:00:00 2001 From: Luanrobs Date: Sun, 19 Oct 2025 03:19:12 -0300 Subject: [PATCH 6/6] Revise slider examples and add clamped variants Updated slider and dynamic range examples in README.md with new values and added clamped sliders. --- README.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5e9595c..dfee2e6 100644 --- a/README.md +++ b/README.md @@ -251,24 +251,25 @@ private IEnumerable> GetVectorValues() } ``` -#### Slider/DynamicRange +#### Slider -![Slider/DynamicRange](https://github.com/user-attachments/assets/9ad79364-715e-439c-8772-3a4b5b1aea5e) +![Slider](https://github.com/user-attachments/assets/9ad79364-715e-439c-8772-3a4b5b1aea5e) ```csharp [Slider(nameof(_min), nameof(_max))] -public int dynamicIntSlider = -5; +public int dynamicIntSlider = -6; [Slider(0, nameof(GetMax))] public float dynamicMaxFloatSlider = 4.6f; +public Vector2 minMax = new(-10, 10); + [Slider(nameof(minMax))] -public float dynamicFloatSlider = 1.05f; +public float dynamicFloatSlider = 1.83f; -[DynamicRange(nameof(minMax))] -public float dynamicFloatRange = 1.05f; +[Slider(nameof(minMax), autoClamp: true)] +public int dynamicIntSliderClamped = 4; -public Vector2 minMax = new(-10, 10); private int _min = -20; private int _max = 20; public float GetMax() => 10; @@ -297,19 +298,24 @@ public Material mat; ```csharp [MinMaxSlider(0f, 10f)] +[MinMaxSlider(0f, 10f)] public Vector2 fixedMinMaxSlider = new(2f, 4f); [MinMaxSlider(nameof(_min), nameof(_max))] public Vector2Int dynamicIntMinMaxSlider = new(-8, 0); [MinMaxSlider(-20, nameof(GetMax))] -public Vector2 dynamicFloatMaxRange = new(-7.7f, -1.7f); +public Vector2 dynamicFloatMaxSlider = new(-7.7f, -1.7f); + +public Vector2 minMax = new(-10, 10); [MinMaxSlider(nameof(minMax))] -public Vector2Int dynamicFloatMinMaxSlider = new(0, 4); +public Vector2 dynamicFloatMinMaxSlider = new(0, 4); + +[MinMaxSlider(nameof(minMax), autoClamp: true)] +public Vector2Int dynamicIntMinMaxSliderClamped = new(2, 6); -public Vector2 minMax = new(-10, 10); private int _min = -20; private int _max = 20; public float GetMax() => 10;