Checking in new MinMaxSlider

TODO
- Finish Editor creator
pull/413/head
Simon Jackson 2022-12-24 14:52:49 +00:00
parent 3ed20ecaed
commit 2848008f63
19 changed files with 1028 additions and 391 deletions

View File

@ -0,0 +1,121 @@
///Credit brogan89
///Sourced from - https://github.com/brogan89/MinMaxSlider
using System;
using UnityEditor;
using UnityEditor.UI;
namespace UnityEngine.UI.Extensions
{
[CustomEditor(typeof(MinMaxSlider), true)]
[CanEditMultipleObjects]
public class MinMaxSliderEditor : SelectableEditor
{
private SerializedProperty _customCamera;
private SerializedProperty _sliderBounds;
private SerializedProperty _minHandle;
private SerializedProperty _maxHandle;
private SerializedProperty _minText;
private SerializedProperty _maxText;
private SerializedProperty _textFormat;
private SerializedProperty _middleGraphic;
private SerializedProperty _minLimit;
private SerializedProperty _maxLimit;
private SerializedProperty _wholeNumbers;
private SerializedProperty _minValue;
private SerializedProperty _maxValue;
private SerializedProperty _onValueChanged;
private readonly GUIContent label = new GUIContent("Min Max Values");
protected override void OnEnable()
{
base.OnEnable();
_customCamera = serializedObject.FindProperty("customCamera");
_sliderBounds = serializedObject.FindProperty("sliderBounds");
_minHandle = serializedObject.FindProperty("minHandle");
_maxHandle = serializedObject.FindProperty("maxHandle");
_minText = serializedObject.FindProperty("minText");
_maxText = serializedObject.FindProperty("maxText");
_textFormat = serializedObject.FindProperty("textFormat");
_middleGraphic = serializedObject.FindProperty("middleGraphic");
_minLimit = serializedObject.FindProperty("minLimit");
_maxLimit = serializedObject.FindProperty("maxLimit");
_wholeNumbers = serializedObject.FindProperty("wholeNumbers");
_minValue = serializedObject.FindProperty("minValue");
_maxValue = serializedObject.FindProperty("maxValue");
_onValueChanged = serializedObject.FindProperty("onValueChanged");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
float minLimitOld = _minLimit.floatValue;
float maxLimitOld = _maxLimit.floatValue;
float minValueOld = _minValue.floatValue;
float maxValueOld = _maxValue.floatValue;
EditorGUILayout.PropertyField(_customCamera);
EditorGUILayout.PropertyField(_sliderBounds);
EditorGUILayout.PropertyField(_minHandle);
EditorGUILayout.PropertyField(_maxHandle);
EditorGUILayout.PropertyField(_middleGraphic);
EditorGUILayout.PropertyField(_minText);
EditorGUILayout.PropertyField(_maxText);
EditorGUILayout.PropertyField(_textFormat);
EditorGUILayout.PropertyField(_minLimit);
EditorGUILayout.PropertyField(_maxLimit);
EditorGUILayout.PropertyField(_wholeNumbers);
EditorGUILayout.PropertyField(_minValue);
EditorGUILayout.PropertyField(_maxValue);
float minValue = Mathf.Clamp(_minValue.floatValue, _minLimit.floatValue, _maxLimit.floatValue);
float maxValue = Mathf.Clamp(_maxValue.floatValue, _minLimit.floatValue, _maxLimit.floatValue);
EditorGUILayout.MinMaxSlider(label, ref minValue, ref maxValue, _minLimit.floatValue, _maxLimit.floatValue);
bool anyValueChanged = !IsEqualFloat(minValueOld, minValue)
|| !IsEqualFloat(maxValueOld, maxValue)
|| !IsEqualFloat(minLimitOld, _minLimit.floatValue)
|| !IsEqualFloat(maxLimitOld, _maxLimit.floatValue);
if (anyValueChanged)
{
MinMaxSlider slider = (MinMaxSlider)target;
// force limits to ints if whole numbers.
// needed to do this here because it wouldn't set in component script for some reason
if (slider.wholeNumbers)
{
_minLimit.floatValue = Mathf.RoundToInt(_minLimit.floatValue);
_maxLimit.floatValue = Mathf.RoundToInt(_maxLimit.floatValue);
}
// set slider values
slider.SetValues(minValue, maxValue, _minLimit.floatValue, _maxLimit.floatValue);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(_onValueChanged);
serializedObject.ApplyModifiedProperties();
}
/// <summary>
/// Returns true if floating point numbers are within 0.01f (close enough to be considered equal)
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
private static bool IsEqualFloat(float a, float b)
{
return Math.Abs(a - b) < 0.01f;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 69352ed1561021b48ac258f81f48a988
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,4 +1,5 @@
using UnityEngine;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityEngine.UI.Extensions;
@ -1847,7 +1848,7 @@ namespace UnityEditor.UI
#endregion
#region Stepper
[MenuItem("GameObject/UI/Extensions/Stepper", false)]
[MenuItem("GameObject/UI/Extensions/Sliders/Stepper", false)]
static public void AddStepper(MenuCommand menuCommand)
{
GameObject go = CreateUIElementRoot("Stepper", menuCommand, new Vector2(kWidth / 2, kThickHeight));
@ -1891,7 +1892,7 @@ namespace UnityEditor.UI
#endregion
#region BoxSlider
[MenuItem("GameObject/UI/Extensions/Box Slider", false)]
[MenuItem("GameObject/UI/Extensions/Sliders/Box Slider", false)]
static public void AddBoxSlider(MenuCommand menuCommand)
{
@ -1946,7 +1947,7 @@ namespace UnityEditor.UI
#endregion
#region Radial Slider
[MenuItem("GameObject/UI/Extensions/Radial Slider", false)]
[MenuItem("GameObject/UI/Extensions/Sliders/Radial Slider", false)]
static public void AddRadialSlider(MenuCommand menuCommand)
{
GameObject sliderRoot = CreateUIElementRoot("Radial Slider", menuCommand, s_ThickGUIElementSize);
@ -1984,21 +1985,21 @@ namespace UnityEditor.UI
#endregion
#region RangeSlider
[MenuItem("GameObject/UI/Extensions/Range Slider", false)]
[MenuItem("GameObject/UI/Extensions/Sliders/Range Slider", false)]
static public void AddRangeSlider(MenuCommand menuCommand)
{
GameObject rangeSliderRoot = CreateUIElementRoot("Range Slider", menuCommand, new Vector2(160, 20));
GameObject minMaxSliderRoot = CreateUIElementRoot("Range Slider", menuCommand, new Vector2(160, 20));
GameObject background = CreateUIObject("Background", rangeSliderRoot);
GameObject background = CreateUIObject("Background", minMaxSliderRoot);
GameObject fillArea = CreateUIObject("Fill Area", rangeSliderRoot);
GameObject fillArea = CreateUIObject("Fill Area", minMaxSliderRoot);
GameObject fill = CreateUIObject("Fill", fillArea);
GameObject handleSlideArea = CreateUIObject("Handle Slide Area", rangeSliderRoot);
GameObject lowHandle = CreateUIObject("Low Handle", handleSlideArea);
GameObject handleSlideArea = CreateUIObject("Handle Slide Area", minMaxSliderRoot);
GameObject minHandle = CreateUIObject("Low Handle", handleSlideArea);
GameObject highHandle = CreateUIObject("High Handle", handleSlideArea);
SetAnchorsAndStretch(rangeSliderRoot);
SetAnchorsAndStretch(minMaxSliderRoot);
Image backgroundImage = background.AddComponent<Image>();
backgroundImage.sprite = AssetDatabase.GetBuiltinExtraResource<Sprite>(kBackgroundSpriteResourcePath);
backgroundImage.type = Image.Type.Sliced;
@ -2029,8 +2030,8 @@ namespace UnityEditor.UI
handleSlideRect.offsetMin = new Vector2(10, -10);
handleSlideRect.offsetMax = new Vector2(-10, 10);
RectTransform lowHandleRect = SetAnchorsAndStretch(lowHandle);
Image lowHandleImage = lowHandle.AddComponent<Image>();
RectTransform lowHandleRect = SetAnchorsAndStretch(minHandle);
Image lowHandleImage = minHandle.AddComponent<Image>();
lowHandleImage.sprite = AssetDatabase.GetBuiltinExtraResource<Sprite>(kKnobPath);
lowHandleRect.sizeDelta = new Vector2(20, 0);
@ -2039,7 +2040,7 @@ namespace UnityEditor.UI
highHandleImage.sprite = AssetDatabase.GetBuiltinExtraResource<Sprite>(kKnobPath);
highHandleRect.sizeDelta = new Vector2(20, 0);
RangeSlider rangeSlider = rangeSliderRoot.AddComponent<RangeSlider>();
RangeSlider rangeSlider = minMaxSliderRoot.AddComponent<RangeSlider>();
rangeSlider.FillRect = fillRect;
rangeSlider.LowHandleRect = lowHandleRect;
rangeSlider.HighHandleRect = highHandleRect;
@ -2047,7 +2048,7 @@ namespace UnityEditor.UI
rangeSlider.HighValue = rangeSlider.MaxValue;
rangeSlider.targetGraphic = fillImage;
Selection.activeGameObject = rangeSliderRoot;
Selection.activeGameObject = minMaxSliderRoot;
}
#endregion
@ -2062,6 +2063,80 @@ namespace UnityEditor.UI
}
#endregion
#region MinMaxSlider
[MenuItem("GameObject/UI/Extensions/Sliders/MinMax Slider", false)]
static public void AddMinMaxSlider(MenuCommand menuCommand)
{
GameObject minMaxSliderRoot = CreateUIElementRoot("MinMax Slider", menuCommand, new Vector2(390, 60));
//GameObject background = CreateUIObject("Background", rangeSliderRoot);
GameObject sliderBounds = CreateUIObject("Slider Bounds", minMaxSliderRoot);
GameObject middleGraphic = CreateUIObject("Middle Graphic", minMaxSliderRoot);
GameObject minHandle = CreateUIObject("Min Handle", minMaxSliderRoot);
GameObject minHandleText = CreateUIObject("Min Text", minHandle);
GameObject maxHandle = CreateUIObject("Max Handle", minMaxSliderRoot);
GameObject maxHandleText = CreateUIObject("Max Text", maxHandle);
SetAnchorsAndStretch(minMaxSliderRoot);
Image backgroundImage = minMaxSliderRoot.AddComponent<Image>();
backgroundImage.sprite = AssetDatabase.GetBuiltinExtraResource<Sprite>(kStandardSpritePath);
backgroundImage.type = Image.Type.Sliced;
backgroundImage.fillCenter = false;
backgroundImage.color = new Color(27, 41, 89);
RectTransform backgroundRect = backgroundImage.rectTransform;
backgroundRect.anchorMin = new Vector2(0.5f, 0.5f);
backgroundRect.anchorMax = new Vector2(0.5f, 0.5f);
backgroundRect.sizeDelta = Vector2.zero;
RectTransform sliderBoundsRect = SetAnchorsAndStretch(sliderBounds);
sliderBoundsRect.anchorMin = new Vector2(0, 0);
sliderBoundsRect.anchorMax = new Vector2(1, 1);
RectTransform middleGraphicRect = SetAnchorsAndStretch(middleGraphic);
Image fillImage = middleGraphic.AddComponent<Image>();
fillImage.sprite = AssetDatabase.GetBuiltinExtraResource<Sprite>(kStandardSpritePath);
fillImage.type = Image.Type.Sliced;
fillImage.fillCenter = true;
fillImage.color = new Color(41, 98, 164);
RectTransform minHandleRect = SetAnchorsAndStretch(minHandle);
Image lowHandleImage = minHandle.AddComponent<Image>();
lowHandleImage.sprite = AssetDatabase.GetBuiltinExtraResource<Sprite>(kStandardSpritePath);
minHandleRect.sizeDelta = new Vector2(30, 62);
RectTransform minHandleTextRect = SetAnchorsAndStretch(minHandleText);
TextMeshProUGUI minHandleTextComponent = minHandleText.AddComponent<TextMeshProUGUI>();
minHandleTextComponent.text = "0";
minHandleTextComponent.fontSize = 36;
minHandleTextRect.sizeDelta = new Vector2(70, 50);
minHandleTextRect.position = new Vector3(0, -60,0);
RectTransform maxHandleRect = SetAnchorsAndStretch(maxHandle);
Image maxHandleImage = maxHandle.AddComponent<Image>();
maxHandleImage.sprite = AssetDatabase.GetBuiltinExtraResource<Sprite>(kStandardSpritePath);
maxHandleRect.sizeDelta = new Vector2(20, 0);
RectTransform maxHandleTextRect = SetAnchorsAndStretch(maxHandleText);
TextMeshProUGUI maxHandleTextComponent = maxHandleText.AddComponent<TextMeshProUGUI>();
maxHandleTextComponent.text = "0";
maxHandleTextComponent.fontSize = 36;
maxHandleTextRect.sizeDelta = new Vector2(70, 50);
maxHandleTextRect.position = new Vector3(0, -60, 0);
MinMaxSlider minMaxSlider = minMaxSliderRoot.AddComponent<MinMaxSlider>();
minMaxSlider.SliderBounds = sliderBoundsRect;
minMaxSlider.MinHandle = minHandleRect;
minMaxSlider.MaxHandle = maxHandleRect;
minMaxSlider.MiddleGraphic = middleGraphicRect;
minMaxSlider.MinText = minHandleTextComponent;
minMaxSlider.MaxText = maxHandleTextComponent;
Selection.activeGameObject = minMaxSliderRoot;
}
#endregion
#endregion

View File

@ -1,9 +1,11 @@
{
"name": "UnityUIExtensions.editor",
"rootNamespace": "",
"references": [
"GUID:343deaaf83e0cee4ca978e7df0b80d21",
"GUID:2bafac87e7f4b9b418d9448d219b01ab",
"GUID:cf414061cae3a954baf92763590f3127"
"GUID:cf414061cae3a954baf92763590f3127",
"GUID:6055be8ebefd69e48b49212b09b47b2f"
],
"includePlatforms": [
"Editor"
@ -14,5 +16,6 @@
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": []
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d7bdc7e70331fe24aba2c9549f84c657
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,371 +1,371 @@
///Credit judah4
///Sourced from - http://forum.unity3d.com/threads/color-picker.267043/
using System;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace UnityEngine.UI.Extensions
{
[RequireComponent(typeof(RectTransform))]
[AddComponentMenu("UI/Extensions/BoxSlider")]
public class BoxSlider : Selectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
{
public enum Direction
{
LeftToRight,
RightToLeft,
BottomToTop,
TopToBottom,
}
[Serializable]
public class BoxSliderEvent : UnityEvent<float, float> { }
[SerializeField]
private RectTransform m_HandleRect;
public RectTransform HandleRect { get { return m_HandleRect; } set { if (SetClass(ref m_HandleRect, value)) { UpdateCachedReferences(); UpdateVisuals(); } } }
[Space(6)]
[SerializeField]
private float m_MinValue = 0;
public float MinValue { get { return m_MinValue; } set { if (SetStruct(ref m_MinValue, value)) { SetX(m_ValueX); SetY(m_ValueY); UpdateVisuals(); } } }
[SerializeField]
private float m_MaxValue = 1;
public float MaxValue { get { return m_MaxValue; } set { if (SetStruct(ref m_MaxValue, value)) { SetX(m_ValueX); SetY(m_ValueY); UpdateVisuals(); } } }
[SerializeField]
private bool m_WholeNumbers = false;
public bool WholeNumbers { get { return m_WholeNumbers; } set { if (SetStruct(ref m_WholeNumbers, value)) { SetX(m_ValueX); SetY(m_ValueY); UpdateVisuals(); } } }
[SerializeField]
private float m_ValueX = 1f;
public float ValueX
{
get
{
if (WholeNumbers)
return Mathf.Round(m_ValueX);
return m_ValueX;
}
set
{
SetX(value);
}
}
public float NormalizedValueX
{
get
{
if (Mathf.Approximately(MinValue, MaxValue))
return 0;
return Mathf.InverseLerp(MinValue, MaxValue, ValueX);
}
set
{
this.ValueX = Mathf.Lerp(MinValue, MaxValue, value);
}
}
[SerializeField]
private float m_ValueY = 1f;
public float ValueY
{
get
{
if (WholeNumbers)
return Mathf.Round(m_ValueY);
return m_ValueY;
}
set
{
SetY(value);
}
}
public float NormalizedValueY
{
get
{
if (Mathf.Approximately(MinValue, MaxValue))
return 0;
return Mathf.InverseLerp(MinValue, MaxValue, ValueY);
}
set
{
this.ValueY = Mathf.Lerp(MinValue, MaxValue, value);
}
}
[Space(6)]
// Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers.
[SerializeField]
private BoxSliderEvent m_OnValueChanged = new BoxSliderEvent();
public BoxSliderEvent OnValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } }
// Private fields
private Transform m_HandleTransform;
private RectTransform m_HandleContainerRect;
// The offset from handle position to mouse down position
private Vector2 m_Offset = Vector2.zero;
private DrivenRectTransformTracker m_Tracker;
// Size of each step.
float StepSize { get { return WholeNumbers ? 1 : (MaxValue - MinValue) * 0.1f; } }
protected BoxSlider()
{ }
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (WholeNumbers)
{
m_MinValue = Mathf.Round(m_MinValue);
m_MaxValue = Mathf.Round(m_MaxValue);
}
UpdateCachedReferences();
SetX(m_ValueX, false);
SetY(m_ValueY, false);
// Update rects since other things might affect them even if value didn't change.
if(!Application.isPlaying) UpdateVisuals();
#if UNITY_2018_3_OR_NEWER
if (!Application.isPlaying)
#else
var prefabType = UnityEditor.PrefabUtility.GetPrefabType(this);
if (prefabType != UnityEditor.PrefabType.Prefab && !Application.isPlaying)
#endif
{
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
}
}
#endif // if UNITY_EDITOR
public virtual void Rebuild(CanvasUpdate executing)
{
#if UNITY_EDITOR
if (executing == CanvasUpdate.Prelayout)
OnValueChanged.Invoke(ValueX, ValueY);
#endif
}
public void LayoutComplete()
{
}
public void GraphicUpdateComplete()
{
}
public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
{
if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
return false;
currentValue = newValue;
return true;
}
public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
{
if (currentValue.Equals(newValue))
return false;
currentValue = newValue;
return true;
}
protected override void OnEnable()
{
base.OnEnable();
UpdateCachedReferences();
SetX(m_ValueX, false);
SetY(m_ValueY, false);
// Update rects since they need to be initialized correctly.
UpdateVisuals();
}
protected override void OnDisable()
{
m_Tracker.Clear();
base.OnDisable();
}
void UpdateCachedReferences()
{
if (m_HandleRect)
{
m_HandleTransform = m_HandleRect.transform;
if (m_HandleTransform.parent != null)
m_HandleContainerRect = m_HandleTransform.parent.GetComponent<RectTransform>();
}
else
{
m_HandleContainerRect = null;
}
}
// Set the valueUpdate the visible Image.
void SetX(float input)
{
SetX(input, true);
}
void SetX(float input, bool sendCallback)
{
// Clamp the input
float newValue = Mathf.Clamp(input, MinValue, MaxValue);
if (WholeNumbers)
newValue = Mathf.Round(newValue);
// If the stepped value doesn't match the last one, it's time to update
if (m_ValueX == newValue)
return;
m_ValueX = newValue;
UpdateVisuals();
if (sendCallback)
m_OnValueChanged.Invoke(newValue, ValueY);
}
void SetY(float input)
{
SetY(input, true);
}
void SetY(float input, bool sendCallback)
{
// Clamp the input
float newValue = Mathf.Clamp(input, MinValue, MaxValue);
if (WholeNumbers)
newValue = Mathf.Round(newValue);
// If the stepped value doesn't match the last one, it's time to update
if (m_ValueY == newValue)
return;
m_ValueY = newValue;
UpdateVisuals();
if (sendCallback)
m_OnValueChanged.Invoke(ValueX, newValue);
}
protected override void OnRectTransformDimensionsChange()
{
base.OnRectTransformDimensionsChange();
UpdateVisuals();
}
enum Axis
{
Horizontal = 0,
Vertical = 1
}
// Force-update the slider. Useful if you've changed the properties and want it to update visually.
private void UpdateVisuals()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
UpdateCachedReferences();
#endif
m_Tracker.Clear();
//to business!
if (m_HandleContainerRect != null)
{
m_Tracker.Add(this, m_HandleRect, DrivenTransformProperties.Anchors);
Vector2 anchorMin = Vector2.zero;
Vector2 anchorMax = Vector2.one;
anchorMin[0] = anchorMax[0] = (NormalizedValueX);
anchorMin[1] = anchorMax[1] = (NormalizedValueY);
if (Application.isPlaying)
{
m_HandleRect.anchorMin = anchorMin;
m_HandleRect.anchorMax = anchorMax;
}
}
}
// Update the slider's position based on the mouse.
void UpdateDrag(PointerEventData eventData, Camera cam)
{
RectTransform clickRect = m_HandleContainerRect;
if (clickRect != null && clickRect.rect.size[0] > 0)
{
Vector2 localCursor;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, eventData.position, cam, out localCursor))
return;
localCursor -= clickRect.rect.position;
float val = Mathf.Clamp01((localCursor - m_Offset)[0] / clickRect.rect.size[0]);
NormalizedValueX = (val);
float valY = Mathf.Clamp01((localCursor - m_Offset)[1] / clickRect.rect.size[1]);
NormalizedValueY = (valY);
}
}
private bool CanDrag(PointerEventData eventData)
{
return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
}
public override void OnPointerDown(PointerEventData eventData)
{
if (!CanDrag(eventData))
return;
base.OnPointerDown(eventData);
m_Offset = Vector2.zero;
if (m_HandleContainerRect != null && RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, eventData.position, eventData.enterEventCamera))
{
Vector2 localMousePos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, eventData.position, eventData.pressEventCamera, out localMousePos))
m_Offset = localMousePos;
m_Offset.y = -m_Offset.y;
}
else
{
// Outside the slider handle - jump to this point instead
UpdateDrag(eventData, eventData.pressEventCamera);
}
}
public virtual void OnDrag(PointerEventData eventData)
{
if (!CanDrag(eventData))
return;
UpdateDrag(eventData, eventData.pressEventCamera);
}
public virtual void OnInitializePotentialDrag(PointerEventData eventData)
{
eventData.useDragThreshold = false;
}
}
}
///Credit judah4
///Sourced from - http://forum.unity3d.com/threads/color-picker.267043/
using System;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace UnityEngine.UI.Extensions
{
[RequireComponent(typeof(RectTransform))]
[AddComponentMenu("UI/Extensions/Sliders/BoxSlider")]
public class BoxSlider : Selectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
{
public enum Direction
{
LeftToRight,
RightToLeft,
BottomToTop,
TopToBottom,
}
[Serializable]
public class BoxSliderEvent : UnityEvent<float, float> { }
[SerializeField]
private RectTransform m_HandleRect;
public RectTransform HandleRect { get { return m_HandleRect; } set { if (SetClass(ref m_HandleRect, value)) { UpdateCachedReferences(); UpdateVisuals(); } } }
[Space(6)]
[SerializeField]
private float m_MinValue = 0;
public float MinValue { get { return m_MinValue; } set { if (SetStruct(ref m_MinValue, value)) { SetX(m_ValueX); SetY(m_ValueY); UpdateVisuals(); } } }
[SerializeField]
private float m_MaxValue = 1;
public float MaxValue { get { return m_MaxValue; } set { if (SetStruct(ref m_MaxValue, value)) { SetX(m_ValueX); SetY(m_ValueY); UpdateVisuals(); } } }
[SerializeField]
private bool m_WholeNumbers = false;
public bool WholeNumbers { get { return m_WholeNumbers; } set { if (SetStruct(ref m_WholeNumbers, value)) { SetX(m_ValueX); SetY(m_ValueY); UpdateVisuals(); } } }
[SerializeField]
private float m_ValueX = 1f;
public float ValueX
{
get
{
if (WholeNumbers)
return Mathf.Round(m_ValueX);
return m_ValueX;
}
set
{
SetX(value);
}
}
public float NormalizedValueX
{
get
{
if (Mathf.Approximately(MinValue, MaxValue))
return 0;
return Mathf.InverseLerp(MinValue, MaxValue, ValueX);
}
set
{
this.ValueX = Mathf.Lerp(MinValue, MaxValue, value);
}
}
[SerializeField]
private float m_ValueY = 1f;
public float ValueY
{
get
{
if (WholeNumbers)
return Mathf.Round(m_ValueY);
return m_ValueY;
}
set
{
SetY(value);
}
}
public float NormalizedValueY
{
get
{
if (Mathf.Approximately(MinValue, MaxValue))
return 0;
return Mathf.InverseLerp(MinValue, MaxValue, ValueY);
}
set
{
this.ValueY = Mathf.Lerp(MinValue, MaxValue, value);
}
}
[Space(6)]
// Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers.
[SerializeField]
private BoxSliderEvent m_OnValueChanged = new BoxSliderEvent();
public BoxSliderEvent OnValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } }
// Private fields
private Transform m_HandleTransform;
private RectTransform m_HandleContainerRect;
// The offset from handle position to mouse down position
private Vector2 m_Offset = Vector2.zero;
private DrivenRectTransformTracker m_Tracker;
// Size of each step.
float StepSize { get { return WholeNumbers ? 1 : (MaxValue - MinValue) * 0.1f; } }
protected BoxSlider()
{ }
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (WholeNumbers)
{
m_MinValue = Mathf.Round(m_MinValue);
m_MaxValue = Mathf.Round(m_MaxValue);
}
UpdateCachedReferences();
SetX(m_ValueX, false);
SetY(m_ValueY, false);
// Update rects since other things might affect them even if value didn't change.
if(!Application.isPlaying) UpdateVisuals();
#if UNITY_2018_3_OR_NEWER
if (!Application.isPlaying)
#else
var prefabType = UnityEditor.PrefabUtility.GetPrefabType(this);
if (prefabType != UnityEditor.PrefabType.Prefab && !Application.isPlaying)
#endif
{
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
}
}
#endif // if UNITY_EDITOR
public virtual void Rebuild(CanvasUpdate executing)
{
#if UNITY_EDITOR
if (executing == CanvasUpdate.Prelayout)
OnValueChanged.Invoke(ValueX, ValueY);
#endif
}
public void LayoutComplete()
{
}
public void GraphicUpdateComplete()
{
}
public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
{
if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
return false;
currentValue = newValue;
return true;
}
public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
{
if (currentValue.Equals(newValue))
return false;
currentValue = newValue;
return true;
}
protected override void OnEnable()
{
base.OnEnable();
UpdateCachedReferences();
SetX(m_ValueX, false);
SetY(m_ValueY, false);
// Update rects since they need to be initialized correctly.
UpdateVisuals();
}
protected override void OnDisable()
{
m_Tracker.Clear();
base.OnDisable();
}
void UpdateCachedReferences()
{
if (m_HandleRect)
{
m_HandleTransform = m_HandleRect.transform;
if (m_HandleTransform.parent != null)
m_HandleContainerRect = m_HandleTransform.parent.GetComponent<RectTransform>();
}
else
{
m_HandleContainerRect = null;
}
}
// Set the valueUpdate the visible Image.
void SetX(float input)
{
SetX(input, true);
}
void SetX(float input, bool sendCallback)
{
// Clamp the input
float newValue = Mathf.Clamp(input, MinValue, MaxValue);
if (WholeNumbers)
newValue = Mathf.Round(newValue);
// If the stepped value doesn't match the last one, it's time to update
if (m_ValueX == newValue)
return;
m_ValueX = newValue;
UpdateVisuals();
if (sendCallback)
m_OnValueChanged.Invoke(newValue, ValueY);
}
void SetY(float input)
{
SetY(input, true);
}
void SetY(float input, bool sendCallback)
{
// Clamp the input
float newValue = Mathf.Clamp(input, MinValue, MaxValue);
if (WholeNumbers)
newValue = Mathf.Round(newValue);
// If the stepped value doesn't match the last one, it's time to update
if (m_ValueY == newValue)
return;
m_ValueY = newValue;
UpdateVisuals();
if (sendCallback)
m_OnValueChanged.Invoke(ValueX, newValue);
}
protected override void OnRectTransformDimensionsChange()
{
base.OnRectTransformDimensionsChange();
UpdateVisuals();
}
enum Axis
{
Horizontal = 0,
Vertical = 1
}
// Force-update the slider. Useful if you've changed the properties and want it to update visually.
private void UpdateVisuals()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
UpdateCachedReferences();
#endif
m_Tracker.Clear();
//to business!
if (m_HandleContainerRect != null)
{
m_Tracker.Add(this, m_HandleRect, DrivenTransformProperties.Anchors);
Vector2 anchorMin = Vector2.zero;
Vector2 anchorMax = Vector2.one;
anchorMin[0] = anchorMax[0] = (NormalizedValueX);
anchorMin[1] = anchorMax[1] = (NormalizedValueY);
if (Application.isPlaying)
{
m_HandleRect.anchorMin = anchorMin;
m_HandleRect.anchorMax = anchorMax;
}
}
}
// Update the slider's position based on the mouse.
void UpdateDrag(PointerEventData eventData, Camera cam)
{
RectTransform clickRect = m_HandleContainerRect;
if (clickRect != null && clickRect.rect.size[0] > 0)
{
Vector2 localCursor;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, eventData.position, cam, out localCursor))
return;
localCursor -= clickRect.rect.position;
float val = Mathf.Clamp01((localCursor - m_Offset)[0] / clickRect.rect.size[0]);
NormalizedValueX = (val);
float valY = Mathf.Clamp01((localCursor - m_Offset)[1] / clickRect.rect.size[1]);
NormalizedValueY = (valY);
}
}
private bool CanDrag(PointerEventData eventData)
{
return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
}
public override void OnPointerDown(PointerEventData eventData)
{
if (!CanDrag(eventData))
return;
base.OnPointerDown(eventData);
m_Offset = Vector2.zero;
if (m_HandleContainerRect != null && RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, eventData.position, eventData.enterEventCamera))
{
Vector2 localMousePos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, eventData.position, eventData.pressEventCamera, out localMousePos))
m_Offset = localMousePos;
m_Offset.y = -m_Offset.y;
}
else
{
// Outside the slider handle - jump to this point instead
UpdateDrag(eventData, eventData.pressEventCamera);
}
}
public virtual void OnDrag(PointerEventData eventData)
{
if (!CanDrag(eventData))
return;
UpdateDrag(eventData, eventData.pressEventCamera);
}
public virtual void OnInitializePotentialDrag(PointerEventData eventData)
{
eventData.useDragThreshold = false;
}
}
}

