feat(ui): unify retained window mounts
This commit is contained in:
parent
6e9e10367f
commit
a8e9503d2e
20 changed files with 823 additions and 366 deletions
|
|
@ -40,14 +40,6 @@ public sealed class ChatWindowController
|
|||
private const uint SendId = 0x10000019u;
|
||||
private const uint MaxMinId = 0x1000046Fu;
|
||||
|
||||
// Scrollbar sprite ids from base layout 0x2100003E (confirmed in Task D).
|
||||
private const uint TrackSprite = 0x06004C5Fu;
|
||||
private const uint ThumbSprite = 0x06004C63u; // 3-slice middle tile
|
||||
private const uint ThumbTopSprite = 0x06004C60u; // 3-slice top cap
|
||||
private const uint ThumbBotSprite = 0x06004C66u; // 3-slice bottom cap
|
||||
private const uint UpSprite = 0x06004C6Cu; // up arrow (top button)
|
||||
private const uint DownSprite = 0x06004C69u; // down arrow (bottom button)
|
||||
|
||||
// Channel menu sprite ids (confirmed in chat element dump).
|
||||
private const uint MenuNormal = 0x06004D65u; // button face
|
||||
private const uint MenuPressed = 0x06004D66u; // button pressed
|
||||
|
|
@ -72,6 +64,12 @@ public sealed class ChatWindowController
|
|||
/// <summary>Channel-selector menu widget.</summary>
|
||||
public UiMenu Menu { get; private set; } = null!;
|
||||
|
||||
/// <summary>Resolved gmMainChatUI root metadata, including DAT size constraints.</summary>
|
||||
public ElementInfo DatWindowInfo { get; private set; } = null!;
|
||||
|
||||
public RetailWindowHandle? WindowHandle { get; private set; }
|
||||
public bool IsMaximized => _maximized;
|
||||
|
||||
// ── Private state ──────────────────────────────────────────────────────
|
||||
|
||||
private ChatChannelKind _activeChannel = ChatChannelKind.Say;
|
||||
|
|
@ -121,6 +119,7 @@ public sealed class ChatWindowController
|
|||
/// <summary>Window top before maximize.</summary>
|
||||
private float _normalTop;
|
||||
private bool _maximized;
|
||||
private UiButton? _maxMinButton;
|
||||
|
||||
// ── Factory ────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -181,7 +180,11 @@ public sealed class ChatWindowController
|
|||
// GameWindow adds this to the host, which re-parents it out of the synthetic wrapper,
|
||||
// orphaning the strays so they never draw.
|
||||
var window = layout.FindElement(RootId) ?? layout.Root;
|
||||
var c = new ChatWindowController { Root = window };
|
||||
var c = new ChatWindowController
|
||||
{
|
||||
Root = window,
|
||||
DatWindowInfo = FindInfo(rootInfo, RootId) ?? rootInfo,
|
||||
};
|
||||
|
||||
// Drop the dat top resize bar (0x1000000F): it is authored 800px wide and
|
||||
// juts out of the content-width window. The host wraps this content in the
|
||||
|
|
@ -234,13 +237,7 @@ public sealed class ChatWindowController
|
|||
bar.Height = bar.Height + oldTop;
|
||||
bar.ResetAnchorCapture();
|
||||
bar.Model = c.Transcript.Scroll;
|
||||
bar.SpriteResolve = resolve;
|
||||
bar.TrackSprite = TrackSprite;
|
||||
bar.ThumbSprite = ThumbSprite;
|
||||
bar.ThumbTopSprite = ThumbTopSprite;
|
||||
bar.ThumbBotSprite = ThumbBotSprite;
|
||||
bar.UpSprite = UpSprite;
|
||||
bar.DownSprite = DownSprite;
|
||||
bar.SpriteResolve ??= resolve;
|
||||
c.Scrollbar = bar;
|
||||
}
|
||||
|
||||
|
|
@ -301,7 +298,7 @@ public sealed class ChatWindowController
|
|||
ReflowInputRow();
|
||||
}
|
||||
|
||||
// ── Max/min toggle — simplified gmMainChatUI::HandleMaximizeButton ──
|
||||
// ── Max/min toggle — gmMainChatUI::HandleMaximizeButton ──
|
||||
if (layout.FindElement(MaxMinId) is UiButton maxMinEl)
|
||||
{
|
||||
// The dat puts max/min and the scrollbar up-button at the SAME X (both
|
||||
|
|
@ -310,6 +307,7 @@ public sealed class ChatWindowController
|
|||
if (track is not null)
|
||||
maxMinEl.Left = track.Left - maxMinEl.Width;
|
||||
maxMinEl.ResetAnchorCapture();
|
||||
c._maxMinButton = maxMinEl;
|
||||
maxMinEl.OnClick = c.ToggleMaximize;
|
||||
}
|
||||
|
||||
|
|
@ -319,28 +317,76 @@ public sealed class ChatWindowController
|
|||
// ── Max/min implementation ─────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Toggle between the normal chat window height and an expanded 320px height.
|
||||
/// Simplified port of retail <c>gmMainChatUI::HandleMaximizeButton @0x4cddb0</c>:
|
||||
/// retail stores the pre-maximize height and restores it on a second click.
|
||||
/// The 320px expanded size is the approximate retail maximized chat height.
|
||||
/// Attach the typed outer-frame handle after the controller's imported content
|
||||
/// has been mounted. Maximize/restore must resize this frame, not the child root.
|
||||
/// </summary>
|
||||
public void AttachWindow(RetailWindowHandle handle)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(handle);
|
||||
if (!ReferenceEquals(handle.ContentRoot, Root))
|
||||
throw new ArgumentException("Chat handle content root does not match the bound layout.", nameof(handle));
|
||||
if (WindowHandle is not null && !ReferenceEquals(WindowHandle, handle))
|
||||
throw new InvalidOperationException("Chat controller is already attached to another window.");
|
||||
WindowHandle = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exact control flow from <c>gmMainChatUI::HandleMaximizeButton @0x004CCE50</c>:
|
||||
/// save/restore Y+height, expand by half the parent, choose up/down growth from
|
||||
/// available space, clamp through the mounted frame's DAT constraints, and set
|
||||
/// the imported max/min button state.
|
||||
/// </summary>
|
||||
private void ToggleMaximize()
|
||||
{
|
||||
if (!_maximized)
|
||||
if (WindowHandle is not { IsRegistered: true } handle)
|
||||
return;
|
||||
|
||||
UiElement frame = handle.OuterFrame;
|
||||
float parentHeight = frame.Parent?.Height ?? 0f;
|
||||
if (parentHeight <= 0f)
|
||||
return;
|
||||
|
||||
if (_maximized)
|
||||
{
|
||||
_normalHeight = Root.Height;
|
||||
_normalTop = Root.Top;
|
||||
// Expand upward: move the top edge up so the bottom stays anchored.
|
||||
Root.Top = MathF.Max(0f, Root.Top + Root.Height - 320f);
|
||||
Root.Height = 320f;
|
||||
_maximized = true;
|
||||
float restoredHeight = Math.Clamp(_normalHeight, frame.MinHeight, frame.MaxHeight);
|
||||
float restoredTop = Math.Clamp(
|
||||
_normalTop,
|
||||
0f,
|
||||
MathF.Max(0f, parentHeight - frame.MinHeight));
|
||||
_maximized = false;
|
||||
_maxMinButton?.TrySetRetailState(RetailUiStateIds.Minimized);
|
||||
handle.ResizeTo(frame.Width, restoredHeight);
|
||||
handle.MoveTo(frame.Left, restoredTop);
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
_normalTop = frame.Top;
|
||||
_normalHeight = frame.Height;
|
||||
|
||||
float expansion = parentHeight / 2f;
|
||||
float targetTop = frame.Top;
|
||||
float targetHeight = frame.Height + expansion;
|
||||
if (frame.Top + targetHeight > parentHeight || frame.Top >= parentHeight / 2f)
|
||||
targetTop = MathF.Max(0f, frame.Top - expansion);
|
||||
|
||||
targetHeight = MathF.Min(targetHeight, parentHeight - targetTop);
|
||||
targetHeight = Math.Clamp(targetHeight, frame.MinHeight, frame.MaxHeight);
|
||||
|
||||
_maximized = true;
|
||||
_maxMinButton?.TrySetRetailState(RetailUiStateIds.Maximized);
|
||||
handle.ResizeTo(frame.Width, targetHeight);
|
||||
handle.MoveTo(frame.Left, targetTop);
|
||||
}
|
||||
|
||||
private static ElementInfo? FindInfo(ElementInfo node, uint id)
|
||||
{
|
||||
if (node.Id == id) return node;
|
||||
foreach (ElementInfo child in node.Children)
|
||||
{
|
||||
Root.Top = _normalTop;
|
||||
Root.Height = _normalHeight;
|
||||
_maximized = false;
|
||||
ElementInfo? found = FindInfo(child, id);
|
||||
if (found is not null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue