refactor(ui): own retained controller lifetimes

This commit is contained in:
Erik 2026-07-10 23:35:26 +02:00
parent 921c388e2c
commit 5d9e98c118
21 changed files with 373 additions and 35 deletions

View file

@ -41,7 +41,7 @@ namespace AcDream.App.UI.Layout;
/// the gate only affects whether we proactively query; recorded in the divergence register.
/// </para>
/// </summary>
public sealed class SelectedObjectController
public sealed class SelectedObjectController : IRetainedPanelController
{
// ── Element ids (toolbar LayoutDesc 0x21000016) ─────────────────────────
/// <summary>Selected-object container / field element id (retail m_pSelObjectField).</summary>
@ -82,11 +82,14 @@ public sealed class SelectedObjectController
private readonly Func<uint, bool> _hasHealth;
private readonly Func<uint, uint> _stackSize;
private readonly Action<uint> _sendQueryHealth;
private readonly Action<Action<uint?>> _unsubscribeSelectionChanged;
private readonly Action<Action<uint, float>> _unsubscribeHealthChanged;
// ── Live state (read by closures on the per-frame draw path) ────────────
private uint? _current;
private string? _currentName;
private double _flashRemaining; // > 0 while the selection overlay is flashing
private bool _disposed;
/// <summary>White label color for the name line.</summary>
private static readonly Vector4 NameColor = new(1f, 1f, 1f, 1f);
@ -94,7 +97,9 @@ public sealed class SelectedObjectController
private SelectedObjectController(
ImportedLayout layout,
Action<Action<uint?>> subscribeSelectionChanged,
Action<Action<uint?>> unsubscribeSelectionChanged,
Action<Action<uint, float>> subscribeHealthChanged,
Action<Action<uint, float>> unsubscribeHealthChanged,
Func<uint, bool> isHealthTarget,
Func<uint, string?> name,
Func<uint, float> healthPercent,
@ -109,6 +114,8 @@ public sealed class SelectedObjectController
_hasHealth = hasHealth;
_stackSize = stackSize;
_sendQueryHealth = sendQueryHealth;
_unsubscribeSelectionChanged = unsubscribeSelectionChanged;
_unsubscribeHealthChanged = unsubscribeHealthChanged;
// Find elements — silently skip absent ones (partial/test layouts).
_name = layout.FindElement(NameId);
@ -189,7 +196,9 @@ public sealed class SelectedObjectController
public static SelectedObjectController Bind(
ImportedLayout layout,
Action<Action<uint?>> subscribeSelectionChanged,
Action<Action<uint?>> unsubscribeSelectionChanged,
Action<Action<uint, float>> subscribeHealthChanged,
Action<Action<uint, float>> unsubscribeHealthChanged,
Func<uint, bool> isHealthTarget,
Func<uint, string?> name,
Func<uint, float> healthPercent,
@ -198,7 +207,8 @@ public sealed class SelectedObjectController
Action<uint> sendQueryHealth,
UiDatFont? datFont)
=> new SelectedObjectController(
layout, subscribeSelectionChanged, subscribeHealthChanged,
layout, subscribeSelectionChanged, unsubscribeSelectionChanged,
subscribeHealthChanged, unsubscribeHealthChanged,
isHealthTarget, name, healthPercent, hasHealth, stackSize, sendQueryHealth, datFont);
/// <summary>
@ -268,4 +278,12 @@ public sealed class SelectedObjectController
{
_overlay?.TrySetRetailState(state);
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
_unsubscribeSelectionChanged(OnSelectionChanged);
_unsubscribeHealthChanged(OnHealthChanged);
}
}