From 84edb62e915b32d2010893b42fb7168528613de2 Mon Sep 17 00:00:00 2001 From: "Simon (darkside) Jackson" Date: Sat, 19 Dec 2015 15:12:10 +0000 Subject: [PATCH] Added alternate toggle control at request of Martin Sharkbomb, adds events to the toggle group for value changed Plus a property to access the single selected toggle (although I'd recommend using the ActiveToggles IEnumerable --HG-- branch : develop_5.3 --- Scripts/Utilities/ExtensionsToggle.cs | 241 ++++++++++++++++++ Scripts/Utilities/ExtensionsToggle.cs.meta | 12 + Scripts/Utilities/ExtensionsToggleGroup.cs | 110 ++++++++ .../Utilities/ExtensionsToggleGroup.cs.meta | 12 + 4 files changed, 375 insertions(+) create mode 100644 Scripts/Utilities/ExtensionsToggle.cs create mode 100644 Scripts/Utilities/ExtensionsToggle.cs.meta create mode 100644 Scripts/Utilities/ExtensionsToggleGroup.cs create mode 100644 Scripts/Utilities/ExtensionsToggleGroup.cs.meta diff --git a/Scripts/Utilities/ExtensionsToggle.cs b/Scripts/Utilities/ExtensionsToggle.cs new file mode 100644 index 0000000..0097080 --- /dev/null +++ b/Scripts/Utilities/ExtensionsToggle.cs @@ -0,0 +1,241 @@ +using System; +using UnityEngine.Events; +using UnityEngine.EventSystems; +using UnityEngine.Serialization; + +namespace UnityEngine.UI +{ + /// + /// Simple toggle -- something that has an 'on' and 'off' states: checkbox, toggle button, radio button, etc. + /// + [AddComponentMenu("UI/Extensions/Extensions Toggle", 31)] + [RequireComponent(typeof(RectTransform))] + public class ExtensionsToggle : Selectable, IPointerClickHandler, ISubmitHandler, ICanvasElement + { + public enum ToggleTransition + { + None, + Fade + } + + [Serializable] + public class ToggleEvent : UnityEvent + { } + + /// + /// Transition type. + /// + public ToggleTransition toggleTransition = ToggleTransition.Fade; + + /// + /// Graphic the toggle should be working with. + /// + public Graphic graphic; + + // group that this toggle can belong to + [SerializeField] + private ExtensionsToggleGroup m_Group; + + public ExtensionsToggleGroup group + { + get { return m_Group; } + set + { + m_Group = value; +#if UNITY_EDITOR + if (Application.isPlaying) +#endif + { + SetToggleGroup(m_Group, true); + PlayEffect(true); + } + } + } + + /// + /// Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers. + /// + public ToggleEvent onValueChanged = new ToggleEvent(); + + // Whether the toggle is on + [FormerlySerializedAs("m_IsActive")] + [Tooltip("Is the toggle currently on or off?")] + [SerializeField] + private bool m_IsOn; + + protected ExtensionsToggle() + { } + +#if UNITY_EDITOR + protected override void OnValidate() + { + base.OnValidate(); + Set(m_IsOn, false); + PlayEffect(toggleTransition == ToggleTransition.None); + + var prefabType = UnityEditor.PrefabUtility.GetPrefabType(this); + if (prefabType != UnityEditor.PrefabType.Prefab && !Application.isPlaying) + CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); + } + +#endif // if UNITY_EDITOR + + public virtual void Rebuild(CanvasUpdate executing) + { +#if UNITY_EDITOR + if (executing == CanvasUpdate.Prelayout) + onValueChanged.Invoke(m_IsOn); +#endif + } + + public virtual void LayoutComplete() + { } + + public virtual void GraphicUpdateComplete() + { } + + protected override void OnEnable() + { + base.OnEnable(); + SetToggleGroup(m_Group, false); + PlayEffect(true); + } + + protected override void OnDisable() + { + SetToggleGroup(null, false); + base.OnDisable(); + } + + protected override void OnDidApplyAnimationProperties() + { + // Check if isOn has been changed by the animation. + // Unfortunately there is no way to check if we don�t have a graphic. + if (graphic != null) + { + bool oldValue = !Mathf.Approximately(graphic.canvasRenderer.GetColor().a, 0); + if (m_IsOn != oldValue) + { + m_IsOn = oldValue; + Set(!oldValue); + } + } + + base.OnDidApplyAnimationProperties(); + } + + private void SetToggleGroup(ExtensionsToggleGroup newGroup, bool setMemberValue) + { + ExtensionsToggleGroup oldGroup = m_Group; + + // Sometimes IsActive returns false in OnDisable so don't check for it. + // Rather remove the toggle too often than too little. + if (m_Group != null) + m_Group.UnregisterToggle(this); + + // At runtime the group variable should be set but not when calling this method from OnEnable or OnDisable. + // That's why we use the setMemberValue parameter. + if (setMemberValue) + m_Group = newGroup; + + // Only register to the new group if this Toggle is active. + if (m_Group != null && IsActive()) + m_Group.RegisterToggle(this); + + // If we are in a new group, and this toggle is on, notify group. + // Note: Don't refer to m_Group here as it's not guaranteed to have been set. + if (newGroup != null && newGroup != oldGroup && isOn && IsActive()) + m_Group.NotifyToggleOn(this); + } + + /// + /// Whether the toggle is currently active. + /// + public bool isOn + { + get { return m_IsOn; } + set + { + Set(value); + } + } + + void Set(bool value) + { + Set(value, true); + } + + void Set(bool value, bool sendCallback) + { + if (m_IsOn == value) + return; + + // if we are in a group and set to true, do group logic + m_IsOn = value; + if (m_Group != null && IsActive()) + { + if (m_IsOn || (!m_Group.AnyTogglesOn() && !m_Group.allowSwitchOff)) + { + m_IsOn = true; + m_Group.NotifyToggleOn(this); + } + } + + // Always send event when toggle is clicked, even if value didn't change + // due to already active toggle in a toggle group being clicked. + // Controls like Dropdown rely on this. + // It's up to the user to ignore a selection being set to the same value it already was, if desired. + PlayEffect(toggleTransition == ToggleTransition.None); + if (sendCallback) + onValueChanged.Invoke(m_IsOn); + } + + /// + /// Play the appropriate effect. + /// + private void PlayEffect(bool instant) + { + if (graphic == null) + return; + +#if UNITY_EDITOR + if (!Application.isPlaying) + graphic.canvasRenderer.SetAlpha(m_IsOn ? 1f : 0f); + else +#endif + graphic.CrossFadeAlpha(m_IsOn ? 1f : 0f, instant ? 0f : 0.1f, true); + } + + /// + /// Assume the correct visual state. + /// + protected override void Start() + { + PlayEffect(true); + } + + private void InternalToggle() + { + if (!IsActive() || !IsInteractable()) + return; + + isOn = !isOn; + } + + /// + /// React to clicks. + /// + public virtual void OnPointerClick(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + InternalToggle(); + } + + public virtual void OnSubmit(BaseEventData eventData) + { + InternalToggle(); + } + } +} diff --git a/Scripts/Utilities/ExtensionsToggle.cs.meta b/Scripts/Utilities/ExtensionsToggle.cs.meta new file mode 100644 index 0000000..74bc572 --- /dev/null +++ b/Scripts/Utilities/ExtensionsToggle.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bc733ccacdce62148ac1f46d1dd84ff6 +timeCreated: 1450456340 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Utilities/ExtensionsToggleGroup.cs b/Scripts/Utilities/ExtensionsToggleGroup.cs new file mode 100644 index 0000000..bf1c67c --- /dev/null +++ b/Scripts/Utilities/ExtensionsToggleGroup.cs @@ -0,0 +1,110 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using UnityEngine.EventSystems; +using UnityEngine.Events; + +namespace UnityEngine.UI +{ + [AddComponentMenu("UI/Extensions/Extensions Toggle Group")] + [DisallowMultipleComponent] + public class ExtensionsToggleGroup : UIBehaviour + { + [SerializeField] + private bool m_AllowSwitchOff = false; + public bool allowSwitchOff { get { return m_AllowSwitchOff; } set { m_AllowSwitchOff = value; } } + + private List m_Toggles = new List(); + + [Serializable] + public class ToggleGroupEvent : UnityEvent + { } + + public ToggleGroupEvent onToggleGroupChanged = new ToggleGroupEvent(); + public ToggleGroupEvent onToggleGroupToggleChanged = new ToggleGroupEvent(); + + public ExtensionsToggle SelectedToggle { get; private set; } + + + protected ExtensionsToggleGroup() + { } + + private void ValidateToggleIsInGroup(ExtensionsToggle toggle) + { + if (toggle == null || !m_Toggles.Contains(toggle)) + throw new ArgumentException(string.Format("Toggle {0} is not part of ToggleGroup {1}", new object[] { toggle, this })); + } + + public void NotifyToggleOn(ExtensionsToggle toggle) + { + ValidateToggleIsInGroup(toggle); + + // disable all toggles in the group + for (var i = 0; i < m_Toggles.Count; i++) + { + if (m_Toggles[i] == toggle) + { + SelectedToggle = toggle; + continue; + } + + m_Toggles[i].isOn = false; + } + onToggleGroupChanged.Invoke(AnyTogglesOn()); + } + + public void UnregisterToggle(ExtensionsToggle toggle) + { + if (m_Toggles.Contains(toggle)) + { + m_Toggles.Remove(toggle); + toggle.onValueChanged.RemoveListener(NotifyToggleChanged); + } + } + + private void NotifyToggleChanged(bool isOn) + { + onToggleGroupToggleChanged.Invoke(isOn); + } + + public void RegisterToggle(ExtensionsToggle toggle) + { + if (!m_Toggles.Contains(toggle)) + { + m_Toggles.Add(toggle); + toggle.onValueChanged.AddListener(NotifyToggleChanged); + } + } + + public bool AnyTogglesOn() + { + return m_Toggles.Find(x => x.isOn) != null; + } + + public IEnumerable ActiveToggles() + { + return m_Toggles.Where(x => x.isOn); + } + + public void SetAllTogglesOff() + { + bool oldAllowSwitchOff = m_AllowSwitchOff; + m_AllowSwitchOff = true; + + for (var i = 0; i < m_Toggles.Count; i++) + m_Toggles[i].isOn = false; + + m_AllowSwitchOff = oldAllowSwitchOff; + } + + public void HasTheGroupToggle(bool value) + { + Debug.Log("Testing, the group has toggled [" + value + "]"); + } + + public void HasAToggleFlipped(bool value) + { + Debug.Log("Testing, a toggle has toggled [" + value + "]"); + } + } +} \ No newline at end of file diff --git a/Scripts/Utilities/ExtensionsToggleGroup.cs.meta b/Scripts/Utilities/ExtensionsToggleGroup.cs.meta new file mode 100644 index 0000000..7c471d1 --- /dev/null +++ b/Scripts/Utilities/ExtensionsToggleGroup.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bf31acdd4bba9fc45b8a5cc6de985aee +timeCreated: 1450454546 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: