first commit

This commit is contained in:
2025-07-06 00:23:46 +02:00
commit 38f50c8819
1788 changed files with 112878 additions and 0 deletions

View File

@@ -0,0 +1,218 @@
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UWE;
namespace NitroxClient.MonoBehaviours.Gui.Modals;
/// <summary>
/// Base class for Modal components, which are dialog boxes that appear in the middle of the screen
/// </summary>
public abstract class Modal
{
/// <summary>
/// Get a Modal by its type at any time (static)
/// </summary>
public static Dictionary<Type, Modal> Modals = new();
/// <summary>
/// Current modal that is visible on the screen
/// </summary>
public static Modal CurrentModal;
private GameObject modalSubWindow;
private TextMeshProUGUI text;
// All the properties that will be overriden by new instances that inherit this class
public string SubWindowName { get; init; }
public string ModalText { get; set; }
/// <summary>
/// Makes it possible to dismiss the modal by clicking outside of the modal or pressing escape (default false).
/// </summary>
public bool IsAvoidable { get; init; }
public bool HideNoButton { get; init; }
public string YesButtonText { get; init; }
public string NoButtonText { get; init; }
public bool FreezeGame { get; init; }
public float Transparency { get; init; }
public float Height { get; init; }
// Is useful for calling IngameMenu::OnDeselect() from a modal class (in Hide() for example)
public bool IsAvoidableBypass = false;
public Modal(string yesButtonText = "YES", bool hideNoButton = true, string noButtonText = "NO", string modalText = "", bool isAvoidable = false, bool freezeGame = false, float transparency = 0.392f, float height = 195f)
{
Type type = GetType();
if (Modals.ContainsKey(type))
{
throw new NotSupportedException($"You cannot set two modals to have the same Type");
}
SubWindowName = GetType().Name;
YesButtonText = yesButtonText;
HideNoButton = hideNoButton;
NoButtonText = noButtonText;
ModalText = modalText;
IsAvoidable = isAvoidable;
FreezeGame = freezeGame;
Transparency = transparency; // 0.392 is the default transparency for Subnautica's modal
Height = height;
Log.Debug($"Registered Modal {SubWindowName} of type {type}");
Modals.Add(type, this);
}
/// <summary>
/// Adds the Modal to the screen
/// </summary>
public void Show()
{
CoroutineHost.StartCoroutine(ShowAsync());
}
public IEnumerator ShowAsync()
{
CurrentModal?.Hide();
CurrentModal = this;
yield return ShowImplementation();
}
/// <summary>
/// Removes the Modal from the screen
/// </summary>
public void Hide()
{
CurrentModal = null;
if (FreezeGame)
{
FreezeTime.End(FreezeTime.Id.Quit);
}
if (IsAvoidable)
{
IngameMenu.main.OnDeselect();
}
else
{
IsAvoidableBypass = true;
IngameMenu.main.OnDeselect();
IsAvoidableBypass = false;
}
}
/// <summary>
/// Called when this modal is deselected (only when pressing outside of the modal)
/// </summary>
public virtual void OnDeselect() { }
/// <summary>
/// This creates the modal when showing it for the first time, you can't modify it afterwards
/// </summary>
private void InitSubWindow()
{
if (!IngameMenu.main)
{
throw new NotSupportedException($"Cannot show ingame subwindow {SubWindowName} because the ingame window does not exist.");
}
if (!modalSubWindow)
{
GameObject derivedSubWindow = IngameMenu.main.transform.Find("QuitConfirmation").gameObject;
modalSubWindow = UnityEngine.Object.Instantiate(derivedSubWindow, IngameMenu.main.transform, false);
modalSubWindow.name = SubWindowName;
// Styling.
RectTransform main = modalSubWindow.GetComponent<RectTransform>();
main.sizeDelta = new Vector2(700, Height);
RectTransform messageTransform = modalSubWindow.FindChild("Header").GetComponent<RectTransform>();
messageTransform.sizeDelta = new Vector2(700, Height);
messageTransform.anchoredPosition = new Vector2(0, 50 - Height / 2);
}
modalSubWindow.GetComponent<Image>().color = Color.white.WithAlpha(Transparency);
// Will happen either it's initialized or not
UpdateModal();
}
/// <summary>
/// Update the modal with informations that may change from one Show() to another
/// </summary>
private void UpdateModal()
{
text = modalSubWindow.FindChild("Header").GetComponent<TextMeshProUGUI>();
text.text = ModalText;
GameObject buttonYesObject = modalSubWindow.FindChild("ButtonYes");
GameObject buttonNoObject = modalSubWindow.FindChild("ButtonNo");
Button yesButton = buttonYesObject.GetComponent<Button>();
// We need to reinitialize onClick to avoid keeping Persisted Events (which are set manually inside Unity's Editor)
yesButton.onClick = new Button.ButtonClickedEvent();
yesButton.onClick.AddListener(ClickYes);
buttonYesObject.GetComponentInChildren<TextMeshProUGUI>().text = YesButtonText;
RectTransform yesButtonTransform = buttonYesObject.GetComponent<RectTransform>();
yesButtonTransform.anchoredPosition = new Vector2(yesButtonTransform.anchoredPosition.x, 50f - Height);
// TODO: fix yes and no button positions
if (HideNoButton)
{
UnityEngine.Object.Destroy(buttonNoObject);
buttonYesObject.transform.position = new Vector3(modalSubWindow.transform.position.x / 2, buttonYesObject.transform.position.y, buttonYesObject.transform.position.z); // Center Button
return;
}
if (buttonNoObject)
{
Button noButton = buttonNoObject.GetComponent<Button>();
noButton.onClick = new Button.ButtonClickedEvent();
noButton.onClick.AddListener(ClickNo);
buttonNoObject.GetComponentInChildren<TextMeshProUGUI>().text = NoButtonText;
RectTransform noButtonTransform = buttonNoObject.GetComponent<RectTransform>();
noButtonTransform.anchoredPosition = new Vector2(noButtonTransform.anchoredPosition.x, 50f - Height);
}
}
public virtual void ClickYes() { }
public virtual void ClickNo() { }
private IEnumerator ShowImplementation()
{
// Execute frame-by-frame to allow UI scripts to initialize.
InitSubWindow();
yield return new WaitForEndOfFrame();
// Equivalent of IngameMenu.main.Open() but without minding for the freeze
IngameMenu.main.gameObject.SetActive(true);
IngameMenu.main.Select();
yield return new WaitForEndOfFrame();
IngameMenu.main.ChangeSubscreen(SubWindowName);
yield return new WaitForEndOfFrame();
if (FreezeGame)
{
FreezeTime.Begin(FreezeTime.Id.Quit);
}
}
/// <summary>
/// Lets you get any existing Modal by its Type
/// </summary>
/// <typeparam name="T">The type of the modal to get</typeparam>
/// <returns>An existing instance of the modal if it already exists, else, a new one</returns>
public static T Get<T>() where T : Modal
{
if (Modals.TryGetValue(typeof(T), out Modal modal))
{
return (T)modal;
}
// No need to add entry in dictionary as it's done in constructor
return (T)Activator.CreateInstance(typeof(T));
}
}