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); } } }