Skip to content

Conversation

Luanrobs
Copy link

@Luanrobs Luanrobs commented Oct 17, 2025

This pull request introduces two new attributes, Slider or DynamicRange and MinMaxSlider, providing dynamic slider controls in the inspector. This contribution directly addresses a feature request from Discussion #65.

Changes:

  • Added SliderAttribute and its corresponding drawer class.
  • Added MinMaxSliderAttribute and its corresponding drawer class.
  • Added Decorators_SliderSampleOrDynamicRangeSample ScriptableObject to demonstrate usage.
  • Added Decorators_MinMaxSliderSample ScriptableObject to demonstrate usage.
  • Modified: TriSamplesWindow GetTypeNiceName(Type type) to support more than one name: Slider/DynamicRange separated by SampleOr
  • Updated README.md with examples and screenshots for both new attributes.

Impact:

  • Developers can now create sliders where the min/max limits are defined by other members (fields, properties, or methods), which is not possible with Unity's native [Range] attribute.
  • Slider/DynamicRange provides a flexible single-value slider for int and float types.
  • MinMaxSlider provides an intuitive dual-handle slider for Vector2 and Vector2Int types, perfect for defining ranges.
  • Improves workflow for tool development and creating more complex, interconnected inspector interfaces.

Slider/DynamicRange

Slider/DynamicRange

[Slider(nameof(_min), nameof(_max))]
public int dynamicIntRange = -5;

[Slider(0, nameof(GetMax))]
public float dynamicMaxFloatRange = 4.6f;

