SHA256
Document UI And Localization
This commit is contained in:
8 files changed
+910
-71
No files matched your search
@@ -11,14 +11,29 @@ using Image = UnityEngine.UI.Image;
|
||||
|
||||
namespace ADOFAIRenderer.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Description of one button of a <see cref="CommonWarningDialogUI"/> dialog: label, click action and optional color.
|
||||
/// </summary>
|
||||
internal class DialogButton
|
||||
{
|
||||
// ReSharper disable FieldCanBeMadeReadOnly.Global
|
||||
/// <summary>Button label, shown as given (callers pass already localized text).</summary>
|
||||
public string Text;
|
||||
|
||||
/// <summary>
|
||||
/// Action run on the Unity main thread when the button is clicked. The dialog is destroyed after the action
|
||||
/// returns, also when the action throws (the exception is logged).
|
||||
/// </summary>
|
||||
public Action OnClick;
|
||||
|
||||
/// <summary>Normal (idle) background color of the button; <c>null</c> keeps the O5Kit theme button color.</summary>
|
||||
public Color? BackgroundColor;
|
||||
// ReSharper restore FieldCanBeMadeReadOnly.Global
|
||||
|
||||
/// <summary>Creates a button description.</summary>
|
||||
/// <param name="text">Button label.</param>
|
||||
/// <param name="onClick">Action run on click, before the dialog closes.</param>
|
||||
/// <param name="backgroundColor">Optional normal background color; <c>null</c> uses the theme color.</param>
|
||||
public DialogButton(string text, Action onClick, Color? backgroundColor = null)
|
||||
{
|
||||
Text = text;
|
||||
@@ -27,10 +42,29 @@ internal class DialogButton
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modal message dialog with a title, a description and one or more buttons.
|
||||
/// <para>
|
||||
/// Each <see cref="ShowDialog"/> 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 (<see cref="UIDocument"/>) is disabled so that it cannot
|
||||
/// receive input; the roots are enabled again in <see cref="OnDestroy"/>. Clicking any button runs its action and then
|
||||
/// destroys the dialog. All members run on the Unity main thread.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal class CommonWarningDialogUI : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The most recently created dialog, or <c>null</c> when that dialog has been destroyed.
|
||||
/// <c>FirstUpdateHook</c> checks it to wait until a startup dialog is closed.
|
||||
/// If several dialogs are open at once, this refers to the newest one only.
|
||||
/// </summary>
|
||||
public static CommonWarningDialogUI? Instance { get; private set; }
|
||||
|
||||
/// <summary>UI Toolkit root elements disabled by this dialog, enabled again when the dialog is destroyed.</summary>
|
||||
private readonly List<VisualElement> _disabledElements = new();
|
||||
|
||||
/// <summary>O5Kit window holding the title, description and buttons; disposed in <see cref="OnDestroy"/>.</summary>
|
||||
private O5Window? _window;
|
||||
|
||||
private void Awake()
|
||||
@@ -38,6 +72,10 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the window, enables the UI Toolkit roots disabled in <see cref="Initialize"/> and clears
|
||||
/// <see cref="Instance"/> if it still refers to this dialog.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
_window?.Dispose();
|
||||
@@ -58,6 +96,15 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and shows a new dialog. Must be called on the Unity main thread.
|
||||
/// </summary>
|
||||
/// <param name="title">Window title text.</param>
|
||||
/// <param name="description">Body text, centered and word-wrapped; TextMeshPro rich text tags are allowed.</param>
|
||||
/// <param name="buttons">
|
||||
/// 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.
|
||||
/// </param>
|
||||
public static void ShowDialog(string title, string description, params DialogButton[] buttons)
|
||||
{
|
||||
if (buttons.Length == 0) return;
|
||||
@@ -68,8 +115,14 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
dialog.Initialize(title, description, buttons);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the canvas, backdrop, window and contents. Layout sizes are in canvas units at the 1920x1080 reference
|
||||
/// resolution of the <see cref="CanvasScaler"/>.
|
||||
/// </summary>
|
||||
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");
|
||||
@@ -78,6 +131,8 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
eventSystemObj.AddComponent<UnityEngine.EventSystems.StandaloneInputModule>();
|
||||
}
|
||||
|
||||
// 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<UIDocument>(FindObjectsSortMode.None))
|
||||
{
|
||||
if (doc && doc.rootVisualElement is { enabledSelf: true })
|
||||
@@ -87,14 +142,17 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// Set UI layer (5)
|
||||
// Set UI layer (5, Unity's built-in "UI" layer)
|
||||
gameObject.layer = 5;
|
||||
|
||||
// Canvas setup
|
||||
// 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<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 32766;
|
||||
|
||||
// Scale with screen size against a 1920x1080 reference; match 0.5 blends width and height scaling equally.
|
||||
var scaler = gameObject.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(1920, 1080);
|
||||
@@ -105,7 +163,8 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
|
||||
var ctx = RendererUIManager.Context;
|
||||
|
||||
// 1. Create full screen backdrop (scrim) to block background input
|
||||
// 1. Create full screen backdrop (scrim) to block background input.
|
||||
// Black at 65% opacity, stretched over the whole canvas; raycastTarget makes it swallow clicks.
|
||||
var bgPanelObj = new GameObject("Backdrop");
|
||||
bgPanelObj.transform.SetParent(transform, false);
|
||||
bgPanelObj.layer = 5;
|
||||
@@ -120,7 +179,8 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
bgRect.offsetMin = Vector2.zero;
|
||||
bgRect.offsetMax = Vector2.zero;
|
||||
|
||||
// 2. Create actual native O5Window
|
||||
// 2. Create the O5Kit window as a child of the backdrop: 540x240 units, draggable, fixed size,
|
||||
// no close button (the dialog closes only through its buttons), 16-unit inner padding.
|
||||
_window = O5Window.Create(ctx, bgPanelObj.transform, new O5WindowOptions
|
||||
{
|
||||
Id = "RendererMod_WarningDialog",
|
||||
@@ -133,7 +193,8 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
Padding = new RectOffset(16, 16, 16, 16)
|
||||
});
|
||||
|
||||
// 3. Build content inside window.Content
|
||||
// 3. Build content inside window.Content: a vertical stack of the description (takes the remaining height)
|
||||
// and the button row (40 units high), 16 units apart.
|
||||
var content = _window.Content;
|
||||
|
||||
var vLayout = content.gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
@@ -144,7 +205,7 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
vLayout.childForceExpandWidth = true;
|
||||
vLayout.childForceExpandHeight = false;
|
||||
|
||||
// Description text
|
||||
// Description text: 18 pt, centered, word-wrapped, light gray (88% white)
|
||||
var descText = O5Factory.ControlText(ctx, content, 18f, noPad: true);
|
||||
descText.text = description;
|
||||
descText.alignment = TextAlignmentOptions.Center;
|
||||
@@ -157,7 +218,7 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
descLE.flexibleWidth = 1f;
|
||||
descLE.flexibleHeight = 1f;
|
||||
|
||||
// Button action row
|
||||
// Button action row: buttons share the row width equally, 10 units apart
|
||||
var btnRowObj = new GameObject("ButtonRow");
|
||||
btnRowObj.transform.SetParent(content, false);
|
||||
var btnRowRect = btnRowObj.AddComponent<RectTransform>();
|
||||
@@ -176,6 +237,7 @@ internal class CommonWarningDialogUI : MonoBehaviour
|
||||
for (var i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
var btnData = buttons[i];
|
||||
// The click handler runs the caller's action, logs any exception from it, and always destroys the dialog.
|
||||
var btn = O5Factory.Button(
|
||||
ctx: ctx,
|
||||
parent: btnRowRect,
|
||||
|
||||
Reference in new issue
Block a user