refactor(ui): own retained controller lifetimes
This commit is contained in:
parent
921c388e2c
commit
5d9e98c118
21 changed files with 373 additions and 35 deletions
|
|
@ -24,9 +24,10 @@ namespace AcDream.App.UI.Layout;
|
|||
/// and bound in place.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class ChatWindowController : IRetainedWindowStateController
|
||||
public sealed class ChatWindowController : IRetainedWindowStateController, IRetainedPanelController
|
||||
{
|
||||
public const uint LayoutId = 0x21000006u;
|
||||
private bool _disposed;
|
||||
|
||||
// Element ids from chat LayoutDesc 0x21000006 (confirmed in Task D/G1).
|
||||
private const uint RootId = 0x1000000Eu;
|
||||
|
|
@ -509,4 +510,10 @@ public sealed class ChatWindowController : IRetainedWindowStateController
|
|||
ChatKind.Combat => new(0.96f, 0.459f, 0.447f, 1f), // colorLightRed
|
||||
_ => new(0.824f, 0.824f, 0.784f, 1f), // colorGrey (fallback)
|
||||
};
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace AcDream.App.UI.Layout;
|
|||
/// Container-switching is live (click a side bag → Use 0x0036 → ViewContents 0x0196 full-replace);
|
||||
/// drag-into-bag / wield-drop wire are later sub-phases.
|
||||
/// </summary>
|
||||
public sealed class InventoryController : IItemListDragHandler
|
||||
public sealed class InventoryController : IItemListDragHandler, IRetainedPanelController
|
||||
{
|
||||
// Element ids — spec §1 (dat dump of 0x21000022 / 0x21000021 + the *::PostInit binds).
|
||||
public const uint ContentsGridId = 0x100001C6u; // gm3DItemsUI m_itemList ("Contents of Backpack")
|
||||
|
|
@ -58,6 +58,7 @@ public sealed class InventoryController : IItemListDragHandler
|
|||
private readonly Action<uint>? _sendNoLongerViewing;
|
||||
private readonly Action<uint, uint, int>? _sendPutItemInContainer; // (item, container, placement)
|
||||
private readonly ItemInteractionController? _itemInteraction;
|
||||
private bool _disposed;
|
||||
|
||||
// ACE PropertyInt ids read by retail InqLoad (decomp 0x0058f130: InqInt(5)/InqInt(0xe6)).
|
||||
private const uint EncumbranceValProperty = 5u; // total carried burden
|
||||
|
|
@ -528,6 +529,8 @@ public sealed class InventoryController : IItemListDragHandler
|
|||
/// <summary>Detach event handlers (idempotent).</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
_objects.ObjectAdded -= OnObjectChanged;
|
||||
_objects.ObjectMoved -= OnObjectMoved;
|
||||
_objects.ObjectRemoved -= OnObjectChanged;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace AcDream.App.UI.Layout;
|
|||
/// 175480 / 173620). Slice 1: equip slots only — no 3D doll viewport (that's Slice 2).
|
||||
/// Unwield is handled by InventoryController (dragging an equipped item onto the pack grid).
|
||||
/// </summary>
|
||||
public sealed class PaperdollController : IItemListDragHandler
|
||||
public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelController
|
||||
{
|
||||
// ── Slots-toggle public surface ───────────────────────────────────────────────────────────────
|
||||
/// <summary>
|
||||
|
|
@ -82,6 +82,7 @@ public sealed class PaperdollController : IItemListDragHandler
|
|||
private readonly PaperdollViewState _viewState = new();
|
||||
private readonly List<UiItemList> _armorSlots = new();
|
||||
private UiElement? _dollViewport; // UiViewport wired in Slice 3; UiElement? keeps this task independent
|
||||
private bool _disposed;
|
||||
|
||||
private PaperdollController(
|
||||
ImportedLayout layout, ClientObjectTable objects, Func<uint> playerGuid,
|
||||
|
|
@ -244,6 +245,8 @@ public sealed class PaperdollController : IItemListDragHandler
|
|||
/// <summary>Detach event handlers (idempotent).</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
_objects.ObjectAdded -= OnObjectChanged;
|
||||
_objects.ObjectMoved -= OnObjectMoved;
|
||||
_objects.ObjectRemoved -= OnObjectChanged;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace AcDream.App.UI.Layout;
|
|||
/// LayoutDesc <c>0x21000074</c> tree. Like the other gm* controllers, this class only
|
||||
/// finds children by retail id and attaches live providers; it does not recreate DAT chrome.
|
||||
/// </summary>
|
||||
public sealed class RadarController
|
||||
public sealed class RadarController : IRetainedPanelController
|
||||
{
|
||||
public const uint LayoutId = 0x21000074u;
|
||||
/// <summary>Production layout property 0x1000002D, recovered directly from the retail DAT.</summary>
|
||||
|
|
@ -39,6 +39,7 @@ public sealed class RadarController
|
|||
private string? _lastCoordinates;
|
||||
private bool _coordinatesInitialized;
|
||||
private bool? _lastUiLocked;
|
||||
private bool _disposed;
|
||||
|
||||
private RadarController(
|
||||
ImportedLayout layout,
|
||||
|
|
@ -235,6 +236,17 @@ public sealed class RadarController
|
|||
? Array.Empty<UiText.Line>()
|
||||
: new[] { new UiText.Line(_lastCoordinates, CoordinateColor) };
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
_radar.SnapshotProvider = null;
|
||||
_radar.SelectObject = null;
|
||||
_radar.HoveredObjectChanged = null;
|
||||
if (_lockButton is not null)
|
||||
_lockButton.OnClick = null;
|
||||
}
|
||||
|
||||
private readonly record struct CompassToken(
|
||||
UiElement? Element,
|
||||
float Magnitude,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace AcDream.App.UI.Layout;
|
|||
/// <c>CreateObject</c> resolves a formerly-unknown guid.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class ToolbarController : IItemListDragHandler
|
||||
public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelController
|
||||
{
|
||||
// Slot element ids in slot-index order (toolbar LayoutDesc 0x21000016, pre-dump).
|
||||
// Row 1 = slots 0-8 (0x100001A7..0x100001AF), Row 2 = slots 9-17 (0x100006B7..0x100006BF).
|
||||
|
|
@ -62,6 +62,7 @@ public sealed class ToolbarController : IItemListDragHandler
|
|||
private readonly UiButton? _characterButton;
|
||||
private readonly UiButton? _inventoryButton;
|
||||
private readonly ClientObjectTable _repo;
|
||||
private readonly CombatState? _combatState;
|
||||
private readonly Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> _shortcuts;
|
||||
private readonly Func<ItemType, uint, uint, uint, uint, uint> _iconIds; // (itemType, icon, underlay, overlay, effects) → GL tex
|
||||
private readonly Action<uint> _useItem; // guid → fire UseObject
|
||||
|
|
@ -70,6 +71,7 @@ public sealed class ToolbarController : IItemListDragHandler
|
|||
private readonly Action<uint, uint>? _sendAddShortcut; // (index, objectGuid)
|
||||
private readonly Action<uint>? _sendRemoveShortcut; // (index)
|
||||
private readonly ItemInteractionController? _itemInteraction;
|
||||
private bool _disposed;
|
||||
|
||||
// Digit sprite DID arrays for slot labels (top row, numbers 1-9).
|
||||
// Read from LayoutDesc 0x21000037, element 0x1000034A under composite 0x10000346.
|
||||
|
|
@ -98,6 +100,7 @@ public sealed class ToolbarController : IItemListDragHandler
|
|||
Action<uint>? sendRemoveShortcut = null)
|
||||
{
|
||||
_repo = repo;
|
||||
_combatState = combatState;
|
||||
_shortcuts = shortcuts;
|
||||
_iconIds = iconIds;
|
||||
_useItem = useItem;
|
||||
|
|
@ -141,15 +144,21 @@ public sealed class ToolbarController : IItemListDragHandler
|
|||
SetCombatMode(CombatMode.NonCombat);
|
||||
|
||||
// Wire live combat-mode changes if a CombatState was provided.
|
||||
if (combatState is not null)
|
||||
combatState.CombatModeChanged += SetCombatMode;
|
||||
if (_combatState is not null)
|
||||
_combatState.CombatModeChanged += SetCombatMode;
|
||||
|
||||
// D.5.4: the table now holds ALL objects (creatures, NPCs, etc.), so filter
|
||||
// to our 18 shortcut guids — else every creature spawn in a busy zone
|
||||
// needlessly re-populates the bar (gmToolbarUI::SetDelayedShortcutNum pattern).
|
||||
repo.ObjectAdded += o => { if (IsShortcutGuid(o.ObjectId)) Populate(); };
|
||||
repo.ObjectUpdated += o => { if (IsShortcutGuid(o.ObjectId)) Populate(); };
|
||||
repo.ObjectRemoved += o => { if (IsShortcutGuid(o.ObjectId)) Populate(); };
|
||||
repo.ObjectAdded += OnRepositoryObjectChanged;
|
||||
repo.ObjectUpdated += OnRepositoryObjectChanged;
|
||||
repo.ObjectRemoved += OnRepositoryObjectChanged;
|
||||
}
|
||||
|
||||
private void OnRepositoryObjectChanged(ClientObject obj)
|
||||
{
|
||||
if (IsShortcutGuid(obj.ObjectId))
|
||||
Populate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -409,4 +418,16 @@ public sealed class ToolbarController : IItemListDragHandler
|
|||
}
|
||||
Populate();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
if (_combatState is not null)
|
||||
_combatState.CombatModeChanged -= SetCombatMode;
|
||||
_repo.ObjectAdded -= OnRepositoryObjectChanged;
|
||||
_repo.ObjectUpdated -= OnRepositoryObjectChanged;
|
||||
_repo.ObjectRemoved -= OnRepositoryObjectChanged;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue