using System.Globalization; namespace AcDream.App.UI.Layout; /// /// Binds retail's SmartBox frame-rate display to the authored text element from /// LayoutDesc 0x2100000F. /// /// /// Faithful port of gmSmartBoxUI::RecvNotice_SetFramerateDisplay @ 0x004D65E0, /// gmSmartBoxUI::UpdateFPSMeter @ 0x004D63A0, and /// gmSmartBoxUI::UseTime @ 0x004D6E30. Portal string table 0x23000001, /// string 0x0DCFFF73 supplies the two labels FPS: and DEG:; /// retail inserts both floating-point values with two decimal places. /// public sealed class RetailFpsController { public const uint LayoutId = 0x2100000Fu; public const uint DisplayElementId = 0x10000047u; private readonly UiText _display; private readonly Func _framesPerSecond; private readonly Func _degradeMultiplier; private readonly Func _isVisible; private RetailFpsController( UiText display, Func framesPerSecond, Func degradeMultiplier, Func isVisible) { _display = display; _framesPerSecond = framesPerSecond; _degradeMultiplier = degradeMultiplier; _isVisible = isVisible; // The selected catalog element is authored at (1,1) inside gmSmartBoxUI. // Selective LayoutDesc import makes it a root, so restore that parent-relative // placement explicitly instead of letting it drift to (0,0). _display.Left = 1f; _display.Top = 1f; _display.Padding = 1f; _display.Selectable = false; _display.LinesProvider = BuildLines; Tick(); } public UiText Display => _display; public static RetailFpsController? Bind( ImportedLayout layout, Func framesPerSecond, Func degradeMultiplier, Func isVisible) { ArgumentNullException.ThrowIfNull(layout); ArgumentNullException.ThrowIfNull(framesPerSecond); ArgumentNullException.ThrowIfNull(degradeMultiplier); ArgumentNullException.ThrowIfNull(isVisible); return layout.FindElement(DisplayElementId) is UiText display ? new RetailFpsController(display, framesPerSecond, degradeMultiplier, isVisible) : null; } /// Mirrors the retail notice-driven show/hide state each UI tick. public void Tick() => _display.Visible = _isVisible(); private IReadOnlyList BuildLines() { string fps = _framesPerSecond().ToString("F2", CultureInfo.InvariantCulture); string degrade = _degradeMultiplier().ToString("F2", CultureInfo.InvariantCulture); return [ new UiText.Line($"FPS: {fps}", _display.DefaultColor), new UiText.Line($"DEG: {degrade}", _display.DefaultColor), ]; } }