600 lines
17 KiB
C#
Raw Normal View History

2026-05-06 15:07:56 +02:00
using UnityEngine;
using DuloGames.UI.Tweens;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using System;
using System.Collections.Generic;
namespace DuloGames.UI
{
[DisallowMultipleComponent, ExecuteInEditMode, AddComponentMenu("UI/Window", 58), RequireComponent(typeof(CanvasGroup))]
public class UIWindow : MonoBehaviour, IEventSystemHandler, ISelectHandler, IPointerDownHandler {
public enum Transition
{
Instant,
Fade
}
public enum VisualState
{
Shown,
Hidden
}
public enum EscapeKeyAction
{
None,
Hide,
HideIfFocused,
Toggle
}
[Serializable] public class TransitionBeginEvent : UnityEvent<UIWindow, VisualState, bool> {}
[Serializable] public class TransitionCompleteEvent : UnityEvent<UIWindow, VisualState> {}
protected static UIWindow m_FucusedWindow;
public static UIWindow FocusedWindow { get { return m_FucusedWindow; } private set { m_FucusedWindow = value; } }
[SerializeField] private UIWindowID m_WindowId = UIWindowID.None;
[SerializeField] private int m_CustomWindowId = 0;
[SerializeField] private VisualState m_StartingState = VisualState.Hidden;
[SerializeField] private EscapeKeyAction m_EscapeKeyAction = EscapeKeyAction.Hide;
[SerializeField] private bool m_UseBlackOverlay = false;
[SerializeField] private bool m_FocusAllowReparent = true;
[SerializeField] private Transition m_Transition = Transition.Instant;
[SerializeField] private TweenEasing m_TransitionEasing = TweenEasing.InOutQuint;
[SerializeField] private float m_TransitionDuration = 0.1f;
protected bool m_IsFocused = false;
private VisualState m_CurrentVisualState = VisualState.Hidden;
private CanvasGroup m_CanvasGroup;
/// <summary>
/// Gets or sets the window identifier.
/// </summary>
/// <value>The window identifier.</value>
public UIWindowID ID
{
get { return this.m_WindowId; }
set { this.m_WindowId = value; }
}
/// <summary>
/// Gets or sets the custom window identifier.
/// </summary>
/// <value>The custom window identifier.</value>
public int CustomID
{
get { return this.m_CustomWindowId; }
set { this.m_CustomWindowId = value; }
}
/// <summary>
/// Gets or sets the escape key action.
/// </summary>
public EscapeKeyAction escapeKeyAction
{
get { return this.m_EscapeKeyAction; }
set { this.m_EscapeKeyAction = value; }
}
/// <summary>
/// Gets or sets a value indicating whether this window should use the black overlay.
/// </summary>
public bool useBlackOverlay
{
get { return this.m_UseBlackOverlay; }
set
{
this.m_UseBlackOverlay = value;
if (Application.isPlaying && this.m_UseBlackOverlay && this.isActiveAndEnabled)
{
UIBlackOverlay overlay = UIBlackOverlay.GetOverlay(this.gameObject);
if (overlay != null)
{
if (value) this.onTransitionBegin.AddListener(overlay.OnTransitionBegin);
else this.onTransitionBegin.RemoveListener(overlay.OnTransitionBegin);
}
}
}
}
/// <summary>
/// Allow re-parenting on focus.
/// </summary>
public bool focusAllowReparent
{
get { return this.m_FocusAllowReparent; }
set { this.m_FocusAllowReparent = value; }
}
/// <summary>
/// Gets or sets the transition.
/// </summary>
/// <value>The transition.</value>
public Transition transition
{
get { return this.m_Transition; }
set { this.m_Transition = value; }
}
/// <summary>
/// Gets or sets the transition easing.
/// </summary>
/// <value>The transition easing.</value>
public TweenEasing transitionEasing
{
get { return this.m_TransitionEasing; }
set { this.m_TransitionEasing = value; }
}
/// <summary>
/// Gets or sets the duration of the transition.
/// </summary>
/// <value>The duration of the transition.</value>
public float transitionDuration
{
get { return this.m_TransitionDuration; }
set { this.m_TransitionDuration = value; }
}
/// <summary>
/// The transition begin (invoked when a transition begins).
/// </summary>
public TransitionBeginEvent onTransitionBegin = new TransitionBeginEvent();
/// <summary>
/// The transition complete event (invoked when a transition completes).
/// </summary>
public TransitionCompleteEvent onTransitionComplete = new TransitionCompleteEvent();
/// <summary>
/// Gets a value indicating whether this window is visible.
/// </summary>
/// <value><c>true</c> if this instance is visible; otherwise, <c>false</c>.</value>
public bool IsVisible
{
get { return (this.m_CanvasGroup != null && this.m_CanvasGroup.alpha > 0f) ? true : false; }
}
/// <summary>
/// Gets a value indicating whether this window is open.
/// </summary>
/// <value><c>true</c> if this instance is open; otherwise, <c>false</c>.</value>
public bool IsOpen
{
get { return (this.m_CurrentVisualState == VisualState.Shown); }
}
/// <summary>
/// Gets a value indicating whether this instance is focused.
/// </summary>
/// <value><c>true</c> if this instance is focused; otherwise, <c>false</c>.</value>
public bool IsFocused
{
get { return this.m_IsFocused; }
}
// Tween controls
[NonSerialized] private readonly TweenRunner<FloatTween> m_FloatTweenRunner;
// Called by Unity prior to deserialization,
// should not be called by users
protected UIWindow()
{
if (this.m_FloatTweenRunner == null)
this.m_FloatTweenRunner = new TweenRunner<FloatTween>();
this.m_FloatTweenRunner.Init(this);
}
protected virtual void Awake()
{
// Get the canvas group
this.m_CanvasGroup = this.gameObject.GetComponent<CanvasGroup>();
// Transition to the starting state
if (Application.isPlaying)
this.ApplyVisualState(this.m_StartingState);
}
protected virtual void Start()
{
// Assign new custom ID
if (this.CustomID == 0)
this.CustomID = UIWindow.NextUnusedCustomID;
// Make sure we have a window manager in the scene if required
if (this.m_EscapeKeyAction != EscapeKeyAction.None)
{
UIWindowManager manager = Component.FindObjectOfType<UIWindowManager>();
// Add a manager if not present
if (manager == null)
{
GameObject newObj = new GameObject("Window Manager");
newObj.AddComponent<UIWindowManager>();
newObj.transform.SetAsFirstSibling();
}
}
}
protected virtual void OnEnable()
{
if (Application.isPlaying && this.m_UseBlackOverlay)
{
UIBlackOverlay overlay = UIBlackOverlay.GetOverlay(this.gameObject);
if (overlay != null)
this.onTransitionBegin.AddListener(overlay.OnTransitionBegin);
}
}
protected virtual void OnDisable()
{
if (Application.isPlaying && this.m_UseBlackOverlay)
{
UIBlackOverlay overlay = UIBlackOverlay.GetOverlay(this.gameObject);
if (overlay != null)
this.onTransitionBegin.RemoveListener(overlay.OnTransitionBegin);
}
}
#if UNITY_EDITOR
protected virtual void OnValidate()
{
this.m_TransitionDuration = Mathf.Max(this.m_TransitionDuration, 0f);
}
#endif
/// <summary>
/// Determines whether this window is active.
/// </summary>
/// <returns><c>true</c> if this instance is active; otherwise, <c>false</c>.</returns>
protected virtual bool IsActive()
{
return (this.enabled && this.gameObject.activeInHierarchy);
}
/// <summary>
/// Raises the select event.
/// </summary>
/// <param name="eventData">Event data.</param>
public virtual void OnSelect(BaseEventData eventData)
{
// Focus the window
this.Focus();
}
/// <summary>
/// Raises the pointer down event.
/// </summary>
/// <param name="eventData">Event data.</param>
public virtual void OnPointerDown(PointerEventData eventData)
{
// Focus the window
this.Focus();
}
/// <summary>
/// Focuses this window.
/// </summary>
public virtual void Focus()
{
if (this.m_IsFocused)
return;
// Flag as focused
this.m_IsFocused = true;
// Call the static on focused window
UIWindow.OnBeforeFocusWindow(this);
// Bring the window forward
this.BringToFront();
}
/// <summary>
/// Brings the window to the front.
/// </summary>
public void BringToFront()
{
UIUtility.BringToFront(this.gameObject, this.m_FocusAllowReparent);
}
/// <summary>
/// Toggle the window Show/Hide.
/// </summary>
public virtual void Toggle()
{
if (this.m_CurrentVisualState == VisualState.Shown)
this.Hide();
else
this.Show();
}
/// <summary>
/// Show the window.
/// </summary>
public virtual void Show()
{
this.Show(false);
}
/// <summary>
/// Show the window.
/// </summary>
/// <param name="instant">If set to <c>true</c> instant.</param>
public virtual void Show(bool instant)
{
if (!this.IsActive())
return;
// Focus the window
this.Focus();
// Check if the window is already shown
if (this.m_CurrentVisualState == VisualState.Shown)
return;
// Transition
this.EvaluateAndTransitionToVisualState(VisualState.Shown, instant);
}
/// <summary>
/// Hide the window.
/// </summary>
public virtual void Hide()
{
this.Hide(false);
}
/// <summary>
/// Hide the window.
/// </summary>
/// <param name="instant">If set to <c>true</c> instant.</param>
public virtual void Hide(bool instant)
{
if (!this.IsActive())
return;
// Check if the window is already hidden
if (this.m_CurrentVisualState == VisualState.Hidden)
return;
// Transition
this.EvaluateAndTransitionToVisualState(VisualState.Hidden, instant);
}
/// <summary>
/// Evaluates and transitions to the specified visual state.
/// </summary>
/// <param name="state">The state to transition to.</param>
/// <param name="instant">If set to <c>true</c> instant.</param>
protected virtual void EvaluateAndTransitionToVisualState(VisualState state, bool instant)
{
float targetAlpha = (state == VisualState.Shown) ? 1f : 0f;
// Call the transition begin event
if (this.onTransitionBegin != null)
this.onTransitionBegin.Invoke(this, state, (instant || this.m_Transition == Transition.Instant));
// Do the transition
if (this.m_Transition == Transition.Fade)
{
float duration = (instant) ? 0f : this.m_TransitionDuration;
// Tween the alpha
this.StartAlphaTween(targetAlpha, duration, true);
}
else
{
// Set the alpha directly
this.SetCanvasAlpha(targetAlpha);
// Call the transition complete event, since it's instant
if (this.onTransitionComplete != null)
this.onTransitionComplete.Invoke(this, state);
}
// Save the state
this.m_CurrentVisualState = state;
// If we are transitioning to show, enable the canvas group raycast blocking
if (state == VisualState.Shown)
{
this.m_CanvasGroup.blocksRaycasts = true;
//this.m_CanvasGroup.interactable = true;
}
}
/// <summary>
/// Instantly applies the visual state.
/// </summary>
/// <param name="state">The state to transition to.</param>
public virtual void ApplyVisualState(VisualState state)
{
float targetAlpha = (state == VisualState.Shown) ? 1f : 0f;
// Set the alpha directly
this.SetCanvasAlpha(targetAlpha);
// Save the state
this.m_CurrentVisualState = state;
// If we are transitioning to show, enable the canvas group raycast blocking
if (state == VisualState.Shown)
{
this.m_CanvasGroup.blocksRaycasts = true;
//this.m_CanvasGroup.interactable = true;
}
else
{
this.m_CanvasGroup.blocksRaycasts = false;
//this.m_CanvasGroup.interactable = false;
}
}
/// <summary>
/// Starts alpha tween.
/// </summary>
/// <param name="targetAlpha">Target alpha.</param>
/// <param name="duration">Duration.</param>
/// <param name="ignoreTimeScale">If set to <c>true</c> ignore time scale.</param>
public void StartAlphaTween(float targetAlpha, float duration, bool ignoreTimeScale)
{
if (this.m_CanvasGroup == null)
return;
var floatTween = new FloatTween { duration = duration, startFloat = this.m_CanvasGroup.alpha, targetFloat = targetAlpha };
floatTween.AddOnChangedCallback(SetCanvasAlpha);
floatTween.AddOnFinishCallback(OnTweenFinished);
floatTween.ignoreTimeScale = ignoreTimeScale;
floatTween.easing = this.m_TransitionEasing;
this.m_FloatTweenRunner.StartTween(floatTween);
}
/// <summary>
/// Sets the canvas alpha.
/// </summary>
/// <param name="alpha">Alpha.</param>
public void SetCanvasAlpha(float alpha)
{
if (this.m_CanvasGroup == null)
return;
// Set the alpha
this.m_CanvasGroup.alpha = alpha;
// If the alpha is zero, disable block raycasts
// Enabling them back on is done in the transition method
if (alpha == 0f)
{
this.m_CanvasGroup.blocksRaycasts = false;
//this.m_CanvasGroup.interactable = false;
}
}
/// <summary>
/// Raises the list tween finished event.
/// </summary>
protected virtual void OnTweenFinished()
{
// Call the transition complete event
if (this.onTransitionComplete != null)
this.onTransitionComplete.Invoke(this, this.m_CurrentVisualState);
}
#region Static Methods
/// <summary>
/// Get all the windows in the scene (Including inactive).
/// </summary>
/// <returns>The windows.</returns>
public static List<UIWindow> GetWindows()
{
List<UIWindow> windows = new List<UIWindow>();
UIWindow[] ws = Resources.FindObjectsOfTypeAll<UIWindow>();
foreach (UIWindow w in ws)
{
// Check if the window is active in the hierarchy
if (w.gameObject.activeInHierarchy)
windows.Add(w);
}
return windows;
}
public static int SortByCustomWindowID(UIWindow w1, UIWindow w2)
{
return w1.CustomID.CompareTo(w2.CustomID);
}
/// <summary>
/// Gets the next unused custom ID for a window.
/// </summary>
/// <value>The next unused ID.</value>
public static int NextUnusedCustomID
{
get
{
// Get the windows
List<UIWindow> windows = UIWindow.GetWindows();
if (GetWindows().Count > 0)
{
// Sort the windows by id
windows.Sort(UIWindow.SortByCustomWindowID);
// Return the last window id plus one
return windows[windows.Count - 1].CustomID + 1;
}
// No windows, return 0
return 0;
}
}
/// <summary>
/// Gets the window with the given ID.
/// </summary>
/// <returns>The window.</returns>
/// <param name="id">Identifier.</param>
public static UIWindow GetWindow(UIWindowID id)
{
// Get the windows and try finding the window with the given id
foreach (UIWindow window in UIWindow.GetWindows())
if (window.ID == id)
return window;
return null;
}
/// <summary>
/// Gets the window with the given custom ID.
/// </summary>
/// <returns>The window.</returns>
/// <param name="id">The custom identifier.</param>
public static UIWindow GetWindowByCustomID(int customId)
{
// Get the windows and try finding the window with the given id
foreach (UIWindow window in UIWindow.GetWindows())
if (window.CustomID == customId)
return window;
return null;
}
/// <summary>
/// Focuses the window with the given ID.
/// </summary>
/// <param name="id">Identifier.</param>
public static void FocusWindow(UIWindowID id)
{
// Focus the window
if (UIWindow.GetWindow(id) != null)
UIWindow.GetWindow(id).Focus();
}
/// <summary>
/// Raises the before focus window event.
/// </summary>
/// <param name="window">The window.</param>
protected static void OnBeforeFocusWindow(UIWindow window)
{
if (m_FucusedWindow != null)
m_FucusedWindow.m_IsFocused = false;
m_FucusedWindow = window;
}
#endregion
}
}