fix(ui): complete retail indicator detail panels

Port the authored effect row template, remaining-time and selection details, synchronize the full gmPanelUI child geometry, and route the burden indicator to Character Information panel 3.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-17 11:36:43 +02:00
parent a96767ba6d
commit d1d603105f
24 changed files with 3696 additions and 147 deletions

View file

@ -21,8 +21,8 @@ public sealed class RetailPanelUiController : IDisposable
private uint? _activePanel;
private uint? _deferredPanel;
private bool _applying;
private bool _synchronizingPlacement;
private PanelPlacement? _mainPanelPlacement;
private bool _synchronizingGeometry;
private PanelGeometry? _mainPanelGeometry;
private bool _disposed;
public RetailPanelUiController(
@ -38,11 +38,11 @@ public sealed class RetailPanelUiController : IDisposable
public uint? ActivePanelId => _activePanel;
/// <summary>
/// Registers one of retail <c>gmPanelUI</c>'s primary toolbar children.
/// Inventory, Character, and Magic retain panel-specific content/resize
/// policy but share the one parent placement. A drag of any registered
/// child updates every hidden sibling through typed window handles, so
/// persistence observes the same canonical position too.
/// Registers one of retail <c>gmPanelUI</c>'s toolbar or detail children.
/// All primary and detail children retain panel-specific content but share
/// one parent geometry. A move or resize of any registered child updates
/// every hidden sibling through typed window handles, so persistence sees
/// the same canonical rectangle too.
/// </summary>
public void RegisterMainPanel(
uint panelId,
@ -61,7 +61,7 @@ public sealed class RetailPanelUiController : IDisposable
windowName,
restorePrevious,
window,
sharesMainPanelPlacement: true);
sharesMainPanelGeometry: true);
}
/// <param name="restorePrevious">
@ -75,14 +75,14 @@ public sealed class RetailPanelUiController : IDisposable
windowName,
restorePrevious,
window: null,
sharesMainPanelPlacement: false);
sharesMainPanelGeometry: false);
private void RegisterCore(
uint panelId,
string windowName,
bool restorePrevious,
RetailWindowHandle? window,
bool sharesMainPanelPlacement)
bool sharesMainPanelGeometry)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (panelId == 0) throw new ArgumentOutOfRangeException(nameof(panelId));
@ -95,14 +95,17 @@ public sealed class RetailPanelUiController : IDisposable
windowName,
restorePrevious,
window,
sharesMainPanelPlacement);
sharesMainPanelGeometry);
_byPanel.Add(panelId, entry);
_byWindow.Add(windowName, panelId);
if (window is not null)
{
window.Moved += OnWindowMoved;
window.Resized += OnWindowResized;
}
if (sharesMainPanelPlacement && _mainPanelPlacement is { } placement)
MoveWindowTo(entry, placement);
if (sharesMainPanelGeometry && _mainPanelGeometry is { } geometry)
ApplyWindowGeometry(entry, geometry);
if (_isVisible(windowName))
ObserveWindowVisibility(windowName, visible: true);
}
@ -144,7 +147,7 @@ public sealed class RetailPanelUiController : IDisposable
{
if (_activePanel == panelId)
{
PrepareMainPanelPlacement(requested);
PrepareMainPanelGeometry(requested);
return _show(requested.WindowName);
}
@ -156,7 +159,7 @@ public sealed class RetailPanelUiController : IDisposable
: null;
if (previous is { } previousEntry)
CaptureMainPanelPlacement(previousEntry, synchronizeSiblings: true);
CaptureMainPanelGeometry(previousEntry, synchronizeSiblings: true);
_deferredPanel = requested.RestorePrevious
&& previous is { RestorePrevious: false }
@ -165,7 +168,7 @@ public sealed class RetailPanelUiController : IDisposable
_activePanel = panelId;
if (previous is not null)
_hide(previous.Value.WindowName);
PrepareMainPanelPlacement(requested);
PrepareMainPanelGeometry(requested);
return _show(requested.WindowName);
}
@ -175,7 +178,7 @@ public sealed class RetailPanelUiController : IDisposable
return _hide(requested.WindowName);
}
CaptureMainPanelPlacement(requested, synchronizeSiblings: true);
CaptureMainPanelGeometry(requested, synchronizeSiblings: true);
bool hidden = _hide(requested.WindowName);
_activePanel = null;
if (_deferredPanel is not uint deferredId
@ -184,7 +187,7 @@ public sealed class RetailPanelUiController : IDisposable
_deferredPanel = null;
_activePanel = deferredId;
PrepareMainPanelPlacement(deferred);
PrepareMainPanelGeometry(deferred);
return _show(deferred.WindowName) || hidden;
}
finally
@ -205,76 +208,109 @@ public sealed class RetailPanelUiController : IDisposable
private void OnWindowMoved(RetailWindowHandle window)
{
if (_disposed || _synchronizingPlacement) return;
if (_disposed || _synchronizingGeometry) return;
foreach (PanelEntry entry in _byPanel.Values)
{
if (!entry.SharesMainPanelPlacement
if (!entry.SharesMainPanelGeometry
|| !ReferenceEquals(entry.Window, window))
continue;
_mainPanelPlacement = new PanelPlacement(window.Left, window.Top);
SynchronizeMainPanelSiblings(window);
CaptureAndSynchronizeMainPanelGeometry(window);
return;
}
}
private void CaptureMainPanelPlacement(
private void OnWindowResized(RetailWindowHandle window)
{
if (_disposed || _synchronizingGeometry) return;
foreach (PanelEntry entry in _byPanel.Values)
{
if (!entry.SharesMainPanelGeometry
|| !ReferenceEquals(entry.Window, window))
continue;
CaptureAndSynchronizeMainPanelGeometry(window);
return;
}
}
private void CaptureAndSynchronizeMainPanelGeometry(RetailWindowHandle source)
{
_mainPanelGeometry = new PanelGeometry(
source.Left,
source.Top,
source.Width,
source.Height);
SynchronizeMainPanelSiblings(source);
}
private void CaptureMainPanelGeometry(
PanelEntry entry,
bool synchronizeSiblings)
{
if (!entry.SharesMainPanelPlacement || entry.Window is not { } window)
if (!entry.SharesMainPanelGeometry || entry.Window is not { } window)
return;
_mainPanelPlacement = new PanelPlacement(window.Left, window.Top);
_mainPanelGeometry = new PanelGeometry(
window.Left,
window.Top,
window.Width,
window.Height);
if (synchronizeSiblings)
SynchronizeMainPanelSiblings(window);
}
private void PrepareMainPanelPlacement(PanelEntry entry)
private void PrepareMainPanelGeometry(PanelEntry entry)
{
if (!entry.SharesMainPanelPlacement || entry.Window is not { } window)
if (!entry.SharesMainPanelGeometry || entry.Window is not { } window)
return;
if (_mainPanelPlacement is not { } placement)
if (_mainPanelGeometry is not { } geometry)
{
_mainPanelPlacement = new PanelPlacement(window.Left, window.Top);
_mainPanelGeometry = new PanelGeometry(
window.Left,
window.Top,
window.Width,
window.Height);
SynchronizeMainPanelSiblings(window);
return;
}
MoveWindowTo(entry, placement);
ApplyWindowGeometry(entry, geometry);
}
private void SynchronizeMainPanelSiblings(RetailWindowHandle source)
{
if (_mainPanelPlacement is not { } placement) return;
if (_mainPanelGeometry is not { } geometry) return;
_synchronizingPlacement = true;
_synchronizingGeometry = true;
try
{
foreach (PanelEntry sibling in _byPanel.Values)
{
if (!sibling.SharesMainPanelPlacement
if (!sibling.SharesMainPanelGeometry
|| sibling.Window is not { } window
|| ReferenceEquals(window, source))
continue;
MoveWindowTo(sibling, placement);
ApplyWindowGeometry(sibling, geometry);
}
}
finally
{
_synchronizingPlacement = false;
_synchronizingGeometry = false;
}
}
private void MoveWindowTo(PanelEntry entry, PanelPlacement placement)
private static void ApplyWindowGeometry(PanelEntry entry, PanelGeometry geometry)
{
if (entry.Window is not { } window
|| (window.Left == placement.Left && window.Top == placement.Top))
return;
if (entry.Window is not { } window) return;
window.MoveTo(placement.Left, placement.Top);
if (window.Left != geometry.Left || window.Top != geometry.Top)
window.MoveTo(geometry.Left, geometry.Top);
if (window.Width != geometry.Width || window.Height != geometry.Height)
window.ResizeTo(geometry.Width, geometry.Height);
}
public void Dispose()
@ -283,14 +319,21 @@ public sealed class RetailPanelUiController : IDisposable
_disposed = true;
foreach (PanelEntry entry in _byPanel.Values)
if (entry.Window is { } window)
{
window.Moved -= OnWindowMoved;
window.Resized -= OnWindowResized;
}
}
private readonly record struct PanelPlacement(float Left, float Top);
private readonly record struct PanelGeometry(
float Left,
float Top,
float Width,
float Height);
private readonly record struct PanelEntry(
string WindowName,
bool RestorePrevious,
RetailWindowHandle? Window,
bool SharesMainPanelPlacement);
bool SharesMainPanelGeometry);
}