using System;
using System.Collections.Generic;
using MelonLoader;
using O5Kit.Core;
using O5Kit.Factory;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.UIElements;
using Image = UnityEngine.UI.Image;
namespace ADOFAIRenderer.UI;
///
/// Description of one button of a dialog: label, click action and optional color.
///
internal class DialogButton
{
// ReSharper disable FieldCanBeMadeReadOnly.Global
/// Button label, shown as given (callers pass already localized text).
public string Text;
///
/// Action run on the main Unity thread when the button is clicked. The dialog is destroyed after the action
/// returns, also when the action throws (the exception is logged).
///
public Action OnClick;
/// Normal (idle) background color of the button; null keeps the O5Kit theme button color.
public Color? BackgroundColor;
// ReSharper restore FieldCanBeMadeReadOnly.Global
/// Creates a button description.
/// Button label.
/// Action run on click, before the dialog closes.
/// Optional normal background color; null uses the theme color.
public DialogButton(string text, Action onClick, Color? backgroundColor = null)
{
Text = text;
OnClick = onClick;
BackgroundColor = backgroundColor;
}
}
///
/// Modal message dialog with a title, a description and one or more buttons.
///
/// Each call creates its own GameObject (kept across scene loads) with a screen-space overlay
/// canvas: a translucent full-screen backdrop that blocks input to everything below, and an O5Kit window on top of it.
/// While the dialog exists, every enabled UI Toolkit root () is disabled so that it cannot
/// receive input; the roots are enabled again in . Clicking any button runs its action and then
/// destroys the dialog. All members run on the main Unity thread.
///
///
internal class CommonWarningDialogUI : MonoBehaviour
{
///
/// The most recently created dialog, or null when that dialog has been destroyed.
/// FirstUpdateHook checks it to wait until a startup dialog is closed.
/// If several dialogs are open at once, this refers to the newest one only.
///
public static CommonWarningDialogUI? Instance { get; private set; }
/// UI Toolkit root elements disabled by this dialog, enabled again when the dialog is destroyed.
private readonly List _disabledElements = new();
/// O5Kit window holding the title, description and buttons; disposed in .
private O5Window? _window;
private void Awake()
{
Instance = this;
}
///
/// Disposes the window, enables the UI Toolkit roots disabled in and clears
/// if it still refers to this dialog.
///
private void OnDestroy()
{
_window?.Dispose();
_window = null;
foreach (var elem in _disabledElements)
{
if (elem != null)
{
elem.SetEnabled(true);
}
}
_disabledElements.Clear();
if (Instance == this)
{
Instance = null;
}
}
///
/// Creates and shows a new dialog. Must be called on the main Unity thread.
///
/// Window title text.
/// Body text, centered and word-wrapped; TextMeshPro rich text tags are allowed.
///
/// Buttons laid out left to right with equal widths. With no buttons nothing is shown, because the dialog
/// has no close button and could not be dismissed.
///
public static void ShowDialog(string title, string description, params DialogButton[] buttons)
{
if (buttons.Length == 0) return;
var obj = new GameObject("CommonWarningDialogUI");
DontDestroyOnLoad(obj);
var dialog = obj.AddComponent();
dialog.Initialize(title, description, buttons);
}
///
/// Builds the canvas, backdrop, window and contents. Layout sizes are in canvas units at the 1920x1080 reference
/// resolution of the .
///
private void Initialize(string title, string description, DialogButton[] buttons)
{
// uGUI buttons need an EventSystem to receive clicks. When the current scene has none, a child EventSystem
// is created; it is destroyed together with the dialog.
if (!UnityEngine.EventSystems.EventSystem.current)
{
var eventSystemObj = new GameObject("EventSystem");
eventSystemObj.transform.SetParent(transform);
eventSystemObj.AddComponent();
eventSystemObj.AddComponent();
}
// Disable every enabled UI Toolkit root so that it ignores input while the dialog is open.
// Roots that are already disabled are left alone and are not re-enabled in OnDestroy.
foreach (var doc in FindObjectsByType(FindObjectsSortMode.None))
{
if (doc && doc.rootVisualElement is { enabledSelf: true })
{
doc.rootVisualElement.SetEnabled(false);
_disabledElements.Add(doc.rootVisualElement);
}
}
// Set UI layer (5, Unity's built-in "UI" layer)
gameObject.layer = 5;
// Canvas setup.
// Sorting order 32766 draws the dialog above the RendererUIManager window canvas (32000), the render overlays
// (DebugMonitorUI 9999, EscapeHandlerUI 10000) and below FFmpegDownloadUI (32767, the largest sorting order).
var canvas = gameObject.AddComponent