2023-08-02 06:08:03 +00:00
// 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
{
/// <summary>
/// Toggle component based on UISelectable with category/name id identifier.
/// </summary>
[RequireComponent(typeof(RectTransform))]
2023-12-05 06:20:20 +00:00
[AddComponentMenu("Doozy/UI/Components/UIToggle")]
2023-08-02 06:08:03 +00:00
[SelectionBase]
public partial class UIToggle : UISelectable , IPointerClickHandler , ISubmitHandler
{
#if UNITY_EDITOR
2023-12-05 06:20:20 +00:00
[UnityEditor.MenuItem("GameObject/Doozy/UI/Components/UIToggle", false, 8)]
2023-08-02 06:08:03 +00:00
private static void CreateComponent ( UnityEditor . MenuCommand menuCommand )
{
GameObjectUtils . AddToScene < UIToggle > ( "UIToggle" , false , true ) ;
}
#endif
/// <summary> UIToggles database </summary>
public static HashSet < UIToggle > database { get ; private set ; } = new HashSet < UIToggle > ( ) ;
[ExecuteOnReload]
private static void OnReload ( )
{
database = new HashSet < UIToggle > ( ) ;
}
[ClearOnReload]
private static SignalStream s_stream ;
/// <summary> UIToggle signal stream </summary>
public static SignalStream stream = > s_stream ? ? ( s_stream = SignalsService . GetStream ( k_StreamCategory , nameof ( UIToggle ) ) ) ;
/// <summary> All toggles that are active and enabled </summary>
public static IEnumerable < UIToggle > availableToggles = > database . Where ( item = > item . isActiveAndEnabled ) ;
/// <summary> TRUE is this selectable is selected by EventSystem.current, FALSE otherwise </summary>
public bool isSelected = > EventSystem . current . currentSelectedGameObject = = gameObject ;
/// <summary> Type of selectable </summary>
public override SelectableType selectableType = > SelectableType . Toggle ;
/// <summary> UIToggle identifier </summary>
public UIToggleId Id = new UIToggleId ( ) ;
/// <summary> Toggle became ON - executed when isOn becomes TRUE </summary>
public ModyEvent OnToggleOnCallback = new ModyEvent ( nameof ( OnToggleOnCallback ) ) ;
// <summary> Toggle became ON with instant animations - executed when isOn becomes TRUE </summary>
public ModyEvent OnInstantToggleOnCallback = new ModyEvent ( nameof ( OnInstantToggleOnCallback ) ) ;
/// <summary> Toggle became OFF - executed when isOn becomes FALSE </summary>
public ModyEvent OnToggleOffCallback = new ModyEvent ( nameof ( OnToggleOffCallback ) ) ;
/// <summary> Toggle became OFF with instant animations - executed when isOn becomes FALSE </summary>
public ModyEvent OnInstantToggleOffCallback = new ModyEvent ( nameof ( OnInstantToggleOffCallback ) ) ;
/// <summary> Toggle changed its value - executed when isOn changes its value </summary>
public BoolEvent OnValueChangedCallback = new BoolEvent ( ) ;
/// <summary> Toggle value changed callback. This special callback also sends when the event happened, the previousValue and the newValue </summary>
public UnityAction < ToggleValueChangedEvent > onToggleValueChangedCallback { get ; set ; }
/// <summary> Returns TRUE if this toggle has a toggle group reference </summary>
public bool inToggleGroup = > ToggleGroup ! = null & & ToggleGroup . toggles . Contains ( this ) ;
[SerializeField] private UIToggleGroup ToggleGroup ;
/// <summary> Reference to the toggle group that this toggle belongs to </summary>
public UIToggleGroup toggleGroup
{
get = > ToggleGroup ;
internal set = > ToggleGroup = value ;
}
/// <summary> TRUE if the toggle is on, FALSE otherwise </summary>
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 ;
/// <summary> 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 </summary>
public bool isLocked
{
get = > IsLocked ;
set = > IsLocked = value ;
}
/// <summary> Internal flag to track if the toggle has been initialized </summary>
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 ( ) ;
}
/// <summary> Initializes the toggle </summary>
protected virtual void InitializeToggle ( )
{
if ( toggleInitialized ) return ;
AddToToggleGroup ( toggleGroup ) ;
toggleInitialized = true ;
if ( inToggleGroup ) return ;
ValueChanged ( isOn , isOn , false , false ) ;
}
/// <summary> Called when on pointer click event is sent by the IPointerClickHandler </summary>
/// <param name="eventData"> Pointer event data </param>
public virtual void OnPointerClick ( PointerEventData eventData )
{
if ( inCooldown ) return ;
if ( eventData . button ! = PointerEventData . InputButton . Left )
return ;
if ( ! IsActive ( ) | | ! IsInteractable ( ) )
return ;
ToggleValue ( ) ;
}
/// <summary> Called when on submit event is sent by the ISubmitHandler </summary>
/// <param name="eventData"> Event data </param>
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 ( ) ;
}
/// <summary> Toggle the toggle's value </summary>
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
}
/// <summary> Adds this toggle to the specified toggle group </summary>
/// <param name="targetToggleGroup"> Target toggle group </param>
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 ) ;
}
/// <summary> Removes this toggle from its assigned toggle group </summary>
public void RemoveFromToggleGroup ( )
{
if ( toggleGroup = = null )
return ;
toggleGroup . RemoveToggle ( this ) ;
}
/// <summary> Called when the value of the toggle is changed by a toggle group </summary>
/// <param name="newValue"> New value of the toggle </param>
/// <param name="animateChange"> TRUE if the change should be animated, FALSE otherwise </param>
/// <param name="triggerValueChanged"> TRUE if the value changed callback should be triggered, FALSE otherwise </param>
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 ) ;
}
/// <summary> Send a signal to the toggle signal stream with the new value for this toggle </summary>
/// <param name="newValue"> New value of the toggle </param>
internal void SendSignal ( bool newValue )
{
stream . SendSignal ( new UIToggleSignalData ( Id . Category , Id . Name , newValue ? CommandToggle . On : CommandToggle . Off , playerIndex , this ) ) ;
}
/// <summary> Called when the value of the toggle changes </summary>
/// <param name="previousValue"> Previous value of the toggle </param>
/// <param name="newValue"> New value of the toggle </param>
/// <param name="animateChange"> TRUE if the change should be animated, FALSE otherwise </param>
/// <param name="triggerValueChanged"> TRUE if the value changed callback should be triggered, FALSE otherwise </param>
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
/// <summary> Get all the registered toggles with the given category and name </summary>
/// <param name="category"> UIToggle category </param>
/// <param name="name"> UIToggle name (from the given category) </param>
public static IEnumerable < UIToggle > GetToggles ( string category , string name ) = >
database . Where ( toggle = > toggle . Id . Category . Equals ( category ) ) . Where ( toggle = > toggle . Id . Name . Equals ( name ) ) ;
/// <summary> Get all the registered toggles with the given category </summary>
/// <param name="category"> UIToggle category </param>
public static IEnumerable < UIToggle > GetAllTogglesInCategory ( string category ) = >
database . Where ( toggle = > toggle . Id . Category . Equals ( category ) ) ;
/// <summary> Get all the toggles that are active and enabled (all the visible/available toggles) </summary>
public static IEnumerable < UIToggle > GetAvailableToggles ( ) = >
database . Where ( toggle = > toggle . isActiveAndEnabled ) ;
/// <summary> Get the selected toggle (if a toggle is not selected, this method returns null) </summary>
public static UIToggle GetSelectedToggle ( ) = >
database . FirstOrDefault ( toggle = > toggle . isSelected ) ;
/// <summary> Select the toggle with the given category and name (if it is active and enabled) </summary>
/// <param name="category"> UIToggle category </param>
/// <param name="name"> UIToggle name (from the given category) </param>
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
{
/// <summary> Set the toggle value to the given value </summary>
/// <param name="target"> Target toggle </param>
/// <param name="newValue"> New value of the toggle </param>
/// <param name="animateChange"> TRUE if the change should be animated, FALSE otherwise </param>
/// <param name="triggerValueChanged"> TRUE if the value changed callback should be triggered, FALSE otherwise </param>
public static T SetIsOn < T > ( 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 ;
}
/// <summary> Lock the toggle from having its isOn value changed, preventing it from being changed </summary>
/// <param name="target"> Target toggle </param>
public static T Lock < T > ( this T target ) where T : UIToggle
{
target . isLocked = true ;
return target ;
}
/// <summary>
/// Unlock the toggle from having its isOn value changed, allowing it to be changed again
/// </summary>
/// <param name="target"> Target toggle </param>
public static T Unlock < T > ( this T target ) where T : UIToggle
{
target . isLocked = false ;
return target ;
}
}
}