acdream/src/AcDream.App/UI
Erik bfba0ecf7f
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
fix(ui): interactive window moves must survive the per-frame anchor layout; lock the dragbar cursor
The dragbar port (e4c99f54) armed the press path but the combat/spell
bar still would not move in the live client, and the move cursor kept
showing with the UI locked. Two distinct causes, both reported from
the user's connected session:

1. Snap-back: the combat/spell bar mounts ANCHORED (Left|Bottom), and
   ApplyAnchor runs every frame before drawing children, recomputing
   Left/Top from margins captured at mount. The drag wrote Left/Top and
   the very next layout pass wrote them back - the window never visibly
   moved. (The unit harness runs no per-frame layout, which is why the
   original tests passed; unanchored windows like inventory never hit
   this.) Interactive window moves AND resizes now re-baseline the
   anchor capture on every applied change, and
   RetailWindowManager.MoveTo/ResizeTo get the same rebase so
   programmatic moves of anchored windows cannot be silently undone
   either. ResetAnchorCapture is exactly the documented tool for this
   ("make the current geometry the new layout baseline after an
   intentional change").

2. Locked cursor: the cursor the user saw was never the window-move
   feedback path (which is lock-gated) - it was the dragbar's own
   authored MD_Data_Cursor, revealed the moment the element began
   claiming the pointer. Authored cursor resolution now suppresses a
   WindowMoveHandle element's cursor while the UI is locked, matching
   the radar's existing locked behavior of hiding its authored drag
   affordance; movement itself was already gated.

Two inversion-sensitive regression tests: an anchored window dragged by
its handle must hold its position ACROSS an ApplyAnchor pass, and the
authored handle cursor must disappear when UiLocked flips on. App
Release suite 3,968 / 3 skips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 21:24:49 +02:00
..
Layout feat(ui): port retail UIElement_Dragbar so authored drag strips move their windows 2026-07-29 21:04:33 +02:00
Testing Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
AutoWieldController.cs fix(items): port retail useability and autowear rejection 2026-07-23 08:33:31 +02:00
ClientCommandController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
ControlsIni.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
CursorFeedbackController.cs fix(ui): interactive window moves must survive the per-frame anchor layout; lock the dragbar cursor 2026-07-29 21:24:49 +02:00
GameplayConfirmationController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
IconComposer.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
IItemListDragHandler.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
IRetainedPanelController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
IRetainedWindowStateController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
ItemDragPayload.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
ItemEquipRules.cs feat(ui): D.2b item interaction + retail cursors + live character sheet 2026-07-03 09:18:43 +02:00
ItemInteractionController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
IUiDatStateful.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
IUiGlobalTimeListener.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
IUiViewportRenderer.cs feat(render): Campaign V slice V6l commit 3 - the offscreen viewports draw on Vulkan 2026-07-28 18:05:24 +02:00
MarkupDocument.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
PortalWaitNoticeController.cs perf(streaming): reserve destination reveal capacity 2026-07-24 19:39:23 +02:00
README.md refactor(ui): extract retained runtime composition 2026-07-11 00:03:45 +02:00
RetailChromeSprites.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailCursorCatalog.cs fix(ui): port retail window control feedback 2026-07-17 09:40:08 +02:00
RetailItemConfirmationController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailPanelCatalog.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailSkillTrainingConfirmationController.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailUiRuntime.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailUiRuntimeLease.cs refactor(app): extract focused window lifetime 2026-07-22 19:43:52 +02:00
RetailWindowHandle.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailWindowLayoutPersistence.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetailWindowManager.cs fix(ui): interactive window moves must survive the per-frame anchor layout; lock the dragbar cursor 2026-07-29 21:24:49 +02:00
RetainedPanelControllerGroup.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
RetainedUiInputBinding.cs refactor(input): own pointer and callback lifetime 2026-07-22 11:59:33 +02:00
UiButton.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiButtonStateMachine.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiCatalogSlot.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiCollapsibleFrame.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiDatFont.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiDialogRoot.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiElement.cs feat(ui): port retail UIElement_Dragbar so authored drag strips move their windows 2026-07-29 21:04:33 +02:00
UiEvent.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiField.cs fix(ui): UiField wrapped-line cache coherent with the text at mouse-hit time 2026-07-29 19:38:52 +02:00
UiHost.cs feat(render): Vulkan campaign V11 step 2 — delete the OpenGL backend 2026-07-29 02:19:53 +02:00
UiItemList.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiItemSlot.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiLayoutPolicy.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiMenu.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiMeter.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiNineSlicePanel.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiPanel.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiRadar.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiRenderContext.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiRoot.cs fix(ui): interactive window moves must survive the per-frame anchor layout; lock the dragbar cursor 2026-07-29 21:24:49 +02:00
UiScrollable.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiScrollablePanel.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiScrollbar.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiShortcutDigitGraphics.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiText.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiTextureElement.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
UiViewport.cs feat(render): Campaign V slice V6l commit 3 - the offscreen viewports draw on Vulkan 2026-07-28 18:05:24 +02:00
WindowNames.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00
WorldDropDispatch.cs Revert "Campaign V slice V4a" - it lost world multisampling 2026-07-27 18:29:28 +02:00

AcDream.App.UI — Retail-style UI toolkit

This is acdream's retained-mode UI toolkit. It mirrors the behavior of the retail AC client (hit-testing, modal, capture, drag-drop, tooltip delay, focus routing, event type codes) without trying to byte-match the retail binary — because the retail widgets live in keystone.dll, which we don't decompile.

Research

All design decisions in this directory are grounded in the master synthesis + six deep-dive docs under docs/research/retail-ui/:

Document Topic
00-master-synthesis.md Cross-slice synthesis + C# port plan
01-architecture-and-init.md Process entry, window, main loop
02-class-hierarchy.md CUIManager / CUIListener / CFont / CSurface
03-rendering.md Font atlas, 2D quad batch, cursor
04-input-events.md WndProc → Device → widget event routing
05-panels.md Chat, attributes, spells, paperdoll, inventory
06-hud-and-assets.md Vital orbs, radar, compass + dat asset catalog

Files

  • UiEvent.cs — 24-byte event struct + retail-faithful type constants (0x01 click, 0x15 drag-begin, 0x3E drop, 0x201 WM_LBUTTONDOWN, …)
  • UiElement.cs — base widget with OnDraw / OnEvent / OnHitTest / OnTick virtuals, children list, ZOrder, focus/capture flags
  • UiPanel.csUiPanel (rect + optional bg/border), UiLabel, UiButton
  • UiRenderContext.cs — per-frame draw context with translate stack
  • UiRoot.cs — top-of-tree + "Device" responsibilities (mouse/keyboard state, focus, modal, capture, drag-drop, tooltip timer). Mirrors the retail DAT_00837ff4 Device object's vtable.
  • RetailWindowManager.cs / RetailWindowHandle.cs — named top-level window registry, raise policy, visibility/focus/capture lifecycle, geometry events, lock propagation, and controller teardown.
  • IRetainedPanelController.cs — disposable lifecycle contract for panel-specific show/hide and descendant-focus behavior.
  • RetainedPanelControllerGroup.cs — one typed lifecycle owner for windows whose imported subtree is bound by multiple focused controllers; teardown is idempotent and runs in reverse construction order.
  • RetailWindowLayoutPersistence.cs / IRetainedWindowStateController.cs — per-character/per-resolution bounds, visibility, collapse/maximize, legacy migration, and safe resolution-clamped restore.
  • RetailUiRuntime.cs — production composition owner. Focused binding records carry state/action delegates and DAT/GL resolvers in; the runtime imports and mounts every gameplay window and owns controller, cursor, persistence, probe, tick, draw, restore, and disposal paths.
  • Layout/RetailWindowFrame.cs — the single production/Studio mount contract for imported-chrome and shared-wrapper windows.
  • UiHost.cs — one-shot wrapper: owns the UiRoot, a TextRenderer, and a default BitmapFont. Provides WireMouse / WireKeyboard helpers for Silk.NET plumbing and removes those device subscriptions before disposing the window manager/controllers and renderer.

Integration pattern

// GameWindow.OnLoad
_uiHost = new UiHost(_gl!, shadersDir, _debugFont);
_uiHost.Root.WorldMouseFallThrough += (btn, x, y, flags) => HandleWorldClick(btn, x, y, flags);
_uiHost.Root.WorldKeyFallThrough   += (vk, lp) => HandleHotkey(vk);
foreach (var mouse in _input.Mice) _uiHost.WireMouse(mouse);
foreach (var kb in _input.Keyboards) _uiHost.WireKeyboard(kb);

// Add panels
var chat = new Panels.ChatWindow { Left = 10, Top = 400, Width = 500, Height = 250 };
_uiHost.Root.AddChild(chat);

// GameWindow.OnRender — after the 3D scene
_uiHost.Tick(deltaSeconds);
_uiHost.Draw(new Vector2(_window!.Size.X, _window.Size.Y));

What's scaffolded vs what still needs building

Shipped in the scaffold (this session)

  • UI tree + event routing + focus + modal + capture + drag-drop
  • Hit-testing (children-first, Z-order tie-break)
  • Tooltip timer (~1000ms)
  • Hover enter/leave, click vs right-click, scroll, keyboard
  • World fall-through so existing camera/player controls still work
  • Simple text/rect drawing through the existing BitmapFont + TextRenderer pipeline

To build next

  1. AcFont + FontCache — load Font DBObjs from portal.dat range 0x40000000..0x40000FFF, bake 256×256 glyph atlas from the referenced RenderSurface (0x06xxxxxx). See slice 03 §4.
  2. Dat sprite loader — decode RenderSurface dats as GL textures; add DrawSprite(uint datId, Rectangle dest, uint rgba) to UiRenderContext.
  3. CursorManager — OS cursor + dat-sourced custom cursors via slice 03 §7.
  4. Scissor clipping — for panels with scrollable interiors (chat, inventory grid). GL_SCISSOR_TEST wrapped in UiRenderContext.PushScissor / PopScissor.
  5. First concrete panel — ChatWindow since we have all 6 wire messages parsed already. See slice 05 §1.
  6. Vital orbs HUD once the server sends GameMessagePrivateUpdateVital. See slice 06 A.1.

Retail magic numbers the scaffold preserves

Because hand-ported panel code will copy the retail switch-case structure, we keep the magic constants:

// Event types
UiEventType.Click          == 0x01   // chunk_00470000.c ~11140
UiEventType.Tooltip        == 0x07   // chunk_00460000.c ~6253 (~1000ms delay)
UiEventType.DragBegin      == 0x15   // chunk_004A0000.c ~2707
UiEventType.DragEnter      == 0x21   // chunk_004A0000.c ~2714
UiEventType.DragOver       == 0x1C   // chunk_004A0000.c ~2723
UiEventType.DropReleased   == 0x3E   // chunk_004A0000.c ~2754
UiEventType.MouseDown      == 0x201  // WM_LBUTTONDOWN
UiEventType.MouseUp        == 0x202  // WM_LBUTTONUP

// Event IDs
// Widget event IDs live in the 0x10000000+ range (retail convention).
// UiRoot auto-assigns EventIds starting at 0x10000001.