View File

@ -0,0 +1,347 @@
///Credit brogan89
///Sourced from - https://github.com/brogan89/MinMaxSlider
using System;
using TMPro;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace UnityEngine.UI.Extensions
{
[RequireComponent(typeof(RectTransform))]
[AddComponentMenu("UI/Extensions/Sliders/MinMax Slider")]
public class MinMaxSlider : Selectable, IBeginDragHandler, IDragHandler, IEndDragHandler
{
private enum DragState
{
Both,
Min,
Max
}
[Header("UI Controls")]
[SerializeField] private Camera customCamera = null;
[SerializeField] private RectTransform sliderBounds = null;
[SerializeField] private RectTransform minHandle = null;
[SerializeField] private RectTransform maxHandle = null;
[SerializeField] private RectTransform middleGraphic = null;
// text components (optional)
[Header("Display Text (Optional)")]
[SerializeField] private TextMeshProUGUI minText = null;
[SerializeField] private TextMeshProUGUI maxText = null;
[SerializeField] private string textFormat = "0";
// values
[Header("Limits")]
[SerializeField] private float minLimit = 0;
[SerializeField] private float maxLimit = 100;
[Header("Values")]
public bool wholeNumbers;
[SerializeField] private float minValue = 25;
[SerializeField] private float maxValue = 75;
public MinMaxValues Values => new MinMaxValues(minValue, maxValue, minLimit, maxLimit);
public RectTransform SliderBounds { get => sliderBounds; set => sliderBounds = value; }
public RectTransform MinHandle { get => minHandle; set => minHandle = value; }
public RectTransform MaxHandle { get => maxHandle; set => maxHandle = value; }
public RectTransform MiddleGraphic { get => middleGraphic; set => middleGraphic = value; }
public TextMeshProUGUI MinText { get => minText; set => minText = value; }
public TextMeshProUGUI MaxText { get => maxText; set => maxText = value; }
/// <summary>
/// Event invoked when either slider value has changed
/// <para></para>
/// T0 = min, T1 = max
/// </summary>
[Serializable]
public class SliderEvent : UnityEvent<float, float> { }
public SliderEvent onValueChanged = new SliderEvent();
private Vector2 dragStartPosition;
private float dragStartMinValue01;
private float dragStartMaxValue01;
private DragState dragState;
private bool passDragEvents; // this allows drag events to be passed through to scrollers
private Camera mainCamera;
private Canvas parentCanvas;
private bool isOverlayCanvas;
protected override void Start()
{
base.Start();
if (!sliderBounds)
{
sliderBounds = transform as RectTransform;
}
parentCanvas = GetComponentInParent<Canvas>();
isOverlayCanvas = parentCanvas.renderMode == RenderMode.ScreenSpaceOverlay;
mainCamera = customCamera != null ? customCamera : Camera.main;
}
public void SetLimits(float minLimit, float maxLimit)
{
this.minLimit = wholeNumbers ? Mathf.RoundToInt(minLimit) : minLimit;
this.maxLimit = wholeNumbers ? Mathf.RoundToInt(maxLimit) : maxLimit;
}
public void SetValues(MinMaxValues values, bool notify = true)
{
SetValues(values.minValue, values.maxValue, values.minLimit, values.maxLimit, notify);
}
public void SetValues(float minValue, float maxValue, bool notify = true)
{
SetValues(minValue, maxValue, minLimit, maxLimit, notify);
}
public void SetValues(float minValue, float maxValue, float minLimit, float maxLimit, bool notify = true)
{
this.minValue = wholeNumbers ? Mathf.RoundToInt(minValue) : minValue;
this.maxValue = wholeNumbers ? Mathf.RoundToInt(maxValue) : maxValue;
SetLimits(minLimit, maxLimit);
RefreshSliders();
UpdateText();
UpdateMiddleGraphic();
if (notify)
{
// event
onValueChanged.Invoke(this.minValue, this.maxValue);
}
}
private void RefreshSliders()
{
SetSliderAnchors();
float clampedMin = Mathf.Clamp(minValue, minLimit, maxLimit);
SetMinHandleValue01(minHandle, GetPercentage(minLimit, maxLimit, clampedMin));
float clampedMax = Mathf.Clamp(maxValue, minLimit, maxLimit);
SetMaxHandleValue01(maxHandle, GetPercentage(minLimit, maxLimit, clampedMax));
}
private void SetSliderAnchors()
{
minHandle.anchorMin = new Vector2(0, 0.5f);
minHandle.anchorMax = new Vector2(0, 0.5f);
minHandle.pivot = new Vector2(0.5f, 0.5f);
maxHandle.anchorMin = new Vector2(1, 0.5f);
maxHandle.anchorMax = new Vector2(1, 0.5f);
maxHandle.pivot = new Vector2(0.5f, 0.5f);
}
private void UpdateText()
{
if (minText)
{
minText.SetText(minValue.ToString(textFormat));
}
if (maxText)
{
maxText.SetText(maxValue.ToString(textFormat));
}
}
private void UpdateMiddleGraphic()
{
if (!middleGraphic) return;
middleGraphic.anchorMin = Vector2.zero;
middleGraphic.anchorMax = Vector2.one;
middleGraphic.offsetMin = new Vector2(minHandle.anchoredPosition.x, 0);
middleGraphic.offsetMax = new Vector2(maxHandle.anchoredPosition.x, 0);
}
#region IDragHandler
public void OnBeginDrag(PointerEventData eventData)
{
passDragEvents = Math.Abs(eventData.delta.x) < Math.Abs(eventData.delta.y);
if (passDragEvents)
{
PassDragEvents<IBeginDragHandler>(x => x.OnBeginDrag(eventData));
}
else
{
Camera uiCamera = isOverlayCanvas ? null : mainCamera;
RectTransformUtility.ScreenPointToLocalPointInRectangle(sliderBounds, eventData.position, uiCamera, out dragStartPosition);
float dragStartValue = GetValueOfPointInSliderBounds01(dragStartPosition);
dragStartMinValue01 = GetMinHandleValue01(minHandle);
dragStartMaxValue01 = GetMaxHandleValue01(maxHandle);
// set drag state
if (dragStartValue < dragStartMinValue01 || RectTransformUtility.RectangleContainsScreenPoint(minHandle, eventData.position, uiCamera))
{
dragState = DragState.Min;
minHandle.SetAsLastSibling();
}
else if (dragStartValue > dragStartMaxValue01 || RectTransformUtility.RectangleContainsScreenPoint(maxHandle, eventData.position, uiCamera))
{
dragState = DragState.Max;
maxHandle.SetAsLastSibling();
}
else
{
dragState = DragState.Both;
}
}
}
public void OnDrag(PointerEventData eventData)
{
if (passDragEvents)
{
PassDragEvents<IDragHandler>(x => x.OnDrag(eventData));
}
else if (minHandle && maxHandle)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(sliderBounds, eventData.position, isOverlayCanvas ? null : mainCamera, out Vector2 clickPosition);
SetSliderAnchors();
if (dragState == DragState.Min || dragState == DragState.Max)
{
float dragPosition01 = GetValueOfPointInSliderBounds01(clickPosition);
float minHandleValue = GetMinHandleValue01(minHandle);
float maxHandleValue = GetMaxHandleValue01(maxHandle);
if (dragState == DragState.Min)
SetMinHandleValue01(minHandle, Mathf.Clamp(dragPosition01, 0, maxHandleValue));
else if (dragState == DragState.Max)
SetMaxHandleValue01(maxHandle, Mathf.Clamp(dragPosition01, minHandleValue, 1));
}
else
{
float distancePercent = (clickPosition.x - dragStartPosition.x) / sliderBounds.rect.width;
SetMinHandleValue01(minHandle, dragStartMinValue01 + distancePercent);
SetMaxHandleValue01(maxHandle, dragStartMaxValue01 + distancePercent);
}
// set values
float min = Mathf.Lerp(minLimit, maxLimit, GetMinHandleValue01(minHandle));
float max = Mathf.Lerp(minLimit, maxLimit, GetMaxHandleValue01(maxHandle));
SetValues(min, max);
UpdateText();
UpdateMiddleGraphic();
}
}
public void OnEndDrag(PointerEventData eventData)
{
if (passDragEvents)
{
PassDragEvents<IEndDragHandler>(x => x.OnEndDrag(eventData));
}
else
{
float minHandleValue = GetMinHandleValue01(minHandle);
float maxHandleValue = GetMaxHandleValue01(maxHandle);
// this safe guards a possible situation where the slides can get stuck
if (Math.Abs(minHandleValue) < MinMaxValues.FLOAT_TOL && Math.Abs(maxHandleValue) < MinMaxValues.FLOAT_TOL)
{
maxHandle.SetAsLastSibling();
}
else if (Math.Abs(minHandleValue - 1) < MinMaxValues.FLOAT_TOL && Math.Abs(maxHandleValue - 1) < MinMaxValues.FLOAT_TOL)
{
minHandle.SetAsLastSibling();
}
}
}
#endregion IDragHandler
private void PassDragEvents<T>(Action<T> callback) where T : IEventSystemHandler
{
Transform parent = transform.parent;
while (parent != null)
{
foreach (var component in parent.GetComponents<Component>())
{
if (!(component is T)) continue;
callback.Invoke((T)(IEventSystemHandler)component);
return;
}
parent = parent.parent;
}
}
/// <summary>
/// Sets position of max handle RectTransform
/// </summary>
/// <param name="handle"></param>
/// <param name="value01">Normalized handle position</param>
private void SetMaxHandleValue01(RectTransform handle, float value01)
{
handle.anchoredPosition = new Vector2(value01 * sliderBounds.rect.width - sliderBounds.rect.width + sliderBounds.offsetMax.x, handle.anchoredPosition.y);
}
/// <summary>
/// Sets position of min handle RectTransform
/// </summary>
/// <param name="handle"></param>
/// <param name="value01">Normalized handle position</param>
private void SetMinHandleValue01(RectTransform handle, float value01)
{
handle.anchoredPosition = new Vector2(value01 * sliderBounds.rect.width + sliderBounds.offsetMin.x, handle.anchoredPosition.y);
}
/// <summary>
/// Returns normalized position of max handle RectTransform
/// </summary>
/// <param name="handle"></param>
/// <returns>Normalized position of max handle RectTransform</returns>
private float GetMaxHandleValue01(RectTransform handle)
{
return 1 + (handle.anchoredPosition.x - sliderBounds.offsetMax.x) / sliderBounds.rect.width;
}
/// <summary>
/// Returns normalized position of min handle RectTransform
/// </summary>
/// <param name="handle"></param>
/// <returns>Normalized position of min handle RectTransform</returns>
private float GetMinHandleValue01(RectTransform handle)
{
return (handle.anchoredPosition.x - sliderBounds.offsetMin.x) / sliderBounds.rect.width;
}
/// <summary>
/// Returns normalized position of a point in a slider bounds rectangle
/// </summary>
/// <param name="position"></param>
/// <returns>Normalized position of a point in a slider bounds rectangle</returns>
private float GetValueOfPointInSliderBounds01(Vector2 position)
{
var width = sliderBounds.rect.width;
return Mathf.Clamp((position.x + width / 2) / width, 0, 1);
}
/// <summary>
/// Returns percentage of input based on min and max values
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="input"></param>
/// <returns></returns>
private static float GetPercentage(float min, float max, float input)
{
return (input - min) / (max - min);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b9f9954231c8bab419504a7ac5ff133e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -8,8 +8,8 @@ using UnityEngine.EventSystems;
namespace UnityEngine.UI.Extensions
{
[AddComponentMenu("UI/Extensions/Radial Slider")]
[RequireComponent(typeof(Image))]
[AddComponentMenu("UI/Extensions/Sliders/Radial Slider")]
public class RadialSlider : MonoBehaviour, IPointerEnterHandler, IPointerDownHandler, IPointerUpHandler, IDragHandler
{
private bool isPointerDown, isPointerReleased, lerpInProgress;

View File

@ -9,9 +9,9 @@ using UnityEngine.EventSystems;
namespace UnityEngine.UI.Extensions
{
[AddComponentMenu("UI/Extensions/Range Slider", 34)]
[ExecuteInEditMode]
[RequireComponent(typeof(RectTransform))]
[AddComponentMenu("UI/Extensions/Sliders/Range Slider", 34)]
public class RangeSlider : Selectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
{
public enum Direction

View File

@ -9,8 +9,8 @@ using UnityEngine.EventSystems;
namespace UnityEngine.UI.Extensions
{
// Stepper control
[AddComponentMenu("UI/Extensions/Stepper")]
[RequireComponent(typeof(RectTransform))]
[AddComponentMenu("UI/Extensions/Sliders/Stepper")]
public class Stepper : UIBehaviour
{
private Selectable[] _sides;

View File

@ -0,0 +1,50 @@
///Credit brogan89
///Sourced from - https://github.com/brogan89/MinMaxSlider
using System;
namespace UnityEngine.UI.Extensions
{
[Serializable]
public struct MinMaxValues
{
/// <summary>
/// Floating point tolerance
/// </summary>
public const float FLOAT_TOL = 0.01f;
public float minValue, maxValue, minLimit, maxLimit;
public static MinMaxValues DEFUALT = new MinMaxValues(25, 75, 0, 100);
public MinMaxValues(float minValue, float maxValue, float minLimit, float maxLimit)
{
this.minValue = minValue;
this.maxValue = maxValue;
this.minLimit = minLimit;
this.maxLimit = maxLimit;
}
/// <summary>
/// Constructor for when values equal limits
/// </summary>
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
public MinMaxValues(float minValue, float maxValue)
{
this.minValue = minValue;
this.maxValue = maxValue;
this.minLimit = minValue;
this.maxLimit = maxValue;
}
public bool IsAtMinAndMax()
{
return Math.Abs(minValue - minLimit) < FLOAT_TOL && Math.Abs(maxValue - maxLimit) < FLOAT_TOL;
}
public override string ToString()
{
return $"Values(min:{minValue}, max:{maxValue}) | Limits(min:{minLimit}, max:{maxLimit})";
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 44af036687d1cdb4da5686d3431d2c3c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: