feat(ui): unify retained window mounts
This commit is contained in:
parent
6e9e10367f
commit
a8e9503d2e
20 changed files with 823 additions and 366 deletions
|
|
@ -2099,22 +2099,20 @@ public sealed class GameWindow : IDisposable
|
|||
healthText: () => (_vitalsVm!.HealthCurrent, _vitalsVm.HealthMax) is (uint c, uint m) ? $"{c}/{m}" : "",
|
||||
staminaText: () => (_vitalsVm!.StaminaCurrent, _vitalsVm.StaminaMax) is (uint c, uint m) ? $"{c}/{m}" : "",
|
||||
manaText: () => (_vitalsVm!.ManaCurrent, _vitalsVm.ManaMax) is (uint c, uint m) ? $"{c}/{m}" : "");
|
||||
// Top-level retail window: user-positioned (Anchors.None so the per-frame
|
||||
// anchor pass doesn't reset it), movable, and horizontally resizable like
|
||||
// retail. On a width change the dat edge-anchors reflow the pieces
|
||||
// (UIElement::UpdateForParentSizeChange @0x00462640): top/bottom edges +
|
||||
// the three bars stretch, corners stay 5px, the right edge/corners track
|
||||
// the right side. Vertical resize is off (the layout has no vertical stretch).
|
||||
var vitalsRoot = imported.Root;
|
||||
vitalsRoot.Left = 10; vitalsRoot.Top = 30;
|
||||
vitalsRoot.ClickThrough = false;
|
||||
vitalsRoot.Anchors = AcDream.App.UI.AnchorEdges.None;
|
||||
vitalsRoot.Draggable = true;
|
||||
vitalsRoot.Resizable = true;
|
||||
vitalsRoot.ResizeX = true;
|
||||
vitalsRoot.ResizeY = false;
|
||||
vitalsRoot.MinWidth = 40f;
|
||||
_uiHost.Root.AddChild(vitalsRoot);
|
||||
AcDream.App.UI.Layout.RetailWindowFrame.Mount(
|
||||
_uiHost.Root, vitalsRoot, ResolveChrome,
|
||||
new AcDream.App.UI.Layout.RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = AcDream.App.UI.WindowNames.Vitals,
|
||||
Chrome = AcDream.App.UI.Layout.RetailWindowChrome.Imported,
|
||||
Left = 10f,
|
||||
Top = 30f,
|
||||
ResizeX = true,
|
||||
ResizeY = false,
|
||||
MinWidth = 40f,
|
||||
ContentClickThrough = false,
|
||||
});
|
||||
Console.WriteLine("[D.2b] retail UI active — vitals window from LayoutDesc importer (0x2100006C).");
|
||||
}
|
||||
else
|
||||
|
|
@ -2150,16 +2148,20 @@ public sealed class GameWindow : IDisposable
|
|||
setUiLocked: SetRetailUiLocked,
|
||||
datFont: vitalsDatFont);
|
||||
|
||||
radarRoot.Left = System.Math.Max(0f, _window!.Size.X - radarRoot.Width - 10f);
|
||||
radarRoot.Top = 10f;
|
||||
radarRoot.ClickThrough = false;
|
||||
// User-positioned top-level window: Anchors.None prevents the
|
||||
// per-frame anchor pass from undoing drag-handle movement.
|
||||
radarRoot.Anchors = AcDream.App.UI.AnchorEdges.None;
|
||||
radarRoot.Resizable = false;
|
||||
radarRoot.ConstrainDragToParent = true;
|
||||
_uiHost.Root.AddChild(radarRoot);
|
||||
_uiHost.RegisterWindow(AcDream.App.UI.WindowNames.Radar, radarRoot);
|
||||
AcDream.App.UI.Layout.RetailWindowFrame.Mount(
|
||||
_uiHost.Root, radarRoot, ResolveChrome,
|
||||
new AcDream.App.UI.Layout.RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = AcDream.App.UI.WindowNames.Radar,
|
||||
Chrome = AcDream.App.UI.Layout.RetailWindowChrome.Imported,
|
||||
Left = System.Math.Max(0f, _window!.Size.X - radarRoot.Width - 10f),
|
||||
Top = 10f,
|
||||
Resizable = false,
|
||||
ResizeX = false,
|
||||
ResizeY = false,
|
||||
ConstrainDragToParent = true,
|
||||
ContentClickThrough = false,
|
||||
});
|
||||
ApplySavedRadarPosition();
|
||||
Console.WriteLine("[D.6] retail radar/compass from LayoutDesc 0x21000074.");
|
||||
}
|
||||
|
|
@ -2204,32 +2206,30 @@ public sealed class GameWindow : IDisposable
|
|||
// HEIGHT so the content's child anchors (input-bar-at-bottom, transcript-
|
||||
// fills) capture correct margins on first layout; resizing the frame reflows
|
||||
// them correctly from there.
|
||||
const int chatBorder = AcDream.App.UI.RetailChromeSprites.Border;
|
||||
var chatRoot = chatController.Root;
|
||||
float contentW = 490f, contentH = chatRoot.Height; // dat-authored height
|
||||
var chatFrame = new AcDream.App.UI.UiNineSlicePanel(ResolveChrome)
|
||||
{
|
||||
Left = 10, Top = 440,
|
||||
Width = contentW + 2 * chatBorder, Height = contentH + 2 * chatBorder,
|
||||
MinWidth = 200f, MinHeight = 90f,
|
||||
// Retail chat is translucent — fade the window's backgrounds/chrome
|
||||
// (text stays opaque). Configurable opacity is a later step; 0.75 reads
|
||||
// as see-through-but-readable. (retail SetDefaultOpacity ~0.5 / active 1.0)
|
||||
Opacity = 0.75f,
|
||||
};
|
||||
chatRoot.Left = chatBorder; chatRoot.Top = chatBorder;
|
||||
chatRoot.Width = contentW; chatRoot.Height = contentH;
|
||||
chatRoot.Anchors = AcDream.App.UI.AnchorEdges.Left | AcDream.App.UI.AnchorEdges.Top
|
||||
| AcDream.App.UI.AnchorEdges.Right | AcDream.App.UI.AnchorEdges.Bottom;
|
||||
var chatHandle = AcDream.App.UI.Layout.RetailWindowFrame.Mount(
|
||||
_uiHost.Root, chatRoot, ResolveChrome,
|
||||
new AcDream.App.UI.Layout.RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = AcDream.App.UI.WindowNames.Chat,
|
||||
Chrome = AcDream.App.UI.Layout.RetailWindowChrome.NineSlice,
|
||||
Left = 10f,
|
||||
Top = 440f,
|
||||
ContentWidth = 490f,
|
||||
ContentHeight = chatRoot.Height,
|
||||
RebaseContentLayout = true,
|
||||
DatConstraintSource = chatController.DatWindowInfo,
|
||||
MinWidth = 200f,
|
||||
ResizeX = true,
|
||||
ResizeY = true,
|
||||
Opacity = 0.75f,
|
||||
});
|
||||
chatController.AttachWindow(chatHandle);
|
||||
// The LayoutDesc root is 800px wide because it shares the display
|
||||
// coordinate space with auxiliary/floaty chat elements. The docked
|
||||
// gmMainChatUI content is the left 490px. Rebase its immediate children
|
||||
// after that intentional crop so subsequent frame resizing grows the
|
||||
// transcript/input across the whole mounted interior.
|
||||
chatRoot.RebaseChildLayoutBaselines();
|
||||
chatRoot.Draggable = false; chatRoot.Resizable = false;
|
||||
chatFrame.AddChild(chatRoot);
|
||||
_uiHost.Root.AddChild(chatFrame);
|
||||
// Tab / Enter enters "write mode" by focusing this input (retail's chat
|
||||
// activation); a focused input suppresses character movement (see the
|
||||
// WantsKeyboard gate in the movement poll).
|
||||
|
|
@ -2365,34 +2365,26 @@ public sealed class GameWindow : IDisposable
|
|||
// thickness on every side, giving an outer window of 310×132.
|
||||
const int toolbarBorder = AcDream.App.UI.RetailChromeSprites.Border;
|
||||
float toolbarContentW = 300f, toolbarContentH = toolbarRoot.Height;
|
||||
var toolbarFrame = new AcDream.App.UI.UiCollapsibleFrame(ResolveChrome)
|
||||
{
|
||||
Left = 10, Top = 300,
|
||||
Width = toolbarContentW + 2 * toolbarBorder,
|
||||
Height = toolbarContentH + 2 * toolbarBorder,
|
||||
// The toolbar is fully opaque (not translucent like the chat window).
|
||||
Opacity = 1.0f,
|
||||
};
|
||||
// Content is offset by the border so it sits inside the chrome.
|
||||
toolbarRoot.Left = toolbarBorder;
|
||||
toolbarRoot.Top = toolbarBorder;
|
||||
toolbarRoot.Width = toolbarContentW;
|
||||
toolbarRoot.Height = toolbarContentH;
|
||||
// Anchor content to Left|Top|Right only — drop Bottom so the dat content
|
||||
// keeps its full height when the frame collapses; row 2 hides via Visible,
|
||||
// not reflow. (Width is fixed so the horizontal anchors are inert but harmless.)
|
||||
toolbarRoot.Anchors = AcDream.App.UI.AnchorEdges.Left | AcDream.App.UI.AnchorEdges.Top
|
||||
| AcDream.App.UI.AnchorEdges.Right;
|
||||
// The frame is the draggable window; the content itself is not. ClickThrough so the
|
||||
// content panel never CLAIMS a hit — its behavioral children (slots, indicators) are
|
||||
// hit children-first, and its empty areas fall through to the frame (move). Critically,
|
||||
// when collapsed the row-2 band is hidden, so below the collapsed frame the content has
|
||||
// no visible/hit children and ClickThrough lets clicks fall through (no phantom
|
||||
// window-drag from where row 2 used to be). Same pattern as the chat content panel.
|
||||
toolbarRoot.ClickThrough = true;
|
||||
toolbarRoot.Draggable = false;
|
||||
toolbarRoot.Resizable = false;
|
||||
toolbarFrame.AddChild(toolbarRoot);
|
||||
var toolbarHandle = AcDream.App.UI.Layout.RetailWindowFrame.Mount(
|
||||
_uiHost.Root, toolbarRoot, ResolveChrome,
|
||||
new AcDream.App.UI.Layout.RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = AcDream.App.UI.WindowNames.Toolbar,
|
||||
Chrome = AcDream.App.UI.Layout.RetailWindowChrome.CollapsibleNineSlice,
|
||||
Left = 10f,
|
||||
Top = 300f,
|
||||
ContentWidth = toolbarContentW,
|
||||
ContentHeight = toolbarContentH,
|
||||
Resizable = false,
|
||||
ResizeX = false,
|
||||
ResizeY = true,
|
||||
ResizableEdges = AcDream.App.UI.ResizeEdges.Bottom,
|
||||
ContentAnchors = AcDream.App.UI.AnchorEdges.Left
|
||||
| AcDream.App.UI.AnchorEdges.Top
|
||||
| AcDream.App.UI.AnchorEdges.Right,
|
||||
ContentClickThrough = true,
|
||||
});
|
||||
var toolbarFrame = (AcDream.App.UI.UiCollapsibleFrame)toolbarHandle.OuterFrame;
|
||||
|
||||
// Collapse-to-one-row: the frame's bottom edge snaps between a one-row (row 2 hidden)
|
||||
// and two-row height. CollapsedHeight is computed from the layout (just above row 2),
|
||||
|
|
@ -2431,8 +2423,6 @@ public sealed class GameWindow : IDisposable
|
|||
Console.WriteLine($"[D.2b] toolbar collapse: collapsed={collapsedH:0} expanded={expandedH:0} (row2Top={minRow2Top:0}).");
|
||||
}
|
||||
|
||||
_uiHost.Root.AddChild(toolbarFrame);
|
||||
|
||||
Console.WriteLine("[D.5.1] retail toolbar window from LayoutDesc importer (0x21000016).");
|
||||
}
|
||||
else Console.WriteLine("[D.5.1] toolbar: LayoutDesc 0x21000016 not found.");
|
||||
|
|
@ -2462,16 +2452,18 @@ public sealed class GameWindow : IDisposable
|
|||
_uiHost.Root, characterLayout.Root, ResolveChrome,
|
||||
new AcDream.App.UI.Layout.RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = AcDream.App.UI.WindowNames.Character,
|
||||
Chrome = AcDream.App.UI.Layout.RetailWindowChrome.NineSlice,
|
||||
Left = 540f,
|
||||
Top = 18f,
|
||||
MaxHeight = 760f,
|
||||
ResizeX = false,
|
||||
ResizeY = true,
|
||||
ResizableEdges = AcDream.App.UI.ResizeEdges.Bottom,
|
||||
Visible = false,
|
||||
ContentAnchors = AcDream.App.UI.AnchorEdges.Left | AcDream.App.UI.AnchorEdges.Top
|
||||
| AcDream.App.UI.AnchorEdges.Bottom,
|
||||
ContentClickThrough = false,
|
||||
WindowName = AcDream.App.UI.WindowNames.Character,
|
||||
});
|
||||
|
||||
Console.WriteLine("[D.2b-C] retail character window from LayoutDesc importer (0x2100002E).");
|
||||
|
|
@ -2515,36 +2507,27 @@ public sealed class GameWindow : IDisposable
|
|||
// SAME UiNineSlicePanel the vitals/chat/toolbar windows use. The gmInventoryUI
|
||||
// LayoutDesc (0x21000023) is 300×362; the frame adds one border thickness on
|
||||
// every side. The frame is the draggable window; the dat content sits inside it.
|
||||
const int invBorder = AcDream.App.UI.RetailChromeSprites.Border;
|
||||
float invContentW = inventoryRoot.Width, invContentH = inventoryRoot.Height;
|
||||
var inventoryFrame = new AcDream.App.UI.UiNineSlicePanel(ResolveChrome)
|
||||
{
|
||||
Left = inventoryRoot.Left, Top = inventoryRoot.Top, // keep the dat window position
|
||||
Width = invContentW + 2 * invBorder,
|
||||
Height = invContentH + 2 * invBorder,
|
||||
Opacity = 1.0f,
|
||||
Visible = false, // F12 toggles it
|
||||
Anchors = AcDream.App.UI.AnchorEdges.None, // user-positioned
|
||||
Draggable = true,
|
||||
};
|
||||
// Content offset by the border so it sits inside the chrome.
|
||||
inventoryRoot.Left = invBorder; inventoryRoot.Top = invBorder;
|
||||
inventoryRoot.Width = invContentW; inventoryRoot.Height = invContentH;
|
||||
// Content fills the frame vertically (Left|Top|Bottom = fixed width + height tracks
|
||||
// the frame) so a vertical resize grows the inner content.
|
||||
inventoryRoot.Anchors = AcDream.App.UI.AnchorEdges.Left | AcDream.App.UI.AnchorEdges.Top
|
||||
| AcDream.App.UI.AnchorEdges.Bottom;
|
||||
inventoryRoot.Draggable = false;
|
||||
inventoryFrame.AddChild(inventoryRoot);
|
||||
|
||||
// Vertical-only resize: drag the BOTTOM edge to expand and see more of the pack.
|
||||
// ResizeX=false + ResizableEdges=Bottom blocks horizontal resize (user request).
|
||||
// MinHeight = the dat default (no shrink below it); MaxHeight = toward full screen.
|
||||
inventoryFrame.Resizable = true;
|
||||
inventoryFrame.ResizeX = false;
|
||||
inventoryFrame.ResizableEdges = AcDream.App.UI.ResizeEdges.Bottom;
|
||||
inventoryFrame.MinHeight = invContentH + 2 * invBorder;
|
||||
inventoryFrame.MaxHeight = 560f;
|
||||
var inventoryHandle = AcDream.App.UI.Layout.RetailWindowFrame.Mount(
|
||||
_uiHost.Root, inventoryRoot, ResolveChrome,
|
||||
new AcDream.App.UI.Layout.RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = AcDream.App.UI.WindowNames.Inventory,
|
||||
Chrome = AcDream.App.UI.Layout.RetailWindowChrome.NineSlice,
|
||||
Left = inventoryRoot.Left,
|
||||
Top = inventoryRoot.Top,
|
||||
ContentWidth = invContentW,
|
||||
ContentHeight = invContentH,
|
||||
Visible = false,
|
||||
ResizeX = false,
|
||||
ResizeY = true,
|
||||
ResizableEdges = AcDream.App.UI.ResizeEdges.Bottom,
|
||||
MaxHeight = 560f,
|
||||
ContentAnchors = AcDream.App.UI.AnchorEdges.Left
|
||||
| AcDream.App.UI.AnchorEdges.Top
|
||||
| AcDream.App.UI.AnchorEdges.Bottom,
|
||||
});
|
||||
var inventoryFrame = (AcDream.App.UI.UiNineSlicePanel)inventoryHandle.OuterFrame;
|
||||
|
||||
// Stretch the contents region with the window: the gm3DItemsUI sub-window + its grid
|
||||
// + scrollbar + the full-window backdrop grow vertically (Left|Top|Bottom); the
|
||||
|
|
@ -2568,9 +2551,6 @@ public sealed class GameWindow : IDisposable
|
|||
PinTopLeft(0x100001CDu); // paperdoll (fixed at top)
|
||||
PinTopLeft(0x100001CEu); // side-bag column (fixed at top)
|
||||
|
||||
_uiHost.Root.AddChild(inventoryFrame);
|
||||
_uiHost.RegisterWindow(AcDream.App.UI.WindowNames.Inventory, inventoryFrame);
|
||||
|
||||
// Phase D.2b-B — populate the inventory from ClientObjectTable: the
|
||||
// "Contents of Backpack" grid + the pack-selector strip + the burden meter +
|
||||
// captions. Analogue of gmInventoryUI/gmBackpackUI/gm3DItemsUI::PostInit.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue