// 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;
}
}
}