Stepper.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. /// Credit David Gileadi
  2. /// Sourced from - https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/pull-requests/11
  3. using System;
  4. using System.Collections;
  5. using UnityEngine.Events;
  6. using UnityEngine.EventSystems;
  7. namespace UnityEngine.UI.Extensions
  8. {
  9. // Stepper control
  10. [AddComponentMenu("UI/Extensions/Stepper")]
  11. [RequireComponent(typeof(RectTransform))]
  12. public class Stepper : UIBehaviour
  13. {
  14. private Selectable[] _sides;
  15. [SerializeField]
  16. [Tooltip("The current step value of the control")]
  17. private int _value = 0;
  18. [SerializeField]
  19. [Tooltip("The minimum step value allowed by the control. When reached it will disable the '-' button")]
  20. private int _minimum = 0;
  21. [SerializeField]
  22. [Tooltip("The maximum step value allowed by the control. When reached it will disable the '+' button")]
  23. private int _maximum = 100;
  24. [SerializeField]
  25. [Tooltip("The step increment used to increment / decrement the step value")]
  26. private int _step = 1;
  27. [SerializeField]
  28. [Tooltip("Does the step value loop around from end to end")]
  29. private bool _wrap = false;
  30. [SerializeField]
  31. [Tooltip("A GameObject with an Image to use as a separator between segments. Size of the RectTransform will determine the size of the separator used.\nNote, make sure to disable the separator GO so that it does not affect the scene")]
  32. private Graphic _separator;
  33. private float _separatorWidth = 0;
  34. private float separatorWidth
  35. {
  36. get
  37. {
  38. if (_separatorWidth == 0 && separator)
  39. {
  40. _separatorWidth = separator.rectTransform.rect.width;
  41. var image = separator.GetComponent<Image>();
  42. if (image)
  43. _separatorWidth /= image.pixelsPerUnit;
  44. }
  45. return _separatorWidth;
  46. }
  47. }
  48. // Event delegates triggered on click.
  49. [SerializeField]
  50. private StepperValueChangedEvent _onValueChanged = new StepperValueChangedEvent();
  51. [Serializable]
  52. public class StepperValueChangedEvent : UnityEvent<int> { }
  53. public Selectable[] sides
  54. {
  55. get
  56. {
  57. if (_sides == null || _sides.Length == 0)
  58. {
  59. _sides = GetSides();
  60. }
  61. return _sides;
  62. }
  63. }
  64. public int value { get { return _value; } set { _value = value; DisableAtExtremes(sides); } }
  65. public int minimum { get { return _minimum; } set { _minimum = value; DisableAtExtremes(sides); } }
  66. public int maximum { get { return _maximum; } set { _maximum = value; DisableAtExtremes(sides); } }
  67. public int step { get { return _step; } set { _step = value; } }
  68. public bool wrap { get { return _wrap; } set { _wrap = value; } }
  69. public Graphic separator { get { return _separator; } set { _separator = value; _separatorWidth = 0; LayoutSides(sides); } }
  70. public StepperValueChangedEvent onValueChanged
  71. {
  72. get { return _onValueChanged; }
  73. set { _onValueChanged = value; }
  74. }
  75. protected Stepper()
  76. { }
  77. #if UNITY_EDITOR
  78. protected override void OnValidate()
  79. {
  80. base.OnValidate();
  81. RecreateSprites(sides);
  82. if (separator)
  83. LayoutSides();
  84. if (!wrap)
  85. {
  86. DisableAtExtremes(sides);
  87. }
  88. }
  89. #endif
  90. protected override void Start()
  91. {
  92. if (isActiveAndEnabled)
  93. StartCoroutine(DelayedInit());
  94. }
  95. protected override void OnEnable()
  96. {
  97. StartCoroutine(DelayedInit());
  98. }
  99. IEnumerator DelayedInit()
  100. {
  101. yield return null;
  102. RecreateSprites(sides);
  103. }
  104. private Selectable[] GetSides()
  105. {
  106. var buttons = GetComponentsInChildren<Selectable>();
  107. if (buttons.Length != 2)
  108. {
  109. throw new InvalidOperationException("A stepper must have two Button children");
  110. }
  111. if (!wrap)
  112. {
  113. DisableAtExtremes(buttons);
  114. }
  115. LayoutSides(buttons);
  116. return buttons;
  117. }
  118. public void StepUp()
  119. {
  120. Step(step);
  121. }
  122. public void StepDown()
  123. {
  124. Step(-step);
  125. }
  126. private void Step(int amount)
  127. {
  128. value += amount;
  129. if (wrap)
  130. {
  131. if (value > maximum) value = minimum;
  132. if (value < minimum) value = maximum;
  133. }
  134. else
  135. {
  136. value = Math.Max(minimum, value);
  137. value = Math.Min(maximum, value);
  138. DisableAtExtremes(sides);
  139. }
  140. _onValueChanged.Invoke(value);
  141. }
  142. private void DisableAtExtremes(Selectable[] sides)
  143. {
  144. sides[0].interactable = wrap || value > minimum;
  145. sides[1].interactable = wrap || value < maximum;
  146. }
  147. private void RecreateSprites(Selectable[] sides)
  148. {
  149. for (int i = 0; i < 2; i++)
  150. {
  151. if (sides[i].image == null)
  152. continue;
  153. var sprite = CutSprite(sides[i].image.sprite, i == 0);
  154. var side = sides[i].GetComponent<StepperSide>();
  155. if (side)
  156. {
  157. side.cutSprite = sprite;
  158. }
  159. sides[i].image.overrideSprite = sprite;
  160. }
  161. }
  162. static internal Sprite CutSprite(Sprite sprite, bool leftmost)
  163. {
  164. if (sprite.border.x == 0 || sprite.border.z == 0)
  165. return sprite;
  166. var rect = sprite.rect;
  167. var border = sprite.border;
  168. if (leftmost)
  169. {
  170. rect.xMax = border.z;
  171. border.z = 0;
  172. }
  173. else
  174. {
  175. rect.xMin = border.x;
  176. border.x = 0;
  177. }
  178. return Sprite.Create(sprite.texture, rect, sprite.pivot, sprite.pixelsPerUnit, 0, SpriteMeshType.FullRect, border);
  179. }
  180. public void LayoutSides(Selectable[] sides = null)
  181. {
  182. sides = sides ?? this.sides;
  183. RecreateSprites(sides);
  184. //RectTransform transform = this.transform as RectTransform;
  185. //float width = (transform.rect.width / 2) - separatorWidth;
  186. //for (int i = 0; i < 2; i++)
  187. //{
  188. // float insetX = i == 0 ? 0 : width + separatorWidth;
  189. // var rectTransform = sides[i].GetComponent<RectTransform>();
  190. // rectTransform.anchorMin = Vector2.zero;
  191. // rectTransform.anchorMax = Vector2.zero;
  192. // rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left, insetX, width);
  193. // rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, 0, transform.rect.height);
  194. //}
  195. //if (separator)
  196. //{
  197. // var sepTransform = gameObject.transform.Find("Separator");
  198. // Graphic sep = (sepTransform != null) ? sepTransform.GetComponent<Graphic>() : (GameObject.Instantiate(separator.gameObject) as GameObject).GetComponent<Graphic>();
  199. // sep.gameObject.name = "Separator";
  200. // sep.gameObject.SetActive(true);
  201. // sep.rectTransform.SetParent(this.transform, false);
  202. // sep.rectTransform.anchorMin = Vector2.zero;
  203. // sep.rectTransform.anchorMax = Vector2.zero;
  204. // sep.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left, width, separatorWidth);
  205. // sep.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, 0, transform.rect.height);
  206. //}
  207. }
  208. }
  209. }