Selectable.cs 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine.Serialization;
  4. using UnityEngine.EventSystems;
  5. namespace UnityEngine.UI
  6. {
  7. [AddComponentMenu("UI/Selectable", 70)]
  8. [ExecuteAlways]
  9. [SelectionBase]
  10. [DisallowMultipleComponent]
  11. /// <summary>
  12. /// Simple selectable object - derived from to create a selectable control.
  13. /// </summary>
  14. public class Selectable
  15. :
  16. UIBehaviour,
  17. IMoveHandler,
  18. IPointerDownHandler, IPointerUpHandler,
  19. IPointerEnterHandler, IPointerExitHandler,
  20. ISelectHandler, IDeselectHandler
  21. {
  22. protected static Selectable[] s_Selectables = new Selectable[10];
  23. protected static int s_SelectableCount = 0;
  24. private bool m_EnableCalled = false;
  25. /// <summary>
  26. /// Copy of the array of all the selectable objects currently active in the scene.
  27. /// </summary>
  28. /// <example>
  29. /// <code>
  30. /// using UnityEngine;
  31. /// using System.Collections;
  32. /// using UnityEngine.UI; // required when using UI elements in scripts
  33. ///
  34. /// public class Example : MonoBehaviour
  35. /// {
  36. /// //Displays the names of all selectable elements in the scene
  37. /// public void GetNames()
  38. /// {
  39. /// foreach (Selectable selectableUI in Selectable.allSelectablesArray)
  40. /// {
  41. /// Debug.Log(selectableUI.name);
  42. /// }
  43. /// }
  44. /// }
  45. /// </code>
  46. /// </example>
  47. public static Selectable[] allSelectablesArray
  48. {
  49. get
  50. {
  51. Selectable[] temp = new Selectable[s_SelectableCount];
  52. Array.Copy(s_Selectables, temp, s_SelectableCount);
  53. return temp;
  54. }
  55. }
  56. /// <summary>
  57. /// How many selectable elements are currently active.
  58. /// </summary>
  59. public static int allSelectableCount { get { return s_SelectableCount; } }
  60. /// <summary>
  61. /// A List instance of the allSelectablesArray to maintain API compatibility.
  62. /// </summary>
  63. [Obsolete("Replaced with allSelectablesArray to have better performance when disabling a element", false)]
  64. public static List<Selectable> allSelectables
  65. {
  66. get
  67. {
  68. return new List<Selectable>(allSelectablesArray);
  69. }
  70. }
  71. /// <summary>
  72. /// Non allocating version for getting the all selectables.
  73. /// If selectables.Length is less then s_SelectableCount only selectables.Length elments will be copied which
  74. /// could result in a incomplete list of elements.
  75. /// </summary>
  76. /// <param name="selectables">The array to be filled with current selectable objects</param>
  77. /// <returns>The number of element copied.</returns>
  78. /// <example>
  79. /// <code>
  80. /// using UnityEngine;
  81. /// using System.Collections;
  82. /// using UnityEngine.UI; // required when using UI elements in scripts
  83. ///
  84. /// public class Example : MonoBehaviour
  85. /// {
  86. /// Selectable[] m_Selectables = new Selectable[10];
  87. ///
  88. /// //Displays the names of all selectable elements in the scene
  89. /// public void GetNames()
  90. /// {
  91. /// if (m_Selectables.Length < Selectable.allSelectableCount)
  92. /// m_Selectables = new Selectable[Selectable.allSelectableCount];
  93. ///
  94. /// int count = Selectable.AllSelectablesNoAlloc(ref m_Selectables);
  95. ///
  96. /// for (int i = 0; i < count; ++i)
  97. /// {
  98. /// Debug.Log(m_Selectables[i].name);
  99. /// }
  100. /// }
  101. /// }
  102. /// </code>
  103. /// </example>
  104. public static int AllSelectablesNoAlloc(Selectable[] selectables)
  105. {
  106. int copyCount = selectables.Length < s_SelectableCount ? selectables.Length : s_SelectableCount;
  107. Array.Copy(s_Selectables, selectables, copyCount);
  108. return copyCount;
  109. }
  110. // Navigation information.
  111. [FormerlySerializedAs("navigation")]
  112. [SerializeField]
  113. private Navigation m_Navigation = Navigation.defaultNavigation;
  114. /// <summary>
  115. ///Transition mode for a Selectable.
  116. /// </summary>
  117. public enum Transition
  118. {
  119. /// <summary>
  120. /// No Transition.
  121. /// </summary>
  122. None,
  123. /// <summary>
  124. /// Use an color tint transition.
  125. /// </summary>
  126. ColorTint,
  127. /// <summary>
  128. /// Use a sprite swap transition.
  129. /// </summary>
  130. SpriteSwap,
  131. /// <summary>
  132. /// Use an animation transition.
  133. /// </summary>
  134. Animation
  135. }
  136. // Type of the transition that occurs when the button state changes.
  137. [FormerlySerializedAs("transition")]
  138. [SerializeField]
  139. private Transition m_Transition = Transition.ColorTint;
  140. // Colors used for a color tint-based transition.
  141. [FormerlySerializedAs("colors")]
  142. [SerializeField]
  143. private ColorBlock m_Colors = ColorBlock.defaultColorBlock;
  144. // Sprites used for a Image swap-based transition.
  145. [FormerlySerializedAs("spriteState")]
  146. [SerializeField]
  147. private SpriteState m_SpriteState;
  148. [FormerlySerializedAs("animationTriggers")]
  149. [SerializeField]
  150. private AnimationTriggers m_AnimationTriggers = new AnimationTriggers();
  151. [Tooltip("Can the Selectable be interacted with?")]
  152. [SerializeField]
  153. private bool m_Interactable = true;
  154. // Graphic that will be colored.
  155. [FormerlySerializedAs("highlightGraphic")]
  156. [FormerlySerializedAs("m_HighlightGraphic")]
  157. [SerializeField]
  158. private Graphic m_TargetGraphic;
  159. private bool m_GroupsAllowInteraction = true;
  160. protected int m_CurrentIndex = -1;
  161. /// <summary>
  162. /// The Navigation setting for this selectable object.
  163. /// </summary>
  164. /// <example>
  165. /// <code>
  166. /// using UnityEngine;
  167. /// using System.Collections;
  168. /// using UnityEngine.UI; // Required when Using UI elements.
  169. ///
  170. /// public class ExampleClass : MonoBehaviour
  171. /// {
  172. /// public Button button;
  173. ///
  174. /// void Start()
  175. /// {
  176. /// //Set the navigation to the default value. ("Automatic" is the default value).
  177. /// button.navigation = Navigation.defaultNavigation;
  178. /// }
  179. /// }
  180. /// </code>
  181. /// </example>
  182. public Navigation navigation { get { return m_Navigation; } set { if (SetPropertyUtility.SetStruct(ref m_Navigation, value)) OnSetProperty(); } }
  183. /// <summary>
  184. /// The type of transition that will be applied to the targetGraphic when the state changes.
  185. /// </summary>
  186. /// <example>
  187. /// <code>
  188. /// using UnityEngine;
  189. /// using System.Collections;
  190. /// using UnityEngine.UI; // Required when Using UI elements.
  191. ///
  192. /// public class ExampleClass : MonoBehaviour
  193. /// {
  194. /// public Button btnMain;
  195. ///
  196. /// void SomeFunction()
  197. /// {
  198. /// //Sets the main button's transition setting to "Color Tint".
  199. /// btnMain.transition = Selectable.Transition.ColorTint;
  200. /// }
  201. /// }
  202. /// </code>
  203. /// </example>
  204. public Transition transition { get { return m_Transition; } set { if (SetPropertyUtility.SetStruct(ref m_Transition, value)) OnSetProperty(); } }
  205. /// <summary>
  206. /// The ColorBlock for this selectable object.
  207. /// </summary>
  208. /// <remarks>
  209. /// Modifications will not be visible if transition is not ColorTint.
  210. /// </remarks>
  211. /// <example>
  212. /// <code>
  213. /// using UnityEngine;
  214. /// using System.Collections;
  215. /// using UnityEngine.UI; // Required when Using UI elements.
  216. ///
  217. /// public class ExampleClass : MonoBehaviour
  218. /// {
  219. /// public Button button;
  220. ///
  221. /// void Start()
  222. /// {
  223. /// //Resets the colors in the buttons transitions.
  224. /// button.colors = ColorBlock.defaultColorBlock;
  225. /// }
  226. /// }
  227. /// </code>
  228. /// </example>
  229. public ColorBlock colors { get { return m_Colors; } set { if (SetPropertyUtility.SetStruct(ref m_Colors, value)) OnSetProperty(); } }
  230. /// <summary>
  231. /// The SpriteState for this selectable object.
  232. /// </summary>
  233. /// <remarks>
  234. /// Modifications will not be visible if transition is not SpriteSwap.
  235. /// </remarks>
  236. /// <example>
  237. // <code>
  238. // using UnityEngine;
  239. // using System.Collections;
  240. // using UnityEngine.UI; // Required when Using UI elements.
  241. //
  242. // public class ExampleClass : MonoBehaviour
  243. // {
  244. // //Creates an instance of a sprite state (This includes the highlighted, pressed and disabled sprite.
  245. // public SpriteState sprState = new SpriteState();
  246. // public Button btnMain;
  247. //
  248. //
  249. // void Start()
  250. // {
  251. // //Assigns the new sprite states to the button.
  252. // btnMain.spriteState = sprState;
  253. // }
  254. // }
  255. // </code>
  256. // </example>
  257. public SpriteState spriteState { get { return m_SpriteState; } set { if (SetPropertyUtility.SetStruct(ref m_SpriteState, value)) OnSetProperty(); } }
  258. /// <summary>
  259. /// The AnimationTriggers for this selectable object.
  260. /// </summary>
  261. /// <remarks>
  262. /// Modifications will not be visible if transition is not Animation.
  263. /// </remarks>
  264. public AnimationTriggers animationTriggers { get { return m_AnimationTriggers; } set { if (SetPropertyUtility.SetClass(ref m_AnimationTriggers, value)) OnSetProperty(); } }
  265. /// <summary>
  266. /// Graphic that will be transitioned upon.
  267. /// </summary>
  268. /// <example>
  269. /// <code>
  270. /// using UnityEngine;
  271. /// using System.Collections;
  272. /// using UnityEngine.UI; // Required when Using UI elements.
  273. ///
  274. /// public class ExampleClass : MonoBehaviour
  275. /// {
  276. /// public Image newImage;
  277. /// public Button btnMain;
  278. ///
  279. /// void SomeFunction()
  280. /// {
  281. /// //Displays the sprite transitions on the image when the transition to Highlighted,pressed or disabled is made.
  282. /// btnMain.targetGraphic = newImage;
  283. /// }
  284. /// }
  285. /// </code>
  286. /// </example>
  287. public Graphic targetGraphic { get { return m_TargetGraphic; } set { if (SetPropertyUtility.SetClass(ref m_TargetGraphic, value)) OnSetProperty(); } }
  288. /// <summary>
  289. /// Is this object interactable.
  290. /// </summary>
  291. /// <example>
  292. /// <code>
  293. /// using UnityEngine;
  294. /// using System.Collections;
  295. /// using UnityEngine.UI; // required when using UI elements in scripts
  296. ///
  297. /// public class Example : MonoBehaviour
  298. /// {
  299. /// public Button startButton;
  300. /// public bool playersReady;
  301. ///
  302. ///
  303. /// void Update()
  304. /// {
  305. /// // checks if the players are ready and if the start button is useable
  306. /// if (playersReady == true && startButton.interactable == false)
  307. /// {
  308. /// //allows the start button to be used
  309. /// startButton.interactable = true;
  310. /// }
  311. /// }
  312. /// }
  313. /// </code>
  314. /// </example>
  315. public bool interactable
  316. {
  317. get { return m_Interactable; }
  318. set
  319. {
  320. if (SetPropertyUtility.SetStruct(ref m_Interactable, value))
  321. {
  322. if (!m_Interactable && EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject)
  323. EventSystem.current.SetSelectedGameObject(null);
  324. OnSetProperty();
  325. }
  326. }
  327. }
  328. private bool isPointerInside { get; set; }
  329. private bool isPointerDown { get; set; }
  330. private bool hasSelection { get; set; }
  331. protected Selectable()
  332. {}
  333. /// <summary>
  334. /// Convenience function that converts the referenced Graphic to a Image, if possible.
  335. /// </summary>
  336. public Image image
  337. {
  338. get { return m_TargetGraphic as Image; }
  339. set { m_TargetGraphic = value; }
  340. }
  341. /// <summary>
  342. /// Convenience function to get the Animator component on the GameObject.
  343. /// </summary>
  344. /// <example>
  345. /// <code>
  346. /// using UnityEngine;
  347. /// using System.Collections;
  348. /// using UnityEngine.UI; // Required when Using UI elements.
  349. ///
  350. /// public class ExampleClass : MonoBehaviour
  351. /// {
  352. /// private Animator buttonAnimator;
  353. /// public Button button;
  354. ///
  355. /// void Start()
  356. /// {
  357. /// //Assigns the "buttonAnimator" with the button's animator.
  358. /// buttonAnimator = button.animator;
  359. /// }
  360. /// }
  361. /// </code>
  362. /// </example>
  363. #if PACKAGE_ANIMATION
  364. public Animator animator
  365. {
  366. get { return GetComponent<Animator>(); }
  367. }
  368. #endif
  369. protected override void Awake()
  370. {
  371. if (m_TargetGraphic == null)
  372. m_TargetGraphic = GetComponent<Graphic>();
  373. }
  374. private readonly List<CanvasGroup> m_CanvasGroupCache = new List<CanvasGroup>();
  375. protected override void OnCanvasGroupChanged()
  376. {
  377. // Figure out if parent groups allow interaction
  378. // If no interaction is alowed... then we need
  379. // to not do that :)
  380. var groupAllowInteraction = true;
  381. Transform t = transform;
  382. while (t != null)
  383. {
  384. t.GetComponents(m_CanvasGroupCache);
  385. bool shouldBreak = false;
  386. for (var i = 0; i < m_CanvasGroupCache.Count; i++)
  387. {
  388. // if the parent group does not allow interaction
  389. // we need to break
  390. if (!m_CanvasGroupCache[i].interactable)
  391. {
  392. groupAllowInteraction = false;
  393. shouldBreak = true;
  394. }
  395. // if this is a 'fresh' group, then break
  396. // as we should not consider parents
  397. if (m_CanvasGroupCache[i].ignoreParentGroups)
  398. shouldBreak = true;
  399. }
  400. if (shouldBreak)
  401. break;
  402. t = t.parent;
  403. }
  404. if (groupAllowInteraction != m_GroupsAllowInteraction)
  405. {
  406. m_GroupsAllowInteraction = groupAllowInteraction;
  407. OnSetProperty();
  408. }
  409. }
  410. /// <summary>
  411. /// Is the object interactable.
  412. /// </summary>
  413. /// <example>
  414. /// <code>
  415. /// using UnityEngine;
  416. /// using System.Collections;
  417. /// using UnityEngine.UI; // required when using UI elements in scripts
  418. ///
  419. /// public class Example : MonoBehaviour
  420. /// {
  421. /// public Button startButton;
  422. ///
  423. /// void Update()
  424. /// {
  425. /// if (!startButton.IsInteractable())
  426. /// {
  427. /// Debug.Log("Start Button has been Disabled");
  428. /// }
  429. /// }
  430. /// }
  431. /// </code>
  432. /// </example>
  433. public virtual bool IsInteractable()
  434. {
  435. return m_GroupsAllowInteraction && m_Interactable;
  436. }
  437. // Call from unity if animation properties have changed
  438. protected override void OnDidApplyAnimationProperties()
  439. {
  440. OnSetProperty();
  441. }
  442. // Select on enable and add to the list.
  443. protected override void OnEnable()
  444. {
  445. //Check to avoid multiple OnEnable() calls for each selectable
  446. if (m_EnableCalled)
  447. return;
  448. base.OnEnable();
  449. if (s_SelectableCount == s_Selectables.Length)
  450. {
  451. Selectable[] temp = new Selectable[s_Selectables.Length * 2];
  452. Array.Copy(s_Selectables, temp, s_Selectables.Length);
  453. s_Selectables = temp;
  454. }
  455. if (EventSystem.current && EventSystem.current.currentSelectedGameObject == gameObject)
  456. {
  457. hasSelection = true;
  458. }
  459. m_CurrentIndex = s_SelectableCount;
  460. s_Selectables[m_CurrentIndex] = this;
  461. s_SelectableCount++;
  462. isPointerDown = false;
  463. DoStateTransition(currentSelectionState, true);
  464. m_EnableCalled = true;
  465. }
  466. protected override void OnTransformParentChanged()
  467. {
  468. base.OnTransformParentChanged();
  469. // If our parenting changes figure out if we are under a new CanvasGroup.
  470. OnCanvasGroupChanged();
  471. }
  472. private void OnSetProperty()
  473. {
  474. #if UNITY_EDITOR
  475. if (!Application.isPlaying)
  476. DoStateTransition(currentSelectionState, true);
  477. else
  478. #endif
  479. DoStateTransition(currentSelectionState, false);
  480. }
  481. // Remove from the list.
  482. protected override void OnDisable()
  483. {
  484. //Check to avoid multiple OnDisable() calls for each selectable
  485. if (!m_EnableCalled)
  486. return;
  487. s_SelectableCount--;
  488. // Update the last elements index to be this index
  489. s_Selectables[s_SelectableCount].m_CurrentIndex = m_CurrentIndex;
  490. // Swap the last element and this element
  491. s_Selectables[m_CurrentIndex] = s_Selectables[s_SelectableCount];
  492. // null out last element.
  493. s_Selectables[s_SelectableCount] = null;
  494. InstantClearState();
  495. base.OnDisable();
  496. m_EnableCalled = false;
  497. }
  498. #if UNITY_EDITOR
  499. protected override void OnValidate()
  500. {
  501. base.OnValidate();
  502. m_Colors.fadeDuration = Mathf.Max(m_Colors.fadeDuration, 0.0f);
  503. // OnValidate can be called before OnEnable, this makes it unsafe to access other components
  504. // since they might not have been initialized yet.
  505. // OnSetProperty potentially access Animator or Graphics. (case 618186)
  506. if (isActiveAndEnabled)
  507. {
  508. if (!interactable && EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject)
  509. EventSystem.current.SetSelectedGameObject(null);
  510. // Need to clear out the override image on the target...
  511. DoSpriteSwap(null);
  512. // If the transition mode got changed, we need to clear all the transitions, since we don't know what the old transition mode was.
  513. StartColorTween(Color.white, true);
  514. TriggerAnimation(m_AnimationTriggers.normalTrigger);
  515. // And now go to the right state.
  516. DoStateTransition(currentSelectionState, true);
  517. }
  518. }
  519. protected override void Reset()
  520. {
  521. m_TargetGraphic = GetComponent<Graphic>();
  522. }
  523. #endif // if UNITY_EDITOR
  524. protected SelectionState currentSelectionState
  525. {
  526. get
  527. {
  528. if (!IsInteractable())
  529. return SelectionState.Disabled;
  530. if (isPointerDown)
  531. return SelectionState.Pressed;
  532. if (hasSelection)
  533. return SelectionState.Selected;
  534. if (isPointerInside)
  535. return SelectionState.Highlighted;
  536. return SelectionState.Normal;
  537. }
  538. }
  539. /// <summary>
  540. /// Clear any internal state from the Selectable (used when disabling).
  541. /// </summary>
  542. protected virtual void InstantClearState()
  543. {
  544. string triggerName = m_AnimationTriggers.normalTrigger;
  545. isPointerInside = false;
  546. isPointerDown = false;
  547. hasSelection = false;
  548. switch (m_Transition)
  549. {
  550. case Transition.ColorTint:
  551. StartColorTween(Color.white, true);
  552. break;
  553. case Transition.SpriteSwap:
  554. DoSpriteSwap(null);
  555. break;
  556. case Transition.Animation:
  557. TriggerAnimation(triggerName);
  558. break;
  559. }
  560. }
  561. /// <summary>
  562. /// Transition the Selectable to the entered state.
  563. /// </summary>
  564. /// <param name="state">State to transition to</param>
  565. /// <param name="instant">Should the transition occur instantly.</param>
  566. protected virtual void DoStateTransition(SelectionState state, bool instant)
  567. {
  568. if (!gameObject.activeInHierarchy)
  569. return;
  570. Color tintColor;
  571. Sprite transitionSprite;
  572. string triggerName;
  573. switch (state)
  574. {
  575. case SelectionState.Normal:
  576. tintColor = m_Colors.normalColor;
  577. transitionSprite = null;
  578. triggerName = m_AnimationTriggers.normalTrigger;
  579. break;
  580. case SelectionState.Highlighted:
  581. tintColor = m_Colors.highlightedColor;
  582. transitionSprite = m_SpriteState.highlightedSprite;
  583. triggerName = m_AnimationTriggers.highlightedTrigger;
  584. break;
  585. case SelectionState.Pressed:
  586. tintColor = m_Colors.pressedColor;
  587. transitionSprite = m_SpriteState.pressedSprite;
  588. triggerName = m_AnimationTriggers.pressedTrigger;
  589. break;
  590. case SelectionState.Selected:
  591. tintColor = m_Colors.selectedColor;
  592. transitionSprite = m_SpriteState.selectedSprite;
  593. triggerName = m_AnimationTriggers.selectedTrigger;
  594. break;
  595. case SelectionState.Disabled:
  596. tintColor = m_Colors.disabledColor;
  597. transitionSprite = m_SpriteState.disabledSprite;
  598. triggerName = m_AnimationTriggers.disabledTrigger;
  599. break;
  600. default:
  601. tintColor = Color.black;
  602. transitionSprite = null;
  603. triggerName = string.Empty;
  604. break;
  605. }
  606. switch (m_Transition)
  607. {
  608. case Transition.ColorTint:
  609. StartColorTween(tintColor * m_Colors.colorMultiplier, instant);
  610. break;
  611. case Transition.SpriteSwap:
  612. DoSpriteSwap(transitionSprite);
  613. break;
  614. case Transition.Animation:
  615. TriggerAnimation(triggerName);
  616. break;
  617. }
  618. }
  619. /// <summary>
  620. /// An enumeration of selected states of objects
  621. /// </summary>
  622. protected enum SelectionState
  623. {
  624. /// <summary>
  625. /// The UI object can be selected.
  626. /// </summary>
  627. Normal,
  628. /// <summary>
  629. /// The UI object is highlighted.
  630. /// </summary>
  631. Highlighted,
  632. /// <summary>
  633. /// The UI object is pressed.
  634. /// </summary>
  635. Pressed,
  636. /// <summary>
  637. /// The UI object is selected
  638. /// </summary>
  639. Selected,
  640. /// <summary>
  641. /// The UI object cannot be selected.
  642. /// </summary>
  643. Disabled,
  644. }
  645. // Selection logic
  646. /// <summary>
  647. /// Finds the selectable object next to this one.
  648. /// </summary>
  649. /// <remarks>
  650. /// The direction is determined by a Vector3 variable.
  651. /// </remarks>
  652. /// <param name="dir">The direction in which to search for a neighbouring Selectable object.</param>
  653. /// <returns>The neighbouring Selectable object. Null if none found.</returns>
  654. /// <example>
  655. /// <code>
  656. /// using UnityEngine;
  657. /// using System.Collections;
  658. /// using UnityEngine.UI; // required when using UI elements in scripts
  659. ///
  660. /// public class ExampleClass : MonoBehaviour
  661. /// {
  662. /// //Sets the direction as "Up" (Y is in positive).
  663. /// public Vector3 direction = new Vector3(0, 1, 0);
  664. /// public Button btnMain;
  665. ///
  666. /// public void Start()
  667. /// {
  668. /// //Finds and assigns the selectable above the main button
  669. /// Selectable newSelectable = btnMain.FindSelectable(direction);
  670. ///
  671. /// Debug.Log(newSelectable.name);
  672. /// }
  673. /// }
  674. /// </code>
  675. /// </example>
  676. public Selectable FindSelectable(Vector3 dir)
  677. {
  678. dir = dir.normalized;
  679. Vector3 localDir = Quaternion.Inverse(transform.rotation) * dir;
  680. Vector3 pos = transform.TransformPoint(GetPointOnRectEdge(transform as RectTransform, localDir));
  681. float maxScore = Mathf.NegativeInfinity;
  682. float maxFurthestScore = Mathf.NegativeInfinity;
  683. float score = 0;
  684. bool wantsWrapAround = navigation.wrapAround && (m_Navigation.mode == Navigation.Mode.Vertical || m_Navigation.mode == Navigation.Mode.Horizontal);
  685. Selectable bestPick = null;
  686. Selectable bestFurthestPick = null;
  687. for (int i = 0; i < s_SelectableCount; ++i)
  688. {
  689. Selectable sel = s_Selectables[i];
  690. if (sel == this)
  691. continue;
  692. if (!sel.IsInteractable() || sel.navigation.mode == Navigation.Mode.None)
  693. continue;
  694. #if UNITY_EDITOR
  695. // Apart from runtime use, FindSelectable is used by custom editors to
  696. // draw arrows between different selectables. For scene view cameras,
  697. // only selectables in the same stage should be considered.
  698. if (Camera.current != null && !UnityEditor.SceneManagement.StageUtility.IsGameObjectRenderedByCamera(sel.gameObject, Camera.current))
  699. continue;
  700. #endif
  701. var selRect = sel.transform as RectTransform;
  702. Vector3 selCenter = selRect != null ? (Vector3)selRect.rect.center : Vector3.zero;
  703. Vector3 myVector = sel.transform.TransformPoint(selCenter) - pos;
  704. // Value that is the distance out along the direction.
  705. float dot = Vector3.Dot(dir, myVector);
  706. // If element is in wrong direction and we have wrapAround enabled check and cache it if furthest away.
  707. if (wantsWrapAround && dot < 0)
  708. {
  709. score = -dot * myVector.sqrMagnitude;
  710. if (score > maxFurthestScore)
  711. {
  712. maxFurthestScore = score;
  713. bestFurthestPick = sel;
  714. }
  715. continue;
  716. }
  717. // Skip elements that are in the wrong direction or which have zero distance.
  718. // This also ensures that the scoring formula below will not have a division by zero error.
  719. if (dot <= 0)
  720. continue;
  721. // This scoring function has two priorities:
  722. // - Score higher for positions that are closer.
  723. // - Score higher for positions that are located in the right direction.
  724. // This scoring function combines both of these criteria.
  725. // It can be seen as this:
  726. // Dot (dir, myVector.normalized) / myVector.magnitude
  727. // The first part equals 1 if the direction of myVector is the same as dir, and 0 if it's orthogonal.
  728. // The second part scores lower the greater the distance is by dividing by the distance.
  729. // The formula below is equivalent but more optimized.
  730. //
  731. // If a given score is chosen, the positions that evaluate to that score will form a circle
  732. // that touches pos and whose center is located along dir. A way to visualize the resulting functionality is this:
  733. // From the position pos, blow up a circular balloon so it grows in the direction of dir.
  734. // The first Selectable whose center the circular balloon touches is the one that's chosen.
  735. score = dot / myVector.sqrMagnitude;
  736. if (score > maxScore)
  737. {
  738. maxScore = score;
  739. bestPick = sel;
  740. }
  741. }
  742. if (wantsWrapAround && null == bestPick) return bestFurthestPick;
  743. return bestPick;
  744. }
  745. private static Vector3 GetPointOnRectEdge(RectTransform rect, Vector2 dir)
  746. {
  747. if (rect == null)
  748. return Vector3.zero;
  749. if (dir != Vector2.zero)
  750. dir /= Mathf.Max(Mathf.Abs(dir.x), Mathf.Abs(dir.y));
  751. dir = rect.rect.center + Vector2.Scale(rect.rect.size, dir * 0.5f);
  752. return dir;
  753. }
  754. // Convenience function -- change the selection to the specified object if it's not null and happens to be active.
  755. void Navigate(AxisEventData eventData, Selectable sel)
  756. {
  757. if (sel != null && sel.IsActive())
  758. eventData.selectedObject = sel.gameObject;
  759. }
  760. /// <summary>
  761. /// Find the selectable object to the left of this one.
  762. /// </summary>
  763. /// <example>
  764. /// <code>
  765. /// using UnityEngine;
  766. /// using System.Collections;
  767. /// using UnityEngine.UI; // required when using UI elements in scripts
  768. ///
  769. /// public class ExampleClass : MonoBehaviour
  770. /// {
  771. /// public Button btnMain;
  772. ///
  773. /// // Disables the selectable UI element directly to the left of the Start Button
  774. /// public void IgnoreSelectables()
  775. /// {
  776. /// //Finds the selectable UI element to the left the start button and assigns it to a variable of type "Selectable"
  777. /// Selectable secondButton = startButton.FindSelectableOnLeft();
  778. /// //Disables interaction with the selectable UI element
  779. /// secondButton.interactable = false;
  780. /// }
  781. /// }
  782. /// </code>
  783. /// </example>
  784. public virtual Selectable FindSelectableOnLeft()
  785. {
  786. if (m_Navigation.mode == Navigation.Mode.Explicit)
  787. {
  788. return m_Navigation.selectOnLeft;
  789. }
  790. if ((m_Navigation.mode & Navigation.Mode.Horizontal) != 0)
  791. {
  792. return FindSelectable(transform.rotation * Vector3.left);
  793. }
  794. return null;
  795. }
  796. /// <summary>
  797. /// Find the selectable object to the right of this one.
  798. /// </summary>
  799. /// <example>
  800. /// <code>
  801. /// using UnityEngine;
  802. /// using System.Collections;
  803. /// using UnityEngine.UI; // required when using UI elements in scripts
  804. ///
  805. /// public class ExampleClass : MonoBehaviour
  806. /// {
  807. /// public Button btnMain;
  808. ///
  809. /// // Disables the selectable UI element directly to the right the Start Button
  810. /// public void IgnoreSelectables()
  811. /// {
  812. /// //Finds the selectable UI element to the right the start button and assigns it to a variable of type "Selectable"
  813. /// Selectable secondButton = startButton.FindSelectableOnRight();
  814. /// //Disables interaction with the selectable UI element
  815. /// secondButton.interactable = false;
  816. /// }
  817. /// }
  818. /// </code>
  819. /// </example>
  820. public virtual Selectable FindSelectableOnRight()
  821. {
  822. if (m_Navigation.mode == Navigation.Mode.Explicit)
  823. {
  824. return m_Navigation.selectOnRight;
  825. }
  826. if ((m_Navigation.mode & Navigation.Mode.Horizontal) != 0)
  827. {
  828. return FindSelectable(transform.rotation * Vector3.right);
  829. }
  830. return null;
  831. }
  832. /// <summary>
  833. /// The Selectable object above current
  834. /// </summary>
  835. /// <example>
  836. /// <code>
  837. /// using UnityEngine;
  838. /// using System.Collections;
  839. /// using UnityEngine.UI; // required when using UI elements in scripts
  840. ///
  841. /// public class ExampleClass : MonoBehaviour
  842. /// {
  843. /// public Button btnMain;
  844. ///
  845. /// // Disables the selectable UI element directly above the Start Button
  846. /// public void IgnoreSelectables()
  847. /// {
  848. /// //Finds the selectable UI element above the start button and assigns it to a variable of type "Selectable"
  849. /// Selectable secondButton = startButton.FindSelectableOnUp();
  850. /// //Disables interaction with the selectable UI element
  851. /// secondButton.interactable = false;
  852. /// }
  853. /// }
  854. /// </code>
  855. /// </example>
  856. public virtual Selectable FindSelectableOnUp()
  857. {
  858. if (m_Navigation.mode == Navigation.Mode.Explicit)
  859. {
  860. return m_Navigation.selectOnUp;
  861. }
  862. if ((m_Navigation.mode & Navigation.Mode.Vertical) != 0)
  863. {
  864. return FindSelectable(transform.rotation * Vector3.up);
  865. }
  866. return null;
  867. }
  868. /// <summary>
  869. /// Find the selectable object below this one.
  870. /// </summary>
  871. /// <example>
  872. /// <code>
  873. /// using UnityEngine;
  874. /// using System.Collections;
  875. /// using UnityEngine.UI; // required when using UI elements in scripts
  876. ///
  877. /// public class Example : MonoBehaviour
  878. /// {
  879. /// public Button startButton;
  880. ///
  881. /// // Disables the selectable UI element directly below the Start Button
  882. /// public void IgnoreSelectables()
  883. /// {
  884. /// //Finds the selectable UI element below the start button and assigns it to a variable of type "Selectable"
  885. /// Selectable secondButton = startButton.FindSelectableOnDown();
  886. /// //Disables interaction with the selectable UI element
  887. /// secondButton.interactable = false;
  888. /// }
  889. /// }
  890. /// </code>
  891. /// </example>
  892. public virtual Selectable FindSelectableOnDown()
  893. {
  894. if (m_Navigation.mode == Navigation.Mode.Explicit)
  895. {
  896. return m_Navigation.selectOnDown;
  897. }
  898. if ((m_Navigation.mode & Navigation.Mode.Vertical) != 0)
  899. {
  900. return FindSelectable(transform.rotation * Vector3.down);
  901. }
  902. return null;
  903. }
  904. /// <summary>
  905. /// Determine in which of the 4 move directions the next selectable object should be found.
  906. /// </summary>
  907. /// <example>
  908. /// <code>
  909. /// using UnityEngine;
  910. /// using System.Collections;
  911. /// using UnityEngine.UI;
  912. /// using UnityEngine.EventSystems;// Required when using Event data.
  913. ///
  914. /// public class ExampleClass : MonoBehaviour, IMoveHandler
  915. /// {
  916. /// //When the focus moves to another selectable object, Invoke this Method.
  917. /// public void OnMove(AxisEventData eventData)
  918. /// {
  919. /// //Assigns the move direction and the raw input vector representing the direction from the event data.
  920. /// MoveDirection moveDir = eventData.moveDir;
  921. /// Vector2 moveVector = eventData.moveVector;
  922. ///
  923. /// //Displays the information in the console
  924. /// Debug.Log(moveDir + ", " + moveVector);
  925. /// }
  926. /// }
  927. /// </code>
  928. /// </example>
  929. public virtual void OnMove(AxisEventData eventData)
  930. {
  931. switch (eventData.moveDir)
  932. {
  933. case MoveDirection.Right:
  934. Navigate(eventData, FindSelectableOnRight());
  935. break;
  936. case MoveDirection.Up:
  937. Navigate(eventData, FindSelectableOnUp());
  938. break;
  939. case MoveDirection.Left:
  940. Navigate(eventData, FindSelectableOnLeft());
  941. break;
  942. case MoveDirection.Down:
  943. Navigate(eventData, FindSelectableOnDown());
  944. break;
  945. }
  946. }
  947. void StartColorTween(Color targetColor, bool instant)
  948. {
  949. if (m_TargetGraphic == null)
  950. return;
  951. m_TargetGraphic.CrossFadeColor(targetColor, instant ? 0f : m_Colors.fadeDuration, true, true);
  952. }
  953. void DoSpriteSwap(Sprite newSprite)
  954. {
  955. if (image == null)
  956. return;
  957. image.overrideSprite = newSprite;
  958. }
  959. void TriggerAnimation(string triggername)
  960. {
  961. #if PACKAGE_ANIMATION
  962. if (transition != Transition.Animation || animator == null || !animator.isActiveAndEnabled || !animator.hasBoundPlayables || string.IsNullOrEmpty(triggername))
  963. return;
  964. animator.ResetTrigger(m_AnimationTriggers.normalTrigger);
  965. animator.ResetTrigger(m_AnimationTriggers.highlightedTrigger);
  966. animator.ResetTrigger(m_AnimationTriggers.pressedTrigger);
  967. animator.ResetTrigger(m_AnimationTriggers.selectedTrigger);
  968. animator.ResetTrigger(m_AnimationTriggers.disabledTrigger);
  969. animator.SetTrigger(triggername);
  970. #endif
  971. }
  972. /// <summary>
  973. /// Returns whether the selectable is currently 'highlighted' or not.
  974. /// </summary>
  975. /// <remarks>
  976. /// Use this to check if the selectable UI element is currently highlighted.
  977. /// </remarks>
  978. /// <example>
  979. /// <code>
  980. /// //Create a UI element. To do this go to Create>UI and select from the list. Attach this script to the UI GameObject to see this script working. The script also works with non-UI elements, but highlighting works better with UI.
  981. ///
  982. /// using UnityEngine;
  983. /// using UnityEngine.Events;
  984. /// using UnityEngine.EventSystems;
  985. /// using UnityEngine.UI;
  986. ///
  987. /// //Use the Selectable class as a base class to access the IsHighlighted method
  988. /// public class Example : Selectable
  989. /// {
  990. /// //Use this to check what Events are happening
  991. /// BaseEventData m_BaseEvent;
  992. ///
  993. /// void Update()
  994. /// {
  995. /// //Check if the GameObject is being highlighted
  996. /// if (IsHighlighted())
  997. /// {
  998. /// //Output that the GameObject was highlighted, or do something else
  999. /// Debug.Log("Selectable is Highlighted");
  1000. /// }
  1001. /// }
  1002. /// }
  1003. /// </code>
  1004. /// </example>
  1005. protected bool IsHighlighted()
  1006. {
  1007. if (!IsActive() || !IsInteractable())
  1008. return false;
  1009. return isPointerInside && !isPointerDown && !hasSelection;
  1010. }
  1011. /// <summary>
  1012. /// Whether the current selectable is being pressed.
  1013. /// </summary>
  1014. protected bool IsPressed()
  1015. {
  1016. if (!IsActive() || !IsInteractable())
  1017. return false;
  1018. return isPointerDown;
  1019. }
  1020. // Change the button to the correct state
  1021. private void EvaluateAndTransitionToSelectionState()
  1022. {
  1023. if (!IsActive() || !IsInteractable())
  1024. return;
  1025. DoStateTransition(currentSelectionState, false);
  1026. }
  1027. /// <summary>
  1028. /// Evaluate current state and transition to pressed state.
  1029. /// </summary>
  1030. /// <example>
  1031. /// <code>
  1032. /// using UnityEngine;
  1033. /// using System.Collections;
  1034. /// using UnityEngine.UI;
  1035. /// using UnityEngine.EventSystems;// Required when using Event data.
  1036. ///
  1037. /// public class ExampleClass : MonoBehaviour, IPointerDownHandler// required interface when using the OnPointerDown method.
  1038. /// {
  1039. /// //Do this when the mouse is clicked over the selectable object this script is attached to.
  1040. /// public void OnPointerDown(PointerEventData eventData)
  1041. /// {
  1042. /// Debug.Log(this.gameObject.name + " Was Clicked.");
  1043. /// }
  1044. /// }
  1045. /// </code>
  1046. /// </example>
  1047. public virtual void OnPointerDown(PointerEventData eventData)
  1048. {
  1049. if (eventData.button != PointerEventData.InputButton.Left)
  1050. return;
  1051. // Selection tracking
  1052. if (IsInteractable() && navigation.mode != Navigation.Mode.None && EventSystem.current != null)
  1053. EventSystem.current.SetSelectedGameObject(gameObject, eventData);
  1054. isPointerDown = true;
  1055. EvaluateAndTransitionToSelectionState();
  1056. }
  1057. /// <summary>
  1058. /// Evaluate eventData and transition to appropriate state.
  1059. /// </summary>
  1060. /// <example>
  1061. /// <code>
  1062. /// using UnityEngine;
  1063. /// using System.Collections;
  1064. /// using UnityEngine.UI;
  1065. /// using UnityEngine.EventSystems;// Required when using Event data.
  1066. ///
  1067. /// public class ExampleClass : MonoBehaviour, IPointerUpHandler, IPointerDownHandler// These are the interfaces the OnPointerUp method requires.
  1068. /// {
  1069. /// //OnPointerDown is also required to receive OnPointerUp callbacks
  1070. /// public void OnPointerDown(PointerEventData eventData)
  1071. /// {
  1072. /// }
  1073. ///
  1074. /// //Do this when the mouse click on this selectable UI object is released.
  1075. /// public void OnPointerUp(PointerEventData eventData)
  1076. /// {
  1077. /// Debug.Log("The mouse click was released");
  1078. /// }
  1079. /// }
  1080. /// </code>
  1081. /// </example>
  1082. public virtual void OnPointerUp(PointerEventData eventData)
  1083. {
  1084. if (eventData.button != PointerEventData.InputButton.Left)
  1085. return;
  1086. isPointerDown = false;
  1087. EvaluateAndTransitionToSelectionState();
  1088. }
  1089. /// <summary>
  1090. /// Evaluate current state and transition to appropriate state.
  1091. /// New state could be pressed or hover depending on pressed state.
  1092. /// </summary>
  1093. /// <example>
  1094. /// <code>
  1095. /// using UnityEngine;
  1096. /// using System.Collections;
  1097. /// using UnityEngine.UI;
  1098. /// using UnityEngine.EventSystems;// Required when using Event data.
  1099. ///
  1100. /// public class ExampleClass : MonoBehaviour, IPointerEnterHandler// required interface when using the OnPointerEnter method.
  1101. /// {
  1102. /// //Do this when the cursor enters the rect area of this selectable UI object.
  1103. /// public void OnPointerEnter(PointerEventData eventData)
  1104. /// {
  1105. /// Debug.Log("The cursor entered the selectable UI element.");
  1106. /// }
  1107. /// }
  1108. /// </code>
  1109. /// </example>
  1110. public virtual void OnPointerEnter(PointerEventData eventData)
  1111. {
  1112. isPointerInside = true;
  1113. EvaluateAndTransitionToSelectionState();
  1114. }
  1115. /// <summary>
  1116. /// Evaluate current state and transition to normal state.
  1117. /// </summary>
  1118. /// <example>
  1119. /// <code>
  1120. /// using UnityEngine;
  1121. /// using System.Collections;
  1122. /// using UnityEngine.UI;
  1123. /// using UnityEngine.EventSystems;// Required when using Event data.
  1124. ///
  1125. /// public class ExampleClass : MonoBehaviour, IPointerExitHandler// required interface when using the OnPointerExit method.
  1126. /// {
  1127. /// //Do this when the cursor exits the rect area of this selectable UI object.
  1128. /// public void OnPointerExit(PointerEventData eventData)
  1129. /// {
  1130. /// Debug.Log("The cursor exited the selectable UI element.");
  1131. /// }
  1132. /// }
  1133. /// </code>
  1134. /// </example>
  1135. public virtual void OnPointerExit(PointerEventData eventData)
  1136. {
  1137. isPointerInside = false;
  1138. EvaluateAndTransitionToSelectionState();
  1139. }
  1140. /// <summary>
  1141. /// Set selection and transition to appropriate state.
  1142. /// </summary>
  1143. /// <example>
  1144. /// <code>
  1145. /// using UnityEngine;
  1146. /// using System.Collections;
  1147. /// using UnityEngine.UI;
  1148. /// using UnityEngine.EventSystems;// Required when using Event data.
  1149. ///
  1150. /// public class ExampleClass : MonoBehaviour, ISelectHandler// required interface when using the OnSelect method.
  1151. /// {
  1152. /// //Do this when the selectable UI object is selected.
  1153. /// public void OnSelect(BaseEventData eventData)
  1154. /// {
  1155. /// Debug.Log(this.gameObject.name + " was selected");
  1156. /// }
  1157. /// }
  1158. /// </code>
  1159. /// </example>
  1160. public virtual void OnSelect(BaseEventData eventData)
  1161. {
  1162. hasSelection = true;
  1163. EvaluateAndTransitionToSelectionState();
  1164. }
  1165. /// <summary>
  1166. /// Unset selection and transition to appropriate state.
  1167. /// </summary>
  1168. /// <example>
  1169. /// <code>
  1170. /// using UnityEngine;
  1171. /// using System.Collections;
  1172. /// using UnityEngine.UI;
  1173. /// using UnityEngine.EventSystems;// Required when using Event data.
  1174. ///
  1175. /// public class ExampleClass : MonoBehaviour, IDeselectHandler //This Interface is required to receive OnDeselect callbacks.
  1176. /// {
  1177. /// public void OnDeselect(BaseEventData data)
  1178. /// {
  1179. /// Debug.Log("Deselected");
  1180. /// }
  1181. /// }
  1182. /// </code>
  1183. /// </example>
  1184. public virtual void OnDeselect(BaseEventData eventData)
  1185. {
  1186. hasSelection = false;
  1187. EvaluateAndTransitionToSelectionState();
  1188. }
  1189. /// <summary>
  1190. /// Selects this Selectable.
  1191. /// </summary>
  1192. /// <example>
  1193. /// <code>
  1194. /// using UnityEngine;
  1195. /// using System.Collections;
  1196. /// using UnityEngine.UI; // required when using UI elements in scripts
  1197. /// using UnityEngine.EventSystems;// Required when using Event data.
  1198. ///
  1199. /// public class ExampleClass : MonoBehaviour// required interface when using the OnSelect method.
  1200. /// {
  1201. /// public InputField myInputField;
  1202. ///
  1203. /// //Do this OnClick.
  1204. /// public void SaveGame()
  1205. /// {
  1206. /// //Makes the Input Field the selected UI Element.
  1207. /// myInputField.Select();
  1208. /// }
  1209. /// }
  1210. /// </code>
  1211. /// </example>
  1212. public virtual void Select()
  1213. {
  1214. if (EventSystem.current == null || EventSystem.current.alreadySelecting)
  1215. return;
  1216. EventSystem.current.SetSelectedGameObject(gameObject);
  1217. }
  1218. }
  1219. }