[Slider(nameof(minMax))]
public float dynamicFloatRange = 1.05f;
or
[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;

Note: [DynamicRange] is included as an alias for [Slider]. This is done for convention, making it easier for developers who may be accustomed to the DynamicRange name from other inspector libraries. Both attributes function identically.

MinMaxSlider

MinMaxSlider

[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);

[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;

Testing:

  • Verified that sliders render correctly for int, float, double, Vector2, and Vector2Int fields.
  • Confirmed that both attributes render and function correctly when used on elements inside lists and arrays.
  • Confirmed that dynamic value resolution works correctly from public/private fields, properties, and methods.
  • Ensured the examples in the sample scripts and README.md work as intended and are easy to understand.
  • Verified that the current value is automatically clamped if the dynamic min/max boundaries change and the value falls outside the new valid range.
  • Confirmed that the sliders render and function correctly even if the resolved min limit becomes greater than the max limit.
  • Tested and confirmed compatibility with other Tri-Inspector attributes, such as [GUIColor] and [OnValueChanged].
image
using System;
using System.Collections.Generic;
using TriInspector;
using UnityEngine;

// Add this script to an empty GameObject in a test scene.
public class AdvancedRangeTest : MonoBehaviour
{
    // ======================================================================
    // 1. BASIC RENDERING TESTS (Your original examples)
    // ======================================================================
    [Header("1. BASIC RENDERING TESTS")]
    [InfoBox("Verify that all sliders render correctly.")]

    [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);

    [MinMaxSlider(nameof(minMax))]
    public Vector2Int dynamicFloatMinMaxSlider = new(0, 4);

    [Slider(nameof(_min), nameof(_max))]
    public int dynamicIntRange = -5;

    [Slider(0, nameof(GetMax))]
    public float dynamicMaxFloatRange = 4.6f;

    [Slider(nameof(minMax))]
    public float dynamicFloatRange = 1.05f;

    // Support members for the basic tests
    public Vector2 minMax = new(-10, 10);
    private int _min = -20;
    private int _max = 20;
    public float GetMax() => 10;


    // ======================================================================
    // 2. AUTO-CLAMPING TESTS
    // ======================================================================
    [Header("2. AUTO-CLAMPING TESTS")]
    [InfoBox("Change the 'Clamp Min/Max' limits below and watch the values " +
             "get clamped inside the new range.")]

    public float clampMin = 0;
    public float clampMax = 10;

    // Starts OUTSIDE the range (15) and should be clamped to 10
    [Slider(nameof(clampMin), nameof(clampMax))]
    public float valueToClamp = 15;

    // Starts PARTIALLY OUTSIDE the range (-5, 15) and should be clamped to (0, 10)
    [MinMaxSlider(nameof(clampMin), nameof(clampMax))]
    public Vector2 minMaxValueToClamp = new Vector2(-5, 15);


    // ======================================================================
    // 3. INVERTED LIMITS TESTS
    // ======================================================================
    [Header("3. INVERTED LIMITS TESTS")]
    [InfoBox("These sliders should render and function correctly, even with min > max.")]

    // Static inverted test
    [MinMaxSlider(10f, 0f)]
    public Vector2 invertedStaticMinMax = new(7, 3);

    // Dynamic inverted test
    public float invertedMin = 5;
    public float invertedMax = -5;
    [Slider(nameof(invertedMin), nameof(invertedMax))]
    public float invertedSlider = 0;


    // ======================================================================
    // 4. ATTRIBUTE COMPATIBILITY TESTS
    // ======================================================================
    [Header("4. ATTRIBUTE COMPATIBILITY TESTS")]
    [InfoBox("Verify that it works with other attributes (GUIColor, OnValueChanged, etc.).")]

    [GUIColor(0.5f, 1f, 0.5f)] // Should be green
    [Slider(0, 10)]
    public float coloredSlider = 5;

    [OnValueChanged(nameof(LogValueChange))] // Should log to console on change
    [MinMaxSlider(0, 100)]
    public Vector2 callbackMinMax = new(10, 20);

    private void LogValueChange()
    {
        Debug.Log("OnValueChanged triggered! New value: " + callbackMinMax);
    }


    // ======================================================================
    // 5. LISTS & ARRAYS TESTS
    // ======================================================================
    [Header("5. LISTS & ARRAYS TESTS")]
    [InfoBox("Verify that sliders work inside list elements.")]

    public List<TestElement> testList;

    [System.Serializable]
    public class TestElement
    {
        public string elementName = "Element";

        // MinMaxSlider test inside list
        [MinMaxSlider(0, 100)]
        public Vector2 listMinMax = new(10, 90);

        // Slider test that reads limits from WITHIN the element itself
        public float elementMin = 0;
        public float elementMax = 10;
        [Slider(nameof(elementMin), nameof(elementMax))]
        public float listSlider = 5;
    }


    // ======================================================================
    // 6. ERROR HANDLING TESTS
    // ======================================================================
    [Header("6. ERROR HANDLING TESTS")]
    [InfoBox("These fields should display a clear error message in the Inspector.")]
    public string stringMember = "I am a string";
    [MinMaxSlider(nameof(stringMember), 10)]
    public Vector2 badMemberType;

    [Slider("NonExistentMember", 10)]
    public float badMemberName;

}

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.
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.
@Luanrobs Luanrobs changed the title Adds DynamicRange and MinMaxRange attributes Adds DynamicRange and MinMaxSlider attributes Oct 17, 2025
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.
@Luanrobs Luanrobs changed the title Adds DynamicRange and MinMaxSlider attributes Adds Slider/DynamicRange and MinMaxSlider attributes Oct 17, 2025
@vanifatovvlad
Copy link
Contributor

Hi. Thanks for your contribution!

I found a few issues in PR:

  1. It seems that Slider and DynamicRange work the same way, so I think the DynamicRange attribute is unnecessary and need to be removed.
  2. TriEditorGUI.DrawMinMaxSlider calls EditorGUI.BeginChangeCheck(), but does not call the completion method which is counterintuitive. This call should be moved to MinMaxSliderDrawer.
  3. Attribute drawers automatically clamps the value. I think it would be more appropriate to create a validator that would error when a value is out of range. The validator could also provide a fix button. However, changing values ​​without explicit user action is not recommended. It would be great to fix this, but it's optional. I'm ready to merge the PR without this fix

@Luanrobs
Copy link
Author

Hi! Thank you very much for the detailed feedback, your observations were spot-on and I completely agree with the suggested changes.

I will apply the fixes today.

Before implementing the third point, I just wanted to ask a quick question to better align with your vision:

Do you think it would be a good idea to allow optional automatic clamping, disabled by default?
For example:

[Slider(nameof(min), nameof(max), autoClamp: true)]
public float slider;

Or, since this is a decorator, do you think it’s more appropriate to never modify the value directly and instead always rely on a validator/fix button approach?

Thanks again! I'll wait for your opinion on this before finalizing the implementation

@vanifatovvlad
Copy link
Contributor

Yes, the optional ability to enable automatic clamping (disabled by default) would be a great solution

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.
@Luanrobs Luanrobs changed the title Adds Slider/DynamicRange and MinMaxSlider attributes Adds Slider and MinMaxSlider attributes Oct 19, 2025
Updated slider and dynamic range examples in README.md with new values and added clamped sliders.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants