Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
MinMaxSlider for Unity
// https://frarees.github.io/default-gist-license
using System;
using UnityEngine;
[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public class MinMaxSliderAttribute : PropertyAttribute
{
public float Min { get; set; }
public float Max { get; set; }
public bool DataFields { get; set; } = true;
public bool FlexibleFields { get; set; } = true;
public bool Bound { get; set; } = true;
public bool Round { get; set; } = true;
public MinMaxSliderAttribute() : this(0, 1)
{
}
public MinMaxSliderAttribute(float min, float max)
{
Min = min;
Max = max;
}
}
// https://frarees.github.io/default-gist-license
using UnityEngine;
using UnityEditor;
[CustomPropertyDrawer(typeof(MinMaxSliderAttribute))]
internal class MinMaxSliderDrawer : PropertyDrawer
{
private const string kVectorMinName = "x";
private const string kVectorMaxName = "y";
private const float kFloatFieldWidth = 16f;
private const float kSpacing = 2f;
private const float kRoundingValue = 100f;
private static readonly int controlHash = "Foldout".GetHashCode();
private static readonly GUIContent unsupported = EditorGUIUtility.TrTextContent("Unsupported field type");
private bool pressed;
private float Round(float value, float roundingValue)
{
return roundingValue == 0 ? value : Mathf.Round(value * roundingValue) / roundingValue;
}
private float FlexibleFloatFieldWidth(float min, float max)
{
var n = Mathf.Max(Mathf.Abs(min), Mathf.Abs(max));
return 14f + (Mathf.Floor(Mathf.Log10(Mathf.Abs(n)) + 1) * 2.5f);
}
private void SetVectorValue(SerializedProperty property, ref float min, ref float max, bool round)
{
switch (property.propertyType)
{
case SerializedPropertyType.Vector2:
{
if (round)
{
min = Round(min, kRoundingValue);
max = Round(max, kRoundingValue);
}
property.vector2Value = new Vector2(min, max);
}
break;
case SerializedPropertyType.Vector2Int:
{
property.vector2IntValue = new Vector2Int((int)min, (int)max);
}
break;
default:
break;
}
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
float min, max;
switch (property.propertyType)
{
case SerializedPropertyType.Vector2:
{
var v = property.vector2Value;
min = v.x;
max = v.y;
}
break;
case SerializedPropertyType.Vector2Int:
{
var v = property.vector2IntValue;
min = v.x;
max = v.y;
}
break;
default:
EditorGUI.LabelField(position, label, unsupported);
return;
}
var attr = attribute as MinMaxSliderAttribute;
float ppp = EditorGUIUtility.pixelsPerPoint;
float spacing = kSpacing * ppp;
float fieldWidth = ppp * (attr.DataFields && attr.FlexibleFields ?
FlexibleFloatFieldWidth(attr.Min, attr.Max) :
kFloatFieldWidth);
var indent = EditorGUI.indentLevel;
int id = GUIUtility.GetControlID(controlHash, FocusType.Keyboard, position);
var r = EditorGUI.PrefixLabel(position, id, label);
Rect sliderPos = r;
if (attr.DataFields)
{
sliderPos.x += fieldWidth + spacing;
sliderPos.width -= (fieldWidth + spacing) * 2;
}
if (Event.current.type == EventType.MouseDown &&
sliderPos.Contains(Event.current.mousePosition))
{
pressed = true;
min = Mathf.Clamp(min, attr.Min, attr.Max);
max = Mathf.Clamp(max, attr.Min, attr.Max);
SetVectorValue(property, ref min, ref max, attr.Round);
GUIUtility.keyboardControl = 0; // TODO keep focus but stop editing
}
if (pressed && Event.current.type == EventType.MouseUp)
{
if (attr.Round)
{
SetVectorValue(property, ref min, ref max, true);
}
pressed = false;
}
EditorGUI.BeginChangeCheck();
EditorGUI.indentLevel = 0;
EditorGUI.MinMaxSlider(sliderPos, ref min, ref max, attr.Min, attr.Max);
EditorGUI.indentLevel = indent;
if (EditorGUI.EndChangeCheck())
{
SetVectorValue(property, ref min, ref max, false);
}
if (attr.DataFields)
{
Rect minPos = r;
minPos.width = fieldWidth;
var vectorMinProp = property.FindPropertyRelative(kVectorMinName);
EditorGUI.showMixedValue = vectorMinProp.hasMultipleDifferentValues;
EditorGUI.BeginChangeCheck();
EditorGUI.indentLevel = 0;
min = EditorGUI.DelayedFloatField(minPos, min);
EditorGUI.indentLevel = indent;
if (EditorGUI.EndChangeCheck())
{
if (attr.Bound)
{
min = Mathf.Max(min, attr.Min);
min = Mathf.Min(min, max);
}
SetVectorValue(property, ref min, ref max, attr.Round);
}
vectorMinProp.Dispose();
Rect maxPos = position;
maxPos.x += maxPos.width - fieldWidth;
maxPos.width = fieldWidth;
var vectorMaxProp = property.FindPropertyRelative(kVectorMaxName);
EditorGUI.showMixedValue = vectorMaxProp.hasMultipleDifferentValues;
EditorGUI.BeginChangeCheck();
EditorGUI.indentLevel = 0;
max = EditorGUI.DelayedFloatField(maxPos, max);
EditorGUI.indentLevel = indent;
if (EditorGUI.EndChangeCheck())
{
if (attr.Bound)
{
max = Mathf.Min(max, attr.Max);
max = Mathf.Max(max, min);
}
SetVectorValue(property, ref min, ref max, attr.Round);
}
vectorMaxProp.Dispose();
EditorGUI.showMixedValue = false;
}
}
}
@AShim3D

This comment has been minimized.

Copy link

@AShim3D AShim3D commented Jul 20, 2015

Nice

@SamuelKnox

This comment has been minimized.

Copy link

@SamuelKnox SamuelKnox commented Apr 8, 2016

I am new to property drawers. What is the usage of this?

@Biodam

This comment has been minimized.

@andrew-raphael-lukasik

This comment has been minimized.

Copy link

@andrew-raphael-lukasik andrew-raphael-lukasik commented Aug 17, 2016

Thanks, very useful

@wilderic

This comment has been minimized.

Copy link

@wilderic wilderic commented Jan 26, 2017

Thanks for this script!
Change MinMaxSliderDrawer.cs line 15 to EditorGUI.MinMaxSlider(position, label, ref min, ref max, attr.min, attr.max); to fix deprecated warning in later Unity versions (I'm using 5.5).

@Vilyx

This comment has been minimized.

@frarees

This comment has been minimized.

Copy link
Owner Author

@frarees frarees commented Jan 25, 2019

Hello everyone! Thanks for giving this little snippet a try. I didn't know this would draw any attention at all... but as it did, I felt it could use a little update. I've updated the files with a bunch of improvements:

  • Changed coding conventions to fit Unity's
  • Inspired by @Vilyx, there's now support for float fields
  • Support for mixed values
  • Fixed issue when the field wasn't a Vector2
  • Proper AttributeUsage
  • Default constructor for normalized ranges
  • Updated deprecated code, thanks @wilderic
  • Tested on Unity 2018

Feel free to use this in your projects, no license whatsoever. Credits appreciated.

MinMaxSlider Property Drawer Usage

  • Place MinMaxSliderDrawer.cs inside an Editor folder (e.g. Assets/Editor, Assets/Third Parties/Editor, ...)
  • Place MinMaxSliderAttribute.cs inside a non-editor folder (e.g. Assets/, Assets/Scripts, ...)
  • Mark a Vector2 field on your MonoBehaviour or serializable class with [MinMaxSlider] (for 0-1 range) or [MinMaxSlider(<min>, <max>)] (where <min> and <max> are float values)
  • Enjoy your new handsome property drawer!
@exzizt

This comment has been minimized.

Copy link

@exzizt exzizt commented Aug 11, 2019

This is great! My only complaint is that the value boxes are too small to see the entire value in:
image

I may see if I can fix that.

Edit:

Changing const float kFloatFieldWidth = 30f; to const float kFloatFieldWidth = 60f; did the trick enough for me!
image

@Erikoinen

This comment has been minimized.

Copy link

@Erikoinen Erikoinen commented Jan 7, 2020

A big thank you!

@FishOfTheNorthStar

This comment has been minimized.

Copy link

@FishOfTheNorthStar FishOfTheNorthStar commented Apr 25, 2020

Great script, thanks very much. It had some problems with indent level when used within foldouts, I hacked a little fix in for that and posted it to a fork if anyone else needs it.

@frarees

This comment has been minimized.

Copy link
Owner Author

@frarees frarees commented Apr 30, 2020

Hi @FishOfTheNorthStar, thanks for your contribution. I'm glad you're finding this little script useful.

I've taken a look at how I could best get around indentation, and worked my way through it. Could you grab the updated gist and see if it works OK for you now?

I took some extra time to improve several aspects of the script:

  • Handle indentation
  • Consistent rect layouting
  • Consistent value fields on retina displays
  • Consistency checks on defined bounds
  • Make sure min <= max
  • Round floats down to 2 decimal places (consist with how Unity handles float fields)
  • Support for Vector2Int
  • Clearer error when using on unsupported types
  • Tried to stick to the eighty column rule

Here's how it looks in Unity 2019.3
preview

@frarees

This comment has been minimized.

Copy link
Owner Author

@frarees frarees commented May 1, 2020

Added license information.

@hyakugei

This comment has been minimized.

Copy link

@hyakugei hyakugei commented Mar 18, 2021

Added an unbounded property to allow for arbitrary setting of values, unbounded by min/max, in my branch. Thanks for this!

@frarees

This comment has been minimized.

Copy link
Owner Author

@frarees frarees commented May 13, 2021

Hey everyone! Extra round of maintenance on this property drawer. This time I didn't keep a formal changelog, sorry about that. But there was a lot going on for this update, emphasizing stability and flexibility. Also, added support for unbounded sliders based on @hyakugei's idea.

Overall, with this update the attribute is way more flexible, providing a number of parameters to fine-tune your MinMaxSlider needs:

  • DataFields: Should it draw Min and Max input fields?
  • FlexibleFields: Should the data fields adapt to how long numbers can get? (e.g. [0, 9] shorter number representation than [10, 100])
  • Bound: Are the data fields bound to [Min, Max]? Or are users able to override these with custom values?
  • Round: Should it round to two decimals?

Tested on Unity 2020.3.5f1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment