using System;
namespace AcDream.App.UI.Layout;
///
/// Owns which of the two vitals windows is visible — retail's
/// "Side By Side Vitals" character option (Options → Character tab).
///
///
/// Retail authors TWO complete vitals windows and swaps their visibility on
/// the option bit; nothing is rearranged in place:
///
///
/// - gmFloatyVitalsUI (LayoutDesc 0x2100006C, the
/// stacked 160x58 window): UpdateFromPlayerModule @0x004CF140 —
/// visible iff PlayerModule::SideBySideVitals() == 0.
/// - gmFloatySideVitalsUI (LayoutDesc 0x21000075, the
/// 460x26 single-row window): UpdateFromPlayerModule @0x004D0810 —
/// visible iff the bit is set.
/// - Live apply: gmGamePlayUI::RecvNotice_PlayerOptionChanged
/// @0x004E9DA0 flips both windows the moment the
/// SideBySideVitals_PlayerOption (0x13) changes; both
/// UpdateFromPlayerModule bodies re-apply on PlayerDescReceived.
/// - The bit is PlayerModule::SideBySideVitals @0x005D3070
/// = (options_ >> 0x15) & 1 — CharacterOptions1 0x00200000,
/// option id 0x13, riding the 0x01A1 PlayerModule blob (not a 0x0005
/// auto-save id) — CharacterOptionTable row confirmed against ACE.
///
///
///
/// acdream shape: the option lives in the J4 Runtime owner
/// (RuntimeCharacterState.Options); this controller polls the borrowed
/// bit once per frame from RetailUiRuntime.Tick (the OP campaign's
/// established live-apply seam) and applies BOTH windows' visibility on the
/// edge — covering the mount-time default, the PlayerModule blob arriving
/// after mount, and the Character tab's live toggle with one mechanism.
/// Both window names ride stateManagedVisibilityWindows so the saved
/// window layout never restores a visibility this option owns.
///
///
public sealed class VitalsSideBySideController
{
private readonly UiRoot _root;
private readonly Func _sideBySideVitals;
private readonly string _stackedWindow;
private readonly string _sideWindow;
private bool? _applied;
public VitalsSideBySideController(
UiRoot root,
Func sideBySideVitals,
string stackedWindow,
string sideWindow)
{
_root = root ?? throw new ArgumentNullException(nameof(root));
_sideBySideVitals = sideBySideVitals
?? throw new ArgumentNullException(nameof(sideBySideVitals));
_stackedWindow = stackedWindow;
_sideWindow = sideWindow;
}
/// The last applied bit (null before the first apply). Exposed for tests.
public bool? Applied => _applied;
public void Tick()
{
bool sideBySide = _sideBySideVitals();
if (_applied == sideBySide) return;
_applied = sideBySide;
// Retail's exact swap (RecvNotice_PlayerOptionChanged @0x004E9DBE):
// option ON → stacked hidden, side shown; OFF → the opposite.
if (sideBySide)
{
_root.HideWindow(_stackedWindow);
_root.ShowWindow(_sideWindow);
}
else
{
_root.ShowWindow(_stackedWindow);
_root.HideWindow(_sideWindow);
}
}
}