Tanoda
RangeSlider.cs
Go to the documentation of this file.
1
5
6using System;
9
11{
12 [AddComponentMenu("UI/Extensions/Range Slider", 34)]
13 [ExecuteInEditMode]
14 [RequireComponent(typeof(RectTransform))]
15 public class RangeSlider : Selectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
16 {
17
18 [Serializable]
19 public class RangeSliderEvent : UnityEvent<float, float> { }
20
21 [SerializeField]
22 private RectTransform m_FillRect;
23
24 public RectTransform FillRect { get { return m_FillRect; } set { if (SetClass(ref m_FillRect, value)) { UpdateCachedReferences(); UpdateVisuals(); } } }
25
26 [SerializeField]
27 private RectTransform m_LowHandleRect;
28
29 public RectTransform LowHandleRect { get { return m_LowHandleRect; } set { if (SetClass(ref m_LowHandleRect, value)) { UpdateCachedReferences(); UpdateVisuals(); } } }
30
31 [SerializeField]
32 private RectTransform m_HighHandleRect;
33
34 public RectTransform HighHandleRect { get { return m_HighHandleRect; } set { if (SetClass(ref m_HighHandleRect, value)) { UpdateCachedReferences(); UpdateVisuals(); } } }
35
36 [Space]
37
38 [SerializeField]
39 private float m_MinValue = 0;
40
41 public float MinValue { get { return m_MinValue; } set { if (SetStruct(ref m_MinValue, value)) { SetLow(m_LowValue); SetHigh(m_HighValue); UpdateVisuals(); } } }
42
43
44 [SerializeField]
45 private float m_MaxValue = 1;
46
47 public float MaxValue { get { return m_MaxValue; } set { if (SetStruct(ref m_MaxValue, value)) { SetLow(m_LowValue); SetHigh(m_HighValue); UpdateVisuals(); } } }
48
49 [SerializeField]
50 private bool m_WholeNumbers = false;
51
52 public bool WholeNumbers { get { return m_WholeNumbers; } set { if (SetStruct(ref m_WholeNumbers, value)) { SetLow(m_LowValue); SetHigh(m_HighValue); UpdateVisuals(); } } }
53
54 [SerializeField]
55 private float m_LowValue;
56 public virtual float LowValue
57 {
58 get
59 {
60 if (WholeNumbers)
61 {
62 return Mathf.Round(m_LowValue);
63 }
64
65 return m_LowValue;
66 }
67 set
68 {
69 SetLow(value);
70 }
71 }
72
73 public float NormalizedLowValue
74 {
75 get
76 {
77 if (Mathf.Approximately(MinValue, MaxValue))
78 {
79 return 0;
80 }
81 return Mathf.InverseLerp(MinValue, MaxValue, LowValue);
82 }
83 set
84 {
85 this.LowValue = Mathf.Lerp(MinValue, MaxValue, value);
86 }
87 }
88
89
90 [SerializeField]
91 private float m_HighValue;
92 public virtual float HighValue
93 {
94 get
95 {
96 if (WholeNumbers)
97 {
98 return Mathf.Round(m_HighValue);
99 }
100
101 return m_HighValue;
102 }
103 set
104 {
105 SetHigh(value);
106 }
107 }
108
110 {
111 get
112 {
113 if (Mathf.Approximately(MinValue, MaxValue))
114 {
115 return 0;
116 }
117 return Mathf.InverseLerp(MinValue, MaxValue, HighValue);
118 }
119 set
120 {
121 this.HighValue = Mathf.Lerp(MinValue, MaxValue, value);
122 }
123 }
124
129 public virtual void SetValueWithoutNotify(float low, float high)
130 {
131 SetLow(low, false);
132 SetHigh(high, false);
133 }
134
135 [Space]
136
137 [SerializeField]
138 private RangeSliderEvent m_OnValueChanged = new RangeSliderEvent();
139
140 public RangeSliderEvent OnValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } }
141
142 // Private fields
143
147 private enum InteractionState
148 {
149 Low,
150 High,
151 Bar,
152 None
153 }
154
155 private InteractionState interactionState = InteractionState.None;
156
157 private Image m_FillImage;
158 private Transform m_FillTransform;
159 private RectTransform m_FillContainerRect;
160 private Transform m_HighHandleTransform;
161 private RectTransform m_HighHandleContainerRect;
162 private Transform m_LowHandleTransform;
163 private RectTransform m_LowHandleContainerRect;
164
165 // The offset from handle position to mouse down position
166 private Vector2 m_LowOffset = Vector2.zero;
167 // The offset from handle position to mouse down position
168 private Vector2 m_HighOffset = Vector2.zero;
169
170 private DrivenRectTransformTracker m_Tracker;
171
172 // This "delayed" mechanism is required for case 1037681.
173 private bool m_DelayedUpdateVisuals = false;
174
175 // Size of each step.
176 float StepSize { get { return WholeNumbers ? 1 : (MaxValue - MinValue) * 0.1f; } }
177
178 protected RangeSlider()
179 { }
180
181#if UNITY_EDITOR
182 protected override void OnValidate()
183 {
184 base.OnValidate();
185
186 if (WholeNumbers)
187 {
188 m_MinValue = Mathf.Round(m_MinValue);
189 m_MaxValue = Mathf.Round(m_MaxValue);
190 }
191
192 if (IsActive())
193 {
194 UpdateCachedReferences();
195 SetLow(m_LowValue, false);
196 SetHigh(m_HighValue, false);
197 //Update rects since other things might affect them even if value didn't change
198 m_DelayedUpdateVisuals = true;
199 }
200
201 if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) && !Application.isPlaying)
202 {
203 CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
204 }
205 }
206#endif
207
208 public virtual void Rebuild(CanvasUpdate executing)
209 {
210#if UNITY_EDITOR
211 if (executing == CanvasUpdate.Prelayout)
212 {
214 }
215#endif
216 }
217
221 public virtual void LayoutComplete()
222 { }
223
227 public virtual void GraphicUpdateComplete()
228 { }
229
230 public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
231 {
232 if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
233 return false;
234
235 currentValue = newValue;
236 return true;
237 }
238
239 public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
240 {
241 if (currentValue.Equals(newValue))
242 return false;
243
244 currentValue = newValue;
245 return true;
246 }
247
248 protected override void OnEnable()
249 {
250 base.OnEnable();
251 UpdateCachedReferences();
252 SetLow(LowValue, false);
253 SetHigh(HighValue, false);
254 // Update rects since they need to be initialized correctly.
255 UpdateVisuals();
256 }
257
258 protected override void OnDisable()
259 {
260 m_Tracker.Clear();
261 base.OnDisable();
262 }
263
268 protected virtual void Update()
269 {
270 if (m_DelayedUpdateVisuals)
271 {
272 m_DelayedUpdateVisuals = false;
273 UpdateVisuals();
274 }
275 }
276
277 protected override void OnDidApplyAnimationProperties()
278 {
279 base.OnDidApplyAnimationProperties();
280 }
281
282 void UpdateCachedReferences()
283 {
284 if (m_FillRect && m_FillRect != (RectTransform)transform)
285 {
286 m_FillTransform = m_FillRect.transform;
287 m_FillImage = m_FillRect.GetComponent<Image>();
288 if (m_FillTransform.parent != null)
289 m_FillContainerRect = m_FillTransform.parent.GetComponent<RectTransform>();
290 }
291 else
292 {
293 m_FillRect = null;
294 m_FillContainerRect = null;
295 m_FillImage = null;
296 }
297
298 if (m_HighHandleRect && m_HighHandleRect != (RectTransform)transform)
299 {
300 m_HighHandleTransform = m_HighHandleRect.transform;
301 if (m_HighHandleTransform.parent != null)
302 m_HighHandleContainerRect = m_HighHandleTransform.parent.GetComponent<RectTransform>();
303 }
304 else
305 {
306 m_HighHandleRect = null;
307 m_HighHandleContainerRect = null;
308 }
309
310 if (m_LowHandleRect && m_LowHandleRect != (RectTransform)transform)
311 {
312 m_LowHandleTransform = m_LowHandleRect.transform;
313 if (m_LowHandleTransform.parent != null)
314 {
315 m_LowHandleContainerRect = m_LowHandleTransform.parent.GetComponent<RectTransform>();
316 }
317 }
318 else
319 {
320 m_LowHandleRect = null;
321 m_LowHandleContainerRect = null;
322 }
323 }
324
325 void SetLow(float input)
326 {
327 SetLow(input, true);
328 }
329
330 protected virtual void SetLow(float input, bool sendCallback)
331 {
332 // Clamp the input
333 float newValue = Mathf.Clamp(input, MinValue, HighValue); //clamp between min and High
334 if (WholeNumbers)
335 {
336 newValue = Mathf.Round(newValue);
337 }
338
339 // If the stepped value doesn't match the last one, it's time to update
340 if (m_LowValue == newValue)
341 return;
342
343 m_LowValue = newValue;
344 UpdateVisuals();
345 if (sendCallback)
346 {
347 UISystemProfilerApi.AddMarker("RangeSlider.lowValue", this);
348 m_OnValueChanged.Invoke(newValue, HighValue);
349 }
350 }
351
352 void SetHigh(float input)
353 {
354 SetHigh(input, true);
355 }
356
357 protected virtual void SetHigh(float input, bool sendCallback)
358 {
359 // Clamp the input
360 float newValue = Mathf.Clamp(input, LowValue, MaxValue); //clamp between min and High
361 if (WholeNumbers)
362 {
363 newValue = Mathf.Round(newValue);
364 }
365
366 // If the stepped value doesn't match the last one, it's time to update
367 if (m_HighValue == newValue)
368 return;
369
370 m_HighValue = newValue;
371 UpdateVisuals();
372 if (sendCallback)
373 {
374 UISystemProfilerApi.AddMarker("RangeSlider.highValue", this);
375 m_OnValueChanged.Invoke(LowValue, newValue);
376 }
377 }
378
379
380 protected override void OnRectTransformDimensionsChange()
381 {
382 base.OnRectTransformDimensionsChange();
383
384 //This can be invoked before OnEnabled is called. So we shouldn't be accessing other objects, before OnEnable is called.
385 if (!IsActive())
386 return;
387
388 UpdateVisuals();
389 }
390
391
392 // Force-update the slider. Useful if you've changed the properties and want it to update visually.
393 private void UpdateVisuals()
394 {
395#if UNITY_EDITOR
396 if (!Application.isPlaying)
397 UpdateCachedReferences();
398#endif
399
400 m_Tracker.Clear();
401
402 if (m_FillContainerRect != null)
403 {
404 m_Tracker.Add(this, m_FillRect, DrivenTransformProperties.Anchors);
405 Vector2 anchorMin = Vector2.zero;
406 Vector2 anchorMax = Vector2.one;
407
408 //this is where some new magic must happen. Slider just uses a filled image
409 //and changes the % of fill. We must move the image anchors to be between the two handles.
410 anchorMin[0] = NormalizedLowValue;
411 anchorMax[0] = NormalizedHighValue;
412
413 m_FillRect.anchorMin = anchorMin;
414 m_FillRect.anchorMax = anchorMax;
415 }
416
417 if (m_LowHandleContainerRect != null)
418 {
419 m_Tracker.Add(this, m_LowHandleRect, DrivenTransformProperties.Anchors);
420 Vector2 anchorMin = Vector2.zero;
421 Vector2 anchorMax = Vector2.one;
422 anchorMin[0] = anchorMax[0] = NormalizedLowValue;
423 m_LowHandleRect.anchorMin = anchorMin;
424 m_LowHandleRect.anchorMax = anchorMax;
425 }
426
427 if (m_HighHandleContainerRect != null)
428 {
429 m_Tracker.Add(this, m_HighHandleRect, DrivenTransformProperties.Anchors);
430 Vector2 anchorMin = Vector2.zero;
431 Vector2 anchorMax = Vector2.one;
432 anchorMin[0] = anchorMax[0] = NormalizedHighValue;
433 m_HighHandleRect.anchorMin = anchorMin;
434 m_HighHandleRect.anchorMax = anchorMax;
435 }
436 }
437
438 // Update the slider's position based on the mouse.
439 void UpdateDrag(PointerEventData eventData, Camera cam)
440 {
441 //this needs to differ from slider in that we have two handles, and need to move the right one.
442 //and if it was neither handle, we will have a separate case where both handles move uniformly
443 //moving the entire range
444
445 //this is where we use our interationState
446 switch (interactionState)
447 {
448 case InteractionState.Low:
449 NormalizedLowValue = CalculateDrag(eventData, cam, m_LowHandleContainerRect, m_LowOffset);
450 break;
451 case InteractionState.High:
452 NormalizedHighValue = CalculateDrag(eventData, cam, m_HighHandleContainerRect, m_HighOffset);
453 break;
454 case InteractionState.Bar:
455 //special case
456 CalculateBarDrag(eventData, cam);
457 break;
458 case InteractionState.None:
459 break;
460 }
461 }
462
463 private float CalculateDrag(PointerEventData eventData, Camera cam, RectTransform containerRect, Vector2 offset)
464 {
465 RectTransform clickRect = containerRect ?? m_FillContainerRect;
466 if (clickRect != null && clickRect.rect.size[0] > 0)
467 {
468 Vector2 localCursor;
469 if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, eventData.position, cam, out localCursor))
470 {
471 return 0f;
472 }
473 localCursor -= clickRect.rect.position;
474
475 float val = Mathf.Clamp01((localCursor - offset)[0] / clickRect.rect.size[0]);
476
477 return val;
478 }
479 return 0;
480 }
481
482 private void CalculateBarDrag(PointerEventData eventData, Camera cam)
483 {
484 RectTransform clickRect = m_FillContainerRect;
485 if (clickRect != null && clickRect.rect.size[0] > 0)
486 {
487 Vector2 localCursor;
488 if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, eventData.position, cam, out localCursor))
489 {
490 return;
491 }
492 localCursor -= clickRect.rect.position;
493
494 //now we need to get the delta drag on the bar
495 //and move both the normalized low and high values by this amount
496 //but also check that neither is going beyond the bounds
498 {
499 //find the mid point on the current bar
500 float mid = (NormalizedHighValue + NormalizedLowValue)/2;
501 //find where the new mid point should be
502 float val = Mathf.Clamp01((localCursor)[0] / clickRect.rect.size[0]);
503 //calculate the delta
504 float delta = val - mid;
505 //check the clamp range
506 if (NormalizedLowValue + delta < 0)
507 {
508 delta = -NormalizedLowValue;
509 }
510 else if (NormalizedHighValue + delta > 1)
511 {
512 delta = 1 - NormalizedHighValue;
513 }
514
515 //adjust both ends
516 NormalizedLowValue += delta;
517 NormalizedHighValue += delta;
518 }
519 }
520 }
521
522 private bool MayDrag(PointerEventData eventData)
523 {
524 return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
525 }
526
527 public override void OnPointerDown(PointerEventData eventData)
528 {
529 if (!MayDrag(eventData))
530 return;
531
532
533 //HANDLE DRAG EVENTS
534 m_LowOffset = m_HighOffset = Vector2.zero;
535 Vector2 localMousePos;
536 if (m_HighHandleRect != null && RectTransformUtility.RectangleContainsScreenPoint(m_HighHandleRect, eventData.position, eventData.enterEventCamera))
537 {
538 //dragging the high value handle
539 if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HighHandleRect, eventData.position, eventData.pressEventCamera, out localMousePos))
540 {
541 m_HighOffset = localMousePos;
542 }
543 interactionState = InteractionState.High;
544 if (transition == Transition.ColorTint)
545 {
546 targetGraphic = m_HighHandleRect.GetComponent<Graphic>();
547 }
548 }
549 else if (m_LowHandleRect != null && RectTransformUtility.RectangleContainsScreenPoint(m_LowHandleRect, eventData.position, eventData.enterEventCamera))
550 {
551 //dragging the low value handle
552 if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_LowHandleRect, eventData.position, eventData.pressEventCamera, out localMousePos))
553 {
554 m_LowOffset = localMousePos;
555 }
556 interactionState = InteractionState.Low;
557 if (transition == Transition.ColorTint)
558 {
559 targetGraphic = m_LowHandleRect.GetComponent<Graphic>();
560 }
561 }
562 else
563 {
564 //outside the handles, move the entire slider along
565 UpdateDrag(eventData, eventData.pressEventCamera);
566 interactionState = InteractionState.Bar;
567 if (transition == Transition.ColorTint)
568 {
569 targetGraphic = m_FillImage;
570 }
571 }
572 base.OnPointerDown(eventData);
573 }
574
575 public virtual void OnDrag(PointerEventData eventData)
576 {
577 if (!MayDrag(eventData))
578 {
579 return;
580 }
581 UpdateDrag(eventData, eventData.pressEventCamera);
582 }
583
584 public override void OnPointerUp(PointerEventData eventData)
585 {
586 base.OnPointerUp(eventData);
587 interactionState = InteractionState.None;
588 }
589
590 public override void OnMove(AxisEventData eventData)
591 {
592 //this requires further investigation
593 }
594
595 public virtual void OnInitializePotentialDrag(PointerEventData eventData)
596 {
597 eventData.useDragThreshold = false;
598 }
599 }
600}
System.Drawing.Image Image
Definition: TestScript.cs:37
override void OnDidApplyAnimationProperties()
Definition: RangeSlider.cs:277
virtual void OnInitializePotentialDrag(PointerEventData eventData)
Definition: RangeSlider.cs:595
override void OnRectTransformDimensionsChange()
Definition: RangeSlider.cs:380
virtual void Rebuild(CanvasUpdate executing)
Definition: RangeSlider.cs:208
virtual void SetLow(float input, bool sendCallback)
Definition: RangeSlider.cs:330
static bool SetClass< T >(ref T currentValue, T newValue)
Definition: RangeSlider.cs:230
virtual void SetValueWithoutNotify(float low, float high)
Set the value of the slider without invoking onValueChanged callback.
Definition: RangeSlider.cs:129
override void OnPointerUp(PointerEventData eventData)
Definition: RangeSlider.cs:584
override void OnPointerDown(PointerEventData eventData)
Definition: RangeSlider.cs:527
virtual void GraphicUpdateComplete()
See ICanvasElement.GraphicUpdateComplete
Definition: RangeSlider.cs:227
virtual void SetHigh(float input, bool sendCallback)
Definition: RangeSlider.cs:357
static bool SetStruct< T >(ref T currentValue, T newValue)
Definition: RangeSlider.cs:239
virtual void OnDrag(PointerEventData eventData)
Definition: RangeSlider.cs:575
override void OnMove(AxisEventData eventData)
Definition: RangeSlider.cs:590
virtual void LayoutComplete()
See ICanvasElement.LayoutComplete
Definition: RangeSlider.cs:221
virtual void Update()
Update the rect based on the delayed update visuals. Got around issue of calling sendMessage from onV...
Definition: RangeSlider.cs:268
Credit Erdener Gonenc - @PixelEnvision.