Tanoda
unity-ui-extensions/Runtime/Scripts/Controls/BoxSlider.cs
Go to the documentation of this file.
1
3
4using System;
7
9{
10 [RequireComponent(typeof(RectTransform))]
11 [AddComponentMenu("UI/Extensions/BoxSlider")]
12 public class BoxSlider : Selectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
13 {
14 public enum Direction
15 {
16 LeftToRight,
17 RightToLeft,
18 BottomToTop,
19 TopToBottom,
20 }
21
22 [Serializable]
23 public class BoxSliderEvent : UnityEvent<float, float> { }
24
25 [SerializeField]
26 private RectTransform m_HandleRect;
27 public RectTransform HandleRect { get { return m_HandleRect; } set { if (SetClass(ref m_HandleRect, value)) { UpdateCachedReferences(); UpdateVisuals(); } } }
28
29 [Space(6)]
30
31 [SerializeField]
32 private float m_MinValue = 0;
33 public float MinValue { get { return m_MinValue; } set { if (SetStruct(ref m_MinValue, value)) { SetX(m_ValueX); SetY(m_ValueY); UpdateVisuals(); } } }
34
35 [SerializeField]
36 private float m_MaxValue = 1;
37 public float MaxValue { get { return m_MaxValue; } set { if (SetStruct(ref m_MaxValue, value)) { SetX(m_ValueX); SetY(m_ValueY); UpdateVisuals(); } } }
38
39 [SerializeField]
40 private bool m_WholeNumbers = false;
41 public bool WholeNumbers { get { return m_WholeNumbers; } set { if (SetStruct(ref m_WholeNumbers, value)) { SetX(m_ValueX); SetY(m_ValueY); UpdateVisuals(); } } }
42
43 [SerializeField]
44 private float m_ValueX = 1f;
45 public float ValueX
46 {
47 get
48 {
49 if (WholeNumbers)
50 return Mathf.Round(m_ValueX);
51 return m_ValueX;
52 }
53 set
54 {
55 SetX(value);
56 }
57 }
58
59 public float NormalizedValueX
60 {
61 get
62 {
63 if (Mathf.Approximately(MinValue, MaxValue))
64 return 0;
65 return Mathf.InverseLerp(MinValue, MaxValue, ValueX);
66 }
67 set
68 {
69 this.ValueX = Mathf.Lerp(MinValue, MaxValue, value);
70 }
71 }
72
73 [SerializeField]
74 private float m_ValueY = 1f;
75 public float ValueY
76 {
77 get
78 {
79 if (WholeNumbers)
80 return Mathf.Round(m_ValueY);
81 return m_ValueY;
82 }
83 set
84 {
85 SetY(value);
86 }
87 }
88
89 public float NormalizedValueY
90 {
91 get
92 {
93 if (Mathf.Approximately(MinValue, MaxValue))
94 return 0;
95 return Mathf.InverseLerp(MinValue, MaxValue, ValueY);
96 }
97 set
98 {
99 this.ValueY = Mathf.Lerp(MinValue, MaxValue, value);
100 }
101 }
102
103 [Space(6)]
104
105 // Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers.
106 [SerializeField]
107 private BoxSliderEvent m_OnValueChanged = new BoxSliderEvent();
108 public BoxSliderEvent OnValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } }
109
110 // Private fields
111
112 private Transform m_HandleTransform;
113 private RectTransform m_HandleContainerRect;
114
115 // The offset from handle position to mouse down position
116 private Vector2 m_Offset = Vector2.zero;
117
118 private DrivenRectTransformTracker m_Tracker;
119
120 // Size of each step.
121 float StepSize { get { return WholeNumbers ? 1 : (MaxValue - MinValue) * 0.1f; } }
122
123 protected BoxSlider()
124 { }
125
126#if UNITY_EDITOR
127 protected override void OnValidate()
128 {
129 base.OnValidate();
130
131 if (WholeNumbers)
132 {
133 m_MinValue = Mathf.Round(m_MinValue);
134 m_MaxValue = Mathf.Round(m_MaxValue);
135 }
136 UpdateCachedReferences();
137 SetX(m_ValueX, false);
138 SetY(m_ValueY, false);
139 // Update rects since other things might affect them even if value didn't change.
140 if(!Application.isPlaying) UpdateVisuals();
141
142#if UNITY_2018_3_OR_NEWER
143 if (!Application.isPlaying)
144#else
145 var prefabType = UnityEditor.PrefabUtility.GetPrefabType(this);
146 if (prefabType != UnityEditor.PrefabType.Prefab && !Application.isPlaying)
147#endif
148 {
149 CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
150 }
151 }
152
153#endif // if UNITY_EDITOR
154
155 public virtual void Rebuild(CanvasUpdate executing)
156 {
157#if UNITY_EDITOR
158 if (executing == CanvasUpdate.Prelayout)
160#endif
161 }
162
163 public void LayoutComplete()
164 {
165
166 }
167
169 {
170
171 }
172
173 public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
174 {
175 if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
176 return false;
177
178 currentValue = newValue;
179 return true;
180 }
181
182 public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
183 {
184 if (currentValue.Equals(newValue))
185 return false;
186
187 currentValue = newValue;
188 return true;
189 }
190
191 protected override void OnEnable()
192 {
193 base.OnEnable();
194 UpdateCachedReferences();
195 SetX(m_ValueX, false);
196 SetY(m_ValueY, false);
197 // Update rects since they need to be initialized correctly.
198 UpdateVisuals();
199 }
200
201 protected override void OnDisable()
202 {
203 m_Tracker.Clear();
204 base.OnDisable();
205 }
206
207 void UpdateCachedReferences()
208 {
209
210 if (m_HandleRect)
211 {
212 m_HandleTransform = m_HandleRect.transform;
213 if (m_HandleTransform.parent != null)
214 m_HandleContainerRect = m_HandleTransform.parent.GetComponent<RectTransform>();
215 }
216 else
217 {
218 m_HandleContainerRect = null;
219 }
220 }
221
222 // Set the valueUpdate the visible Image.
223 void SetX(float input)
224 {
225 SetX(input, true);
226 }
227
228 void SetX(float input, bool sendCallback)
229 {
230 // Clamp the input
231 float newValue = Mathf.Clamp(input, MinValue, MaxValue);
232 if (WholeNumbers)
233 newValue = Mathf.Round(newValue);
234
235 // If the stepped value doesn't match the last one, it's time to update
236 if (m_ValueX == newValue)
237 return;
238
239 m_ValueX = newValue;
240 UpdateVisuals();
241 if (sendCallback)
242 m_OnValueChanged.Invoke(newValue, ValueY);
243 }
244
245 void SetY(float input)
246 {
247 SetY(input, true);
248 }
249
250 void SetY(float input, bool sendCallback)
251 {
252 // Clamp the input
253 float newValue = Mathf.Clamp(input, MinValue, MaxValue);
254 if (WholeNumbers)
255 newValue = Mathf.Round(newValue);
256
257 // If the stepped value doesn't match the last one, it's time to update
258 if (m_ValueY == newValue)
259 return;
260
261 m_ValueY = newValue;
262 UpdateVisuals();
263 if (sendCallback)
264 m_OnValueChanged.Invoke(ValueX, newValue);
265 }
266
267
268 protected override void OnRectTransformDimensionsChange()
269 {
270 base.OnRectTransformDimensionsChange();
271 UpdateVisuals();
272 }
273
274 enum Axis
275 {
276 Horizontal = 0,
277 Vertical = 1
278 }
279
280
281 // Force-update the slider. Useful if you've changed the properties and want it to update visually.
282 private void UpdateVisuals()
283 {
284#if UNITY_EDITOR
285 if (!Application.isPlaying)
286 UpdateCachedReferences();
287#endif
288
289 m_Tracker.Clear();
290
291
292 //to business!
293 if (m_HandleContainerRect != null)
294 {
295 m_Tracker.Add(this, m_HandleRect, DrivenTransformProperties.Anchors);
296 Vector2 anchorMin = Vector2.zero;
297 Vector2 anchorMax = Vector2.one;
298 anchorMin[0] = anchorMax[0] = (NormalizedValueX);
299 anchorMin[1] = anchorMax[1] = (NormalizedValueY);
300
301 if (Application.isPlaying)
302 {
303 m_HandleRect.anchorMin = anchorMin;
304 m_HandleRect.anchorMax = anchorMax;
305 }
306
307 }
308 }
309
310 // Update the slider's position based on the mouse.
311 void UpdateDrag(PointerEventData eventData, Camera cam)
312 {
313 RectTransform clickRect = m_HandleContainerRect;
314 if (clickRect != null && clickRect.rect.size[0] > 0)
315 {
316 Vector2 localCursor;
317 if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, eventData.position, cam, out localCursor))
318 return;
319 localCursor -= clickRect.rect.position;
320
321 float val = Mathf.Clamp01((localCursor - m_Offset)[0] / clickRect.rect.size[0]);
322 NormalizedValueX = (val);
323
324 float valY = Mathf.Clamp01((localCursor - m_Offset)[1] / clickRect.rect.size[1]);
325 NormalizedValueY = (valY);
326
327 }
328 }
329
330 private bool CanDrag(PointerEventData eventData)
331 {
332 return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
333 }
334
335 public override void OnPointerDown(PointerEventData eventData)
336 {
337 if (!CanDrag(eventData))
338 return;
339
340 base.OnPointerDown(eventData);
341
342 m_Offset = Vector2.zero;
343 if (m_HandleContainerRect != null && RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, eventData.position, eventData.enterEventCamera))
344 {
345 Vector2 localMousePos;
346 if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, eventData.position, eventData.pressEventCamera, out localMousePos))
347 m_Offset = localMousePos;
348 m_Offset.y = -m_Offset.y;
349 }
350 else
351 {
352 // Outside the slider handle - jump to this point instead
353 UpdateDrag(eventData, eventData.pressEventCamera);
354 }
355 }
356
357 public virtual void OnDrag(PointerEventData eventData)
358 {
359 if (!CanDrag(eventData))
360 return;
361
362 UpdateDrag(eventData, eventData.pressEventCamera);
363 }
364
365 public virtual void OnInitializePotentialDrag(PointerEventData eventData)
366 {
367 eventData.useDragThreshold = false;
368 }
369
370 }
371}
Credit Erdener Gonenc - @PixelEnvision.