feat(ui): centralize retail selection state

This commit is contained in:
Erik 2026-07-11 00:51:20 +02:00
parent c7607f019c
commit 7983309d23
30 changed files with 591 additions and 108 deletions

View file

@ -1,6 +1,7 @@
using System;
using System.Numerics;
using AcDream.App.UI;
using AcDream.Core.Selection;
namespace AcDream.App.UI.Layout;
@ -82,7 +83,7 @@ public sealed class SelectedObjectController : IRetainedPanelController
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 SelectionState _selection;
private readonly Action<Action<uint, float>> _unsubscribeHealthChanged;
// ── Live state (read by closures on the per-frame draw path) ────────────
@ -96,8 +97,7 @@ public sealed class SelectedObjectController : IRetainedPanelController
private SelectedObjectController(
ImportedLayout layout,
Action<Action<uint?>> subscribeSelectionChanged,
Action<Action<uint?>> unsubscribeSelectionChanged,
SelectionState selection,
Action<Action<uint, float>> subscribeHealthChanged,
Action<Action<uint, float>> unsubscribeHealthChanged,
Func<uint, bool> isHealthTarget,
@ -114,7 +114,7 @@ public sealed class SelectedObjectController : IRetainedPanelController
_hasHealth = hasHealth;
_stackSize = stackSize;
_sendQueryHealth = sendQueryHealth;
_unsubscribeSelectionChanged = unsubscribeSelectionChanged;
_selection = selection ?? throw new ArgumentNullException(nameof(selection));
_unsubscribeHealthChanged = unsubscribeHealthChanged;
// Find elements — silently skip absent ones (partial/test layouts).
@ -171,8 +171,10 @@ public sealed class SelectedObjectController : IRetainedPanelController
}
// Register the handlers LAST so the initial state is fully set up first.
subscribeSelectionChanged(OnSelectionChanged);
_selection.Changed += OnSelectionTransition;
subscribeHealthChanged(OnHealthChanged);
if (_selection.SelectedObjectId is { } initial)
ApplySelection(initial);
}
/// <summary>
@ -180,8 +182,7 @@ public sealed class SelectedObjectController : IRetainedPanelController
/// Port of retail <c>gmToolbarUI::HandleSelectionChanged</c> + <c>RecvNotice_UpdateObjectHealth</c>.
/// </summary>
/// <param name="layout">Imported toolbar layout (LayoutDesc 0x21000016).</param>
/// <param name="subscribeSelectionChanged">Called once with <see cref="OnSelectionChanged"/>
/// (typical host: <c>h =&gt; SelectionChanged += h</c>).</param>
/// <param name="selection">The single Core selected-object owner.</param>
/// <param name="subscribeHealthChanged">Called once with <see cref="OnHealthChanged"/>
/// (typical host: <c>h =&gt; Combat.HealthChanged += h</c>) — drives meter visibility.</param>
/// <param name="isHealthTarget">Returns true for guids that may show a health meter
@ -195,8 +196,7 @@ public sealed class SelectedObjectController : IRetainedPanelController
/// <param name="datFont">Dat font for the name label; null = debug bitmap font fallback.</param>
public static SelectedObjectController Bind(
ImportedLayout layout,
Action<Action<uint?>> subscribeSelectionChanged,
Action<Action<uint?>> unsubscribeSelectionChanged,
SelectionState selection,
Action<Action<uint, float>> subscribeHealthChanged,
Action<Action<uint, float>> unsubscribeHealthChanged,
Func<uint, bool> isHealthTarget,
@ -207,7 +207,7 @@ public sealed class SelectedObjectController : IRetainedPanelController
Action<uint> sendQueryHealth,
UiDatFont? datFont)
=> new SelectedObjectController(
layout, subscribeSelectionChanged, unsubscribeSelectionChanged,
layout, selection,
subscribeHealthChanged, unsubscribeHealthChanged,
isHealthTarget, name, healthPercent, hasHealth, stackSize, sendQueryHealth, datFont);
@ -215,7 +215,7 @@ public sealed class SelectedObjectController : IRetainedPanelController
/// Port of <c>gmToolbarUI::HandleSelectionChanged</c> (<c>:198635</c>):
/// clear-then-populate the selected-object strip on any selection change.
/// </summary>
public void OnSelectionChanged(uint? guid)
private void ApplySelection(uint? guid)
{
// ── 1. Clear first (retail: SetText("") + m_pSelObjectField->SetState(0)
// + SetVisible(0) on the meters). ──────────────────────────────────────
@ -279,11 +279,14 @@ public sealed class SelectedObjectController : IRetainedPanelController
_overlay?.TrySetRetailState(state);
}
private void OnSelectionTransition(SelectionTransition transition)
=> ApplySelection(transition.SelectedObjectId);
public void Dispose()
{
if (_disposed) return;
_disposed = true;
_unsubscribeSelectionChanged(OnSelectionChanged);
_selection.Changed -= OnSelectionTransition;
_unsubscribeHealthChanged(OnHealthChanged);
}
}