// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved. // This code can only be used under the standard Unity Asset Store End User License Agreement // A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms using System.Collections.Generic; using System.Linq; using Doozy.Runtime.Common.Attributes; using Doozy.Runtime.Common.Events; using Doozy.Runtime.Common.Utils; using Doozy.Runtime.Mody; using Doozy.Runtime.Signals; using Doozy.Runtime.UIManager.Events; using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedAutoPropertyAccessor.Global // ReSharper disable MemberCanBeProtected.Global namespace Doozy.Runtime.UIManager.Components { /// /// Toggle component based on UISelectable with category/name id identifier. /// [RequireComponent(typeof(RectTransform))] [AddComponentMenu("Doozy/UI/Components/UIToggle")] [SelectionBase] public partial class UIToggle : UISelectable, IPointerClickHandler, ISubmitHandler { #if UNITY_EDITOR [UnityEditor.MenuItem("GameObject/Doozy/UI/Components/UIToggle", false, 8)] private static void CreateComponent(UnityEditor.MenuCommand menuCommand) { GameObjectUtils.AddToScene("UIToggle", false, true); } #endif /// UIToggles database public static HashSet database { get; private set; } = new HashSet(); [ExecuteOnReload] private static void OnReload() { database = new HashSet(); } [ClearOnReload] private static SignalStream s_stream; /// UIToggle signal stream public static SignalStream stream => s_stream ?? (s_stream = SignalsService.GetStream(k_StreamCategory, nameof(UIToggle))); /// All toggles that are active and enabled public static IEnumerable availableToggles => database.Where(item => item.isActiveAndEnabled); /// TRUE is this selectable is selected by EventSystem.current, FALSE otherwise public bool isSelected => EventSystem.current.currentSelectedGameObject == gameObject; /// Type of selectable public override SelectableType selectableType => SelectableType.Toggle; /// UIToggle identifier public UIToggleId Id = new UIToggleId(); /// Toggle became ON - executed when isOn becomes TRUE public ModyEvent OnToggleOnCallback = new ModyEvent(nameof(OnToggleOnCallback)); // Toggle became ON with instant animations - executed when isOn becomes TRUE public ModyEvent OnInstantToggleOnCallback = new ModyEvent(nameof(OnInstantToggleOnCallback)); /// Toggle became OFF - executed when isOn becomes FALSE public ModyEvent OnToggleOffCallback = new ModyEvent(nameof(OnToggleOffCallback)); /// Toggle became OFF with instant animations - executed when isOn becomes FALSE public ModyEvent OnInstantToggleOffCallback = new ModyEvent(nameof(OnInstantToggleOffCallback)); /// Toggle changed its value - executed when isOn changes its value public BoolEvent OnValueChangedCallback = new BoolEvent(); /// Toggle value changed callback. This special callback also sends when the event happened, the previousValue and the newValue public UnityAction onToggleValueChangedCallback { get; set; } /// Returns TRUE if this toggle has a toggle group reference public bool inToggleGroup => ToggleGroup != null && ToggleGroup.toggles.Contains(this); [SerializeField] private UIToggleGroup ToggleGroup; /// Reference to the toggle group that this toggle belongs to public UIToggleGroup toggleGroup { get => ToggleGroup; internal set => ToggleGroup = value; } /// TRUE if the toggle is on, FALSE otherwise public override bool isOn { get => IsOn; set { if (isLocked) return; bool previousValue = IsOn; IsOn = value; if (inToggleGroup) { toggleGroup.ToggleChangedValue(toggle: this, animateChange: true); return; } ValueChanged(previousValue: previousValue, newValue: value, animateChange: true, triggerValueChanged: true); } } [SerializeField] protected bool IsLocked; /// TRUE if the toggle is locked, FALSE otherwise. A locked toggle cannot be toggled and will maintain its current isOn value even if the user clicks on it public bool isLocked { get => IsLocked; set => IsLocked = value; } /// Internal flag to track if the toggle has been initialized protected bool toggleInitialized { get; set; } protected override void Awake() { toggleInitialized = false; if (!Application.isPlaying) return; database.Add(this); base.Awake(); } protected override void OnEnable() { if (!Application.isPlaying) return; StopCooldown(); database.Remove(null); base.OnEnable(); InitializeToggle(); } protected override void OnDisable() { StopCooldown(); database.Remove(null); base.OnDisable(); } protected override void OnDestroy() { database.Remove(null); database.Remove(this); base.OnDestroy(); } /// Initializes the toggle protected virtual void InitializeToggle() { if (toggleInitialized) return; AddToToggleGroup(toggleGroup); toggleInitialized = true; if (inToggleGroup) return; ValueChanged(isOn, isOn, false, false); } /// Called when on pointer click event is sent by the IPointerClickHandler /// Pointer event data public virtual void OnPointerClick(PointerEventData eventData) { if (inCooldown) return; if (eventData.button != PointerEventData.InputButton.Left) return; if (!IsActive() || !IsInteractable()) return; ToggleValue(); } /// Called when on submit event is sent by the ISubmitHandler /// Event data public virtual void OnSubmit(BaseEventData eventData) { if (inCooldown) return; if (!IsActive() || !IsInteractable()) return; ToggleValue(); if (!inputSettings.submitTriggersPointerClick) return; behaviours.GetBehaviour(UIBehaviour.Name.PointerClick)?.Execute(); behaviours.GetBehaviour(UIBehaviour.Name.PointerLeftClick)?.Execute(); } /// Toggle the toggle's value protected virtual void ToggleValue() { if (isLocked) return; //if the toggle is locked, we don't toggle it isOn = !isOn; //toggle the toggle's value StartCooldown(); //start the cooldown } /// Adds this toggle to the specified toggle group /// Target toggle group public void AddToToggleGroup(UIToggleGroup targetToggleGroup) { if (targetToggleGroup == null) return; if (inToggleGroup && targetToggleGroup != toggleGroup) //if the toggle is already in a toggle group, we remove it from that group RemoveFromToggleGroup(); //remove from the previous toggle group if (isLocked) //check if the toggle is locked because we don't want to add locked toggles to a toggle group isLocked = false; // if the toggle is locked, we unlock it to allow the toggle group to take control of it targetToggleGroup.AddToggle(this); } /// Removes this toggle from its assigned toggle group public void RemoveFromToggleGroup() { if (toggleGroup == null) return; toggleGroup.RemoveToggle(this); } /// Called when the value of the toggle is changed by a toggle group /// New value of the toggle /// TRUE if the change should be animated, FALSE otherwise /// TRUE if the value changed callback should be triggered, FALSE otherwise protected internal virtual void UpdateValueFromGroup(bool newValue, bool animateChange, bool triggerValueChanged = true) { if (isLocked) isLocked = false; //if the toggle is locked, we unlock it to allow the toggle group to take control of it bool previousValue = IsOn; IsOn = newValue; ValueChanged(previousValue, newValue, animateChange, triggerValueChanged); } /// Send a signal to the toggle signal stream with the new value for this toggle /// New value of the toggle internal void SendSignal(bool newValue) { stream.SendSignal(new UIToggleSignalData(Id.Category, Id.Name, newValue ? CommandToggle.On : CommandToggle.Off, playerIndex, this)); } /// Called when the value of the toggle changes /// Previous value of the toggle /// New value of the toggle /// TRUE if the change should be animated, FALSE otherwise /// TRUE if the value changed callback should be triggered, FALSE otherwise internal virtual void ValueChanged(bool previousValue, bool newValue, bool animateChange, bool triggerValueChanged) { RefreshState(); switch (newValue) { case true: switch (animateChange) { case true: OnToggleOnCallback?.Execute(); break; default: OnInstantToggleOnCallback?.Execute(); break; } break; case false: switch (animateChange) { case true: OnToggleOffCallback?.Execute(); break; default: OnInstantToggleOffCallback?.Execute(); break; } break; } if (!triggerValueChanged) return; SendSignal(newValue); OnValueChangedCallback?.Invoke(newValue); onToggleValueChangedCallback?.Invoke(new ToggleValueChangedEvent(previousValue, newValue, animateChange)); } #region Static Methods /// Get all the registered toggles with the given category and name /// UIToggle category /// UIToggle name (from the given category) public static IEnumerable GetToggles(string category, string name) => database.Where(toggle => toggle.Id.Category.Equals(category)).Where(toggle => toggle.Id.Name.Equals(name)); /// Get all the registered toggles with the given category /// UIToggle category public static IEnumerable GetAllTogglesInCategory(string category) => database.Where(toggle => toggle.Id.Category.Equals(category)); /// Get all the toggles that are active and enabled (all the visible/available toggles) public static IEnumerable GetAvailableToggles() => database.Where(toggle => toggle.isActiveAndEnabled); /// Get the selected toggle (if a toggle is not selected, this method returns null) public static UIToggle GetSelectedToggle() => database.FirstOrDefault(toggle => toggle.isSelected); /// Select the toggle with the given category and name (if it is active and enabled) /// UIToggle category /// UIToggle name (from the given category) public static bool SelectToggle(string category, string name) { UIToggle toggle = availableToggles.FirstOrDefault(b => b.Id.Category.Equals(category) & b.Id.Name.Equals(name)); if (toggle == null) return false; toggle.Select(); return true; } #endregion } public static class UIToggleExtensions { /// Set the toggle value to the given value /// Target toggle /// New value of the toggle /// TRUE if the change should be animated, FALSE otherwise /// TRUE if the value changed callback should be triggered, FALSE otherwise public static T SetIsOn(this T target, bool newValue, bool animateChange = true, bool triggerValueChanged = true) where T : UIToggle { if (target.isLocked) return target; bool previousValue = target.isOn; target.IsOn = newValue; if (target.inToggleGroup) { target.toggleGroup.ToggleChangedValue(target, animateChange, triggerValueChanged); return target; } target.ValueChanged(previousValue, newValue, animateChange, triggerValueChanged); return target; } /// Lock the toggle from having its isOn value changed, preventing it from being changed /// Target toggle public static T Lock(this T target) where T : UIToggle { target.isLocked = true; return target; } /// /// Unlock the toggle from having its isOn value changed, allowing it to be changed again /// /// Target toggle public static T Unlock(this T target) where T : UIToggle { target.isLocked = false; return target; } } }