Tanoda
HSVPicker/UtilityScripts/BoxSlider.cs
Go to the documentation of this file.
1using System;
2using UnityEditor;
5
7{
8 [AddComponentMenu("UI/BoxSlider", 35)]
9 [RequireComponent(typeof(RectTransform))]
10 public class BoxSlider : Selectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
11 {
12 public enum Direction
13 {
14 LeftToRight,
15 RightToLeft,
16 BottomToTop,
17 TopToBottom
18 }
19
20 [Serializable]
21 public class BoxSliderEvent : UnityEvent<float, float>
22 {
23 }
24
25 [SerializeField] private RectTransform m_HandleRect;
26
27 public RectTransform handleRect
28 {
29 get => m_HandleRect;
30 set
31 {
32 if (SetClass(ref m_HandleRect, value))
33 {
34 UpdateCachedReferences();
35 UpdateVisuals();
36 }
37 }
38 }
39
40 [Space(6)] [SerializeField] private float m_MinValue;
41
42 public float minValue
43 {
44 get => m_MinValue;
45 set
46 {
47 if (SetStruct(ref m_MinValue, value))
48 {
49 Set(m_Value);
50 SetY(m_ValueY);
51 UpdateVisuals();
52 }
53 }
54 }
55
56 [SerializeField] private float m_MaxValue = 1;
57
58 public float maxValue
59 {
60 get => m_MaxValue;
61 set
62 {
63 if (SetStruct(ref m_MaxValue, value))
64 {
65 Set(m_Value);
66 SetY(m_ValueY);
67 UpdateVisuals();
68 }
69 }
70 }
71
72 [SerializeField] private bool m_WholeNumbers;
73
74 public bool wholeNumbers
75 {
76 get => m_WholeNumbers;
77 set
78 {
79 if (SetStruct(ref m_WholeNumbers, value))
80 {
81 Set(m_Value);
82 SetY(m_ValueY);
83 UpdateVisuals();
84 }
85 }
86 }
87
88 [SerializeField] private float m_Value = 1f;
89
90 public float value
91 {
92 get
93 {
94 if (wholeNumbers)
95 return Mathf.Round(m_Value);
96 return m_Value;
97 }
98 set => Set(value);
99 }
100
101 public float normalizedValue
102 {
103 get
104 {
105 if (Mathf.Approximately(minValue, maxValue))
106 return 0;
107 return Mathf.InverseLerp(minValue, maxValue, value);
108 }
109 set => this.value = Mathf.Lerp(minValue, maxValue, value);
110 }
111
112 [SerializeField] private float m_ValueY = 1f;
113
114 public float valueY
115 {
116 get
117 {
118 if (wholeNumbers)
119 return Mathf.Round(m_ValueY);
120 return m_ValueY;
121 }
122 set => SetY(value);
123 }
124
125 public float normalizedValueY
126 {
127 get
128 {
129 if (Mathf.Approximately(minValue, maxValue))
130 return 0;
131 return Mathf.InverseLerp(minValue, maxValue, valueY);
132 }
133 set => valueY = Mathf.Lerp(minValue, maxValue, value);
134 }
135
136 [Space(6)]
137
138 // Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers.
139 [SerializeField]
140 private BoxSliderEvent m_OnValueChanged = new BoxSliderEvent();
141
143 {
144 get => m_OnValueChanged;
145 set => m_OnValueChanged = value;
146 }
147
148 // Private fields
149
150 //private Image m_FillImage;
151 //private Transform m_FillTransform;
152 //private RectTransform m_FillContainerRect;
153 private Transform m_HandleTransform;
154 private RectTransform m_HandleContainerRect;
155
156 // The offset from handle position to mouse down position
157 private Vector2 m_Offset = Vector2.zero;
158
159 private DrivenRectTransformTracker m_Tracker;
160
161 // Size of each step.
162 float stepSize => wholeNumbers ? 1 : (maxValue - minValue) * 0.1f;
163
164 protected BoxSlider()
165 {
166 }
167
168#if UNITY_EDITOR
169 protected override void OnValidate()
170 {
171 base.OnValidate();
172
173 if (wholeNumbers)
174 {
175 m_MinValue = Mathf.Round(m_MinValue);
176 m_MaxValue = Mathf.Round(m_MaxValue);
177 }
178
179 //Onvalidate is called before OnEnabled. We need to make sure not to touch any other objects before OnEnable is run.
180 if (IsActive())
181 {
182 UpdateCachedReferences();
183 Set(m_Value, false);
184 SetY(m_ValueY, false);
185 // Update rects since other things might affect them even if value didn't change.
186 UpdateVisuals();
187 }
188
189#if UNITY_2018_3_OR_NEWER
190
191 if (!PrefabUtility.IsPartOfPrefabAsset(this) && !Application.isPlaying)
192 CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
193
194#else
195 var prefabType = UnityEditor.PrefabUtility.GetPrefabType(this);
196 if (prefabType != UnityEditor.PrefabType.Prefab && !Application.isPlaying)
197 CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
198 #endif
199 }
200#endif // if UNITY_EDITOR
201
202 public virtual void Rebuild(CanvasUpdate executing)
203 {
204#if UNITY_EDITOR
205 if (executing == CanvasUpdate.Prelayout)
206 onValueChanged.Invoke(value, valueY);
207#endif
208 }
209
210 public void LayoutComplete()
211 {
212 }
213
215 {
216 }
217
218 public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
219 {
220 if (currentValue == null && newValue == null || currentValue != null && currentValue.Equals(newValue))
221 return false;
222
223 currentValue = newValue;
224 return true;
225 }
226
227 public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
228 {
229 if (currentValue.Equals(newValue))
230 return false;
231
232 currentValue = newValue;
233 return true;
234 }
235
236 protected override void OnEnable()
237 {
238 base.OnEnable();
239 UpdateCachedReferences();
240 Set(m_Value, false);
241 SetY(m_ValueY, false);
242 // Update rects since they need to be initialized correctly.
243 UpdateVisuals();
244 }
245
246 protected override void OnDisable()
247 {
248 m_Tracker.Clear();
249 base.OnDisable();
250 }
251
252 void UpdateCachedReferences()
253 {
254 if (m_HandleRect)
255 {
256 m_HandleTransform = m_HandleRect.transform;
257 if (m_HandleTransform.parent != null)
258 m_HandleContainerRect = m_HandleTransform.parent.GetComponent<RectTransform>();
259 }
260 else
261 {
262 m_HandleContainerRect = null;
263 }
264 }
265
266 // Set the valueUpdate the visible Image.
267 void Set(float input)
268 {
269 Set(input, true);
270 }
271
272 void Set(float input, bool sendCallback)
273 {
274 // Clamp the input
275 var newValue = Mathf.Clamp(input, minValue, maxValue);
276 if (wholeNumbers)
277 newValue = Mathf.Round(newValue);
278
279 // If the stepped value doesn't match the last one, it's time to update
280 if (m_Value.Equals(newValue))
281 return;
282
283 m_Value = newValue;
284 UpdateVisuals();
285 if (sendCallback)
286 m_OnValueChanged.Invoke(newValue, valueY);
287 }
288
289 void SetY(float input)
290 {
291 SetY(input, true);
292 }
293
294 void SetY(float input, bool sendCallback)
295 {
296 // Clamp the input
297 var newValue = Mathf.Clamp(input, minValue, maxValue);
298 if (wholeNumbers)
299 newValue = Mathf.Round(newValue);
300
301 // If the stepped value doesn't match the last one, it's time to update
302 if (m_ValueY.Equals(newValue))
303 return;
304
305 m_ValueY = newValue;
306 UpdateVisuals();
307 if (sendCallback)
308 m_OnValueChanged.Invoke(value, newValue);
309 }
310
311
312 protected override void OnRectTransformDimensionsChange()
313 {
314 base.OnRectTransformDimensionsChange();
315 UpdateVisuals();
316 }
317
318 enum Axis
319 {
320 Horizontal = 0,
321 Vertical = 1
322 }
323
324
325 // Force-update the slider. Useful if you've changed the properties and want it to update visually.
326 private void UpdateVisuals()
327 {
328#if UNITY_EDITOR
329 if (!Application.isPlaying)
330 UpdateCachedReferences();
331#endif
332
333 m_Tracker.Clear();
334
335
336 //to business!
337 if (m_HandleContainerRect != null)
338 {
339 m_Tracker.Add(this, m_HandleRect, DrivenTransformProperties.Anchors);
340 var anchorMin = Vector2.zero;
341 var anchorMax = Vector2.one;
342 anchorMin[0] = anchorMax[0] = normalizedValue;
343 anchorMin[1] = anchorMax[1] = normalizedValueY;
344
345 m_HandleRect.anchorMin = anchorMin;
346 m_HandleRect.anchorMax = anchorMax;
347 }
348 }
349
350 // Update the slider's position based on the mouse.
351 void UpdateDrag(PointerEventData eventData, Camera cam)
352 {
353 var clickRect = m_HandleContainerRect;
354 if (clickRect != null && clickRect.rect.size[0] > 0)
355 {
356 Vector2 localCursor;
357 if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, eventData.position, cam,
358 out localCursor))
359 return;
360 localCursor -= clickRect.rect.position;
361
362 var val = Mathf.Clamp01((localCursor - m_Offset)[0] / clickRect.rect.size[0]);
363 normalizedValue = val;
364
365 var valY = Mathf.Clamp01((localCursor - m_Offset)[1] / clickRect.rect.size[1]);
366 normalizedValueY = valY;
367 }
368 }
369
370 private bool MayDrag(PointerEventData eventData)
371 {
372 return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
373 }
374
375 public override void OnPointerDown(PointerEventData eventData)
376 {
377 if (!MayDrag(eventData))
378 return;
379
380 base.OnPointerDown(eventData);
381
382 m_Offset = Vector2.zero;
383 if (m_HandleContainerRect != null &&
384 RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, eventData.position,
385 eventData.enterEventCamera))
386 {
387 Vector2 localMousePos;
388 if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, eventData.position,
389 eventData.pressEventCamera, out localMousePos))
390 m_Offset = localMousePos;
391 m_Offset.y = -m_Offset.y;
392 }
393 else
394 {
395 // Outside the slider handle - jump to this point instead
396 UpdateDrag(eventData, eventData.pressEventCamera);
397 }
398 }
399
400 public virtual void OnDrag(PointerEventData eventData)
401 {
402 if (!MayDrag(eventData))
403 return;
404
405 UpdateDrag(eventData, eventData.pressEventCamera);
406 }
407
408 //public override void OnMove(AxisEventData eventData)
409 //{
410 // if (!IsActive() || !IsInteractable())
411 // {
412 // base.OnMove(eventData);
413 // return;
414 // }
415
416 // switch (eventData.moveDir)
417 // {
418 // case MoveDirection.Left:
419 // if (axis == Axis.Horizontal && FindSelectableOnLeft() == null) {
420 // Set(reverseValue ? value + stepSize : value - stepSize);
421 // SetY (reverseValue ? valueY + stepSize : valueY - stepSize);
422 // }
423 // else
424 // base.OnMove(eventData);
425 // break;
426 // case MoveDirection.Right:
427 // if (axis == Axis.Horizontal && FindSelectableOnRight() == null) {
428 // Set(reverseValue ? value - stepSize : value + stepSize);
429 // SetY(reverseValue ? valueY - stepSize : valueY + stepSize);
430 // }
431 // else
432 // base.OnMove(eventData);
433 // break;
434 // case MoveDirection.Up:
435 // if (axis == Axis.Vertical && FindSelectableOnUp() == null) {
436 // Set(reverseValue ? value - stepSize : value + stepSize);
437 // SetY(reverseValue ? valueY - stepSize : valueY + stepSize);
438 // }
439 // else
440 // base.OnMove(eventData);
441 // break;
442 // case MoveDirection.Down:
443 // if (axis == Axis.Vertical && FindSelectableOnDown() == null) {
444 // Set(reverseValue ? value + stepSize : value - stepSize);
445 // SetY(reverseValue ? valueY + stepSize : valueY - stepSize);
446 // }
447 // else
448 // base.OnMove(eventData);
449 // break;
450 // }
451 //}
452
453 //public override Selectable FindSelectableOnLeft()
454 //{
455 // if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal)
456 // return null;
457 // return base.FindSelectableOnLeft();
458 //}
459
460 //public override Selectable FindSelectableOnRight()
461 //{
462 // if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Horizontal)
463 // return null;
464 // return base.FindSelectableOnRight();
465 //}
466
467 //public override Selectable FindSelectableOnUp()
468 //{
469 // if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Vertical)
470 // return null;
471 // return base.FindSelectableOnUp();
472 //}
473
474 //public override Selectable FindSelectableOnDown()
475 //{
476 // if (navigation.mode == Navigation.Mode.Automatic && axis == Axis.Vertical)
477 // return null;
478 // return base.FindSelectableOnDown();
479 //}
480
481 public virtual void OnInitializePotentialDrag(PointerEventData eventData)
482 {
483 eventData.useDragThreshold = false;
484 }
485 }
486}
static bool SetStruct< T >(ref T currentValue, T newValue)
override void OnPointerDown(PointerEventData eventData)
static bool SetClass< T >(ref T currentValue, T newValue)
virtual void Rebuild(CanvasUpdate executing)
virtual void OnDrag(PointerEventData eventData)
virtual void OnInitializePotentialDrag(PointerEventData eventData)