docs(D.5.1): toolbar phase-1 design spec
First D.5 sub-phase: ship gmToolbarUI as the first data-driven game panel (18 slots from LayoutDesc 0x21000016, populated from the persisted PlayerDescription shortcuts, real composited icons, click-to-use). Minimal scope; faithful CPU icon pre-composite (IconData::RenderIcons port). Five bounded units: UiItemSlot, UiItemList, IconComposer, CreateObject IconId extension, ToolbarController. Roadmap registration of D.5.1 is plan step 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a5c5126e8d
commit
0b5e849325
1 changed files with 272 additions and 0 deletions
272
docs/superpowers/specs/2026-06-16-d2b-toolbar-phase1-design.md
Normal file
272
docs/superpowers/specs/2026-06-16-d2b-toolbar-phase1-design.md
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
# D.5.1 — Toolbar (action bar) — Phase 1 design
|
||||||
|
|
||||||
|
**Date:** 2026-06-16
|
||||||
|
**Status:** design approved (brainstorm), spec under review → writing-plans next
|
||||||
|
**Phase:** D.5.1 — first sub-phase of D.5 "Core panels" (D.2b retail-look track). NEW
|
||||||
|
sub-phase; roadmap registration is plan step 0 (roadmap discipline rule 4).
|
||||||
|
**Builds on:** the shipped D.2b widget toolkit (`b7f7e2b`→`89626cd`) — generic
|
||||||
|
Type-registered widgets built by `DatWidgetFactory`, assembled by `LayoutImporter`,
|
||||||
|
bound by thin `gm*UI::PostInit`-style controllers. See
|
||||||
|
[`claude-memory/project_d2b_retail_ui.md`](../../../claude-memory/project_d2b_retail_ui.md).
|
||||||
|
|
||||||
|
**Research evidence base (the anchors live here — this spec cites, does not re-derive):**
|
||||||
|
- [`docs/research/2026-06-16-ui-panels-synthesis.md`](../../research/2026-06-16-ui-panels-synthesis.md) — the build plan + consolidated widget list + cross-panel wire table
|
||||||
|
- [`docs/research/2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md`](../../research/2026-06-16-ui-item-slot-icon-dragdrop-spine-deep-dive.md) — `UIElement_UIItem`/`UIElement_ItemList` port spec, the icon composite, drag-drop spine
|
||||||
|
- [`docs/research/2026-06-16-action-bar-toolbar-deep-dive.md`](../../research/2026-06-16-action-bar-toolbar-deep-dive.md) — `gmToolbarUI` shortcut model + wire + element map
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Goal
|
||||||
|
|
||||||
|
Ship the **action bar (`gmToolbarUI`)** as the first data-driven *game* panel (vitals
|
||||||
|
and chat were HUD). 18 shortcut slots built from `LayoutDesc 0x21000016` via the existing
|
||||||
|
`LayoutImporter`, populated from the persisted `PlayerDescription` shortcut block, each
|
||||||
|
pinned item rendering its **real composited icon**, with **click-to-use**. Gated
|
||||||
|
`ACDREAM_RETAIL_UI=1`, whole-window-drag.
|
||||||
|
|
||||||
|
The point of doing the toolbar first is that it is the **thinnest end-to-end slice that
|
||||||
|
exercises the entire shared item spine** — the `UiItemSlot` widget, the icon composite
|
||||||
|
pipeline, the `UiItemList` widget, a find-by-id controller, and the `CreateObject` icon
|
||||||
|
extension — on the simplest of the three panels (no nested sub-windows, no 3D viewport,
|
||||||
|
no multi-column grid). Everything built here is reused verbatim by the inventory and
|
||||||
|
paperdoll phases.
|
||||||
|
|
||||||
|
## 2. Scope
|
||||||
|
|
||||||
|
**In scope (Phase 1):**
|
||||||
|
- `UiItemSlot` widget (port of `UIElement_UIItem`, class `0x10000032`) — empty-slot + icon render.
|
||||||
|
- `UiItemList` widget (port of `UIElement_ItemList`, class `0x10000031`) — single-cell instances.
|
||||||
|
- Icon composite pipeline (faithful CPU pre-composite — Approach A, §4.3).
|
||||||
|
- `CreateObject.TryParse` extension to capture `IconId` onto `ItemInstance`.
|
||||||
|
- `ToolbarController` — find-by-id bind, populate-from-shortcuts, deferred re-bind, click-to-use.
|
||||||
|
- Toolbar window mounted under `ACDREAM_RETAIL_UI=1`, whole-window-drag.
|
||||||
|
|
||||||
|
**Out of scope (later D.5 sub-phases):**
|
||||||
|
- Drag/reorder within the bar; drag-to-add from inventory (needs inventory as a drag source).
|
||||||
|
- The `AddShortCut`/`RemoveShortCut` mutate wire (`0x019C`/`0x019D`) — builders already exist; wiring them is deferred to the drag phase.
|
||||||
|
- The hidden selected-object Health/Mana meters (`0x100001A1`/`A2`) + the stack-split slider (`0x100001A4`) — stay `SetVisible(0)`, matching `gmToolbarUI::PostInit`.
|
||||||
|
- Spell shortcuts (`ItemList_InsertSpellShortcut`, `CM_Magic` path).
|
||||||
|
- Faithful window manager (Dragbar/Resizebar drag-resize) — uses the accepted IA-12 whole-window-drag approximation.
|
||||||
|
- Inventory and paperdoll panels.
|
||||||
|
|
||||||
|
## 3. Retail anchors (the load-bearing facts, verified)
|
||||||
|
|
||||||
|
All confirmed against the named decomp during the research phase and re-verified for this
|
||||||
|
spec. Lines are `acclient_2013_pseudo_c.txt`.
|
||||||
|
|
||||||
|
- **Window:** `gmToolbarUI` element class `0x10000007` → `LayoutDesc 0x21000016` (300×122).
|
||||||
|
`gmToolbarUI::Register` (decomp 196897), `GetUIElementType`→`0x10000007` (196707).
|
||||||
|
- **18 slots, two rows of 9:** element ids `0x100001A7-AF` (top) + `0x100006B7-BF` (bottom),
|
||||||
|
wired in `gmToolbarUI::InitShortcutArray` (decomp 197051); each is a `DynamicCast(0x10000031)`
|
||||||
|
= `UIElement_ItemList`, pushed into `m_shortcutSlots` in slot-index order.
|
||||||
|
- **Slot content:** each slot list holds one `UIElement_UIItem` (item-cell, class
|
||||||
|
`0x10000032`). The cell's bound weenie guid is `UIElement_UIItem::itemID` (offset `+0x5FC`),
|
||||||
|
read in `UIItem_Update` (decomp 230230: `uint32_t itemID = this->itemID; … GetWeenieObject(itemID)`).
|
||||||
|
- **Persisted model:** `ShortCutManager::shortCuts_[18]` (`acclient.h:36492`); the struct is
|
||||||
|
`ShortCutData { int index_; uint objectID_; uint spellID_; }` (`acclient.h:36484`). Delivered
|
||||||
|
at login in the `PlayerDescription` `SHORTCUT` block (`CharacterOptionDataFlag.SHORTCUT 0x1`).
|
||||||
|
acdream already parses it → `PlayerDescriptionParser.cs:345-356` → `Parsed.Shortcuts`
|
||||||
|
(`ShortcutEntry{Index, ObjectGuid, SpellId, Layer}`).
|
||||||
|
- **Populate at login:** `gmToolbarUI::UpdateFromPlayerDesc` (decomp 198838) — `FlushShortcuts`
|
||||||
|
then for i in 0..0x12 read `shortCuts_[i]->objectID_` and `AddShortcut(this, objId, i, send=0)`.
|
||||||
|
- **Deferred bind:** `UIElement_UIItem::SetDelayedShortcutNum` / `AddShortcut` (decomp 196867)
|
||||||
|
re-binds a slot whose weenie hasn't loaded yet once `CreateObject` for that guid arrives.
|
||||||
|
- **Activation (click-to-use):** `gmToolbarUI::UseShortcut` (decomp 196395) → `ItemHolder::UseObject`
|
||||||
|
(decomp 402923, 0.2s throttle `m_timeLastUsed + 0.2`) → ordinary use-item dispatch (NOT a
|
||||||
|
shortcut-specific wire message). acdream's use-item path = `InteractRequests.BuildUse` (`0x0036`).
|
||||||
|
- **Icon composite:** `UIElement_UIItem::UIItem_SetIcon` (230143) → `ACCWeenieObject::GetIconData`
|
||||||
|
(408224) → `IconData::RenderIcons` (407524). Five layers, bottom→top: item-type default
|
||||||
|
underlay `DBObj::GetByEnum(0x10000004, lsb(itemType)+1)`; custom underlay `_iconUnderlayID`;
|
||||||
|
base `_iconID`; custom overlay `_iconOverlayID` + `SurfaceWindow::ReplaceColor` tint; effect
|
||||||
|
overlay `DBObj::GetByEnum(0x10000005, lsb(effects)+1)`. **Every layer is DBObj type `0xc`
|
||||||
|
= RenderSurface, id range `0x06000000-0x07FFFFFF`** — decoded DIRECTLY via
|
||||||
|
`TextureCache.GetOrUploadRenderSurface` (the D.2b RenderSurface-vs-Surface gotcha: feeding
|
||||||
|
a `0x06` id to `GetOrUpload` returns 1×1 magenta). Icon is NOT appraise-gated (no appraise
|
||||||
|
branch in the icon path; appraise gates `UpdateTooltip` only).
|
||||||
|
- **acdream gap:** `CreateObject.TryParse` currently DISCARDS `IconId` (`CreateObject.cs:516`:
|
||||||
|
`_ = ReadPackedDwordOfKnownType(..., IconTypePrefix)`). `ItemInstance` already has the
|
||||||
|
`IconId`/`IconUnderlayId`/`IconOverlayId`/`StackSize`/`ContainerId` fields.
|
||||||
|
|
||||||
|
## 4. Architecture & components
|
||||||
|
|
||||||
|
Five new/extended units, each with one purpose and a defined interface. The pattern
|
||||||
|
mirrors the shipped vitals/chat re-drive exactly: dat `LayoutDesc` → `LayoutImporter` →
|
||||||
|
`DatWidgetFactory` builds widgets generically → a thin controller binds by id.
|
||||||
|
|
||||||
|
### 4.1 `UiItemSlot` (new behavioral widget) — port of `UIElement_UIItem` (`0x10000032`)
|
||||||
|
|
||||||
|
- **Location:** `src/AcDream.App/UI/UiItemSlot.cs`.
|
||||||
|
- **Registration:** `DatWidgetFactory` dispatches it on the resolved element **class id**
|
||||||
|
`0x10000032`. NOTE: the shipped factory keys off the small *numeric* Types (1–0x12); the
|
||||||
|
item-slot/item-list are `UIElement` subclasses identified by a high class id, so the plan
|
||||||
|
must add a class-id dispatch branch (the class id is already surfaced — `ElementReader.Merge`
|
||||||
|
resolves it through the `BaseElement` chain, and `UIElement_UIItem` derives from
|
||||||
|
`UIElement_Field`/Type 3, so do NOT register numeric Type 3 — that stays chrome `UiDatElement`,
|
||||||
|
per the shipped toolkit's deliberate Type-3 rule). Behavioral **leaf** — overrides
|
||||||
|
`ConsumesDatChildren => true` so the importer does NOT build its dat sub-elements (it
|
||||||
|
reproduces them procedurally).
|
||||||
|
- **State:** `uint ItemId` (the bound weenie guid, retail `+0x5FC`). Phase 1 needs only this.
|
||||||
|
Quantity / selection / drag-accept / ghost / open-container overlay states are *structurally
|
||||||
|
reserved* (documented as later-phase hooks) but inert.
|
||||||
|
- **Render:** if `ItemId == 0` → draw the empty-slot sprite (the dat state `ItemSlot_Empty`
|
||||||
|
→ `0x060074CF`, read from the element's states like every other `UiDatElement` sprite). Else
|
||||||
|
→ draw the composited icon (§4.3) into the 32×32 cell. Phase 1 draws no quantity text / no
|
||||||
|
overlays.
|
||||||
|
- **Depends on:** the icon pipeline (§4.3), `UiRenderContext.DrawSprite`.
|
||||||
|
|
||||||
|
### 4.2 `UiItemList` (new behavioral widget) — port of `UIElement_ItemList` (`0x10000031`)
|
||||||
|
|
||||||
|
- **Location:** `src/AcDream.App/UI/UiItemList.cs`.
|
||||||
|
- **Registration:** `DatWidgetFactory` keyed off class id `0x10000031`. Behavioral leaf
|
||||||
|
(`ConsumesDatChildren => true`) — manages its `UiItemSlot` children procedurally.
|
||||||
|
- **Phase-1 API subset:** `AddItem(UiItemSlot)` / `Flush()` / `GetNumUIItems()` /
|
||||||
|
`GetItem(int)`. The toolbar uses 18 **single-cell** instances (one `UiItemSlot` each), so
|
||||||
|
the N-cell grid layout (column wrap, cell pitch) is NOT needed yet — deferred to the
|
||||||
|
inventory phase. A single-cell list just hosts at most one slot.
|
||||||
|
- **Depends on:** `UiItemSlot`.
|
||||||
|
|
||||||
|
### 4.3 Icon pipeline (Approach A — faithful CPU pre-composite)
|
||||||
|
|
||||||
|
- **Location:** `src/AcDream.App/UI/IconComposer.cs` (App layer — it touches GL texture
|
||||||
|
upload). Pure-decode helpers may live alongside `TextureCache`.
|
||||||
|
- **Behaviour:** port `IconData::RenderIcons` (407524). For a given item's icon ids, build a
|
||||||
|
single 32×32 BGRA composite on the CPU by alpha-compositing the layers bottom→top
|
||||||
|
(§3 list), apply the `ReplaceColor` palette tint to the custom-overlay layer, then upload
|
||||||
|
the result once as a GL texture and **cache it keyed by the icon-id tuple** (so identical
|
||||||
|
items share one composite). The slot draws one sprite.
|
||||||
|
- **Layer decode:** each layer id is a `0x06` RenderSurface decoded DIRECTLY (Portal/HighRes
|
||||||
|
`TryGet<RenderSurface>` → `SurfaceDecoder.DecodeRenderSurface(palette:null)`), the same path
|
||||||
|
`TextureCache.GetOrUploadRenderSurface` already uses — but composited on the CPU rather than
|
||||||
|
drawn as separate sprites.
|
||||||
|
- **Enum-mapper layers:** the type-default underlay (`GetByEnum(0x10000004, …)`) and effect
|
||||||
|
overlay (`GetByEnum(0x10000005, …)`) require reading the two DBObj enum-mapper tables. These
|
||||||
|
are bounded lookups (index → RenderSurface id); port them as part of this unit. If a mapper
|
||||||
|
proves more involved than the research suggests, the base + custom underlay/overlay layers
|
||||||
|
still composite correctly and the enum layers can land as a tight follow-up within the phase
|
||||||
|
(documented, not silently dropped).
|
||||||
|
- **Why pre-composite, not stacked draws:** the custom-overlay `ReplaceColor` tint is a
|
||||||
|
per-pixel palette operation, not a simple alpha-blend — it cannot be reproduced by a tinted
|
||||||
|
`DrawSprite`. CPU compositing is therefore the faithful path, and it's the shared spine for
|
||||||
|
all three panels, so it's built correctly once.
|
||||||
|
- **Depends on:** `DatCollection` (RenderSurface decode), GL texture upload.
|
||||||
|
|
||||||
|
### 4.4 `CreateObject` icon extension + `ItemInstance`
|
||||||
|
|
||||||
|
- **Location:** `src/AcDream.Core.Net/Messages/CreateObject.cs`, `src/AcDream.Core/Items/ItemInstance.cs`.
|
||||||
|
- **Change:** in `CreateObject.TryParse`, capture the `IconId` (currently discarded at
|
||||||
|
`CreateObject.cs:516`) — and the underlay/overlay/effect ids if present in the same block —
|
||||||
|
onto the parsed object so `ItemRepository` stores them on `ItemInstance` (fields already exist).
|
||||||
|
- **Step 0 verification:** confirm against **ACE source** (`WorldObject.SerializeCreateObject`
|
||||||
|
/ the weenie property serialization) that a *contained* pack item's `CreateObject` actually
|
||||||
|
carries `IconId` (synthesis risk #3 — LIKELY, not yet byte-traced). Reading ACE is sufficient;
|
||||||
|
no live capture needed. If ACE only sends `IconId` for world-visible objects and relies on
|
||||||
|
`PlayerDescription` for pack items, fall back to the PD inventory block as the icon source —
|
||||||
|
this is a branch the plan must resolve before the icon pipeline is wired.
|
||||||
|
|
||||||
|
### 4.5 `ToolbarController` (new) — the `gmToolbarUI::PostInit` analogue
|
||||||
|
|
||||||
|
- **Location:** `src/AcDream.App/UI/ToolbarController.cs` (alongside `VitalsController`,
|
||||||
|
`ChatWindowController`).
|
||||||
|
- **Bind:** `Bind(LayoutDesc 0x21000016, …)` — find the 18 slot `UiItemList`s by id
|
||||||
|
(`0x100001A7-AF` + `0x100006B7-BF`) into an ordered `_slots[18]`. Force the 2 meters
|
||||||
|
(`0x100001A1`/`A2`) + slider (`0x100001A4`) hidden (matches `gmToolbarUI::PostInit`).
|
||||||
|
- **Populate (port `UpdateFromPlayerDesc`):** on the `PlayerDescription` arriving, `Flush` all
|
||||||
|
slots, then for each `Parsed.Shortcuts` entry resolve `ObjectGuid` → `ItemRepository` item →
|
||||||
|
set `_slots[Index]`'s cell `ItemId`. The cell renders the composited icon from the item's
|
||||||
|
`IconId`.
|
||||||
|
- **Deferred re-bind (port `SetDelayedShortcutNum`):** if a shortcut's guid is not yet in
|
||||||
|
`ItemRepository`, record it pending; when `ItemRepository` raises item-added for that guid,
|
||||||
|
bind the waiting slot. (Reuse `ItemRepository`'s existing item-change events.)
|
||||||
|
- **Click-to-use (port `UseShortcut`):** a slot click → controller → existing
|
||||||
|
`InteractRequests.BuildUse` (`0x0036`) for the cell's `ItemId`, gated by the 0.2s
|
||||||
|
use-throttle (`ItemHolder::UseObject`). No special shortcut wire.
|
||||||
|
- **Depends on:** `PlayerDescriptionParser.Parsed.Shortcuts`, `ItemRepository`, the slot
|
||||||
|
widgets, the command/interact send path.
|
||||||
|
|
||||||
|
### 4.6 Wiring & gating
|
||||||
|
|
||||||
|
- The toolbar window is built by `LayoutImporter` from `0x21000016` and mounted in `UiRoot`
|
||||||
|
under `ACDREAM_RETAIL_UI=1`, like vitals/chat. Always-on this phase. Root is `Anchors=None`
|
||||||
|
+ `Draggable` (whole-window-drag, IA-12 approximation) — NOT `Resizable` (faithful resize is
|
||||||
|
the deferred window manager).
|
||||||
|
- `GameWindow` wiring follows the existing vitals/chat drain pattern (one controller
|
||||||
|
constructed + bound; per-panel try/catch fault isolation already exists).
|
||||||
|
|
||||||
|
## 5. Data flow (login → visible toolbar)
|
||||||
|
|
||||||
|
1. Login → `PlayerDescription` arrives → `PlayerDescriptionParser` fills `Parsed.Shortcuts`.
|
||||||
|
2. In parallel, the player's pack items arrive as `CreateObject` messages → `ItemRepository`
|
||||||
|
stores `ItemInstance`s **including `IconId`** (the §4.4 extension).
|
||||||
|
3. `ToolbarController` (bound to the imported `0x21000016` window) runs its populate pass:
|
||||||
|
for each shortcut, resolve guid → item → set slot `ItemId`. Missing items → pending,
|
||||||
|
re-bound on item-added.
|
||||||
|
4. Each filled `UiItemSlot` asks `IconComposer` for the composited 32×32 texture (cached by
|
||||||
|
icon-id tuple) and draws it; empty slots draw `0x060074CF`.
|
||||||
|
5. Click a filled slot → use-item (`0x0036`) with throttle.
|
||||||
|
|
||||||
|
## 6. Testing strategy
|
||||||
|
|
||||||
|
Conformance tests in the layer matching each unit; dat-free fixtures where possible (mirror
|
||||||
|
the vitals `0x2100006C` golden-fixture approach).
|
||||||
|
|
||||||
|
- **`CreateObject` IconId** (`tests/AcDream.Core.Net.Tests`): a golden `CreateObject` byte
|
||||||
|
buffer parses with the expected `IconId` (and the previously-discarded fields).
|
||||||
|
- **`IconComposer`** (`tests/AcDream.App.Tests`): layer ORDER + presence given a synthetic
|
||||||
|
icon-id tuple (assert the composite requests layers bottom→top in the `RenderIcons` order;
|
||||||
|
assert the cache returns the same texture for the same tuple). The `ReplaceColor` tint math
|
||||||
|
gets a small unit test against a known palette index.
|
||||||
|
- **`UiItemSlot`** (`tests/AcDream.App.Tests`): `ItemId==0` selects the empty sprite;
|
||||||
|
`ItemId!=0` requests the composite. `ConsumesDatChildren==true`.
|
||||||
|
- **`UiItemList`**: `AddItem`/`Flush`/`GetNumUIItems`/`GetItem` over single-cell instances.
|
||||||
|
- **`ToolbarController`**: find-by-id binds 18 slots from a fixture tree; shortcut→item
|
||||||
|
resolution sets the right slot; an item arriving late triggers the deferred re-bind; a slot
|
||||||
|
click emits a use-item for the bound guid with the throttle respected. Meters/slider hidden.
|
||||||
|
- **Build + full suite green** before the visual gate.
|
||||||
|
|
||||||
|
## 7. Acceptance criteria
|
||||||
|
|
||||||
|
- `dotnet build` + `dotnet test` green.
|
||||||
|
- **Visual (the user's gate):** launch, log in `+Acdream` → an 18-slot action bar renders with
|
||||||
|
the correct dat chrome + empty-slot sprites; any persisted shortcuts show their **real
|
||||||
|
composited item icons**; clicking a pinned item **uses** it (observable server-side /
|
||||||
|
in-world). Whole-window drag works.
|
||||||
|
- Every AC-specific algorithm cites its named-decomp anchor in a comment (per the phase
|
||||||
|
checklist).
|
||||||
|
- Divergence rows added (§8); D.5.1 registered in the roadmap; memory updated if a durable
|
||||||
|
lesson emerges.
|
||||||
|
|
||||||
|
## 8. Divergence register + roadmap (bookkeeping)
|
||||||
|
|
||||||
|
- **Whole-window-drag** instead of faithful Dragbar-driven drag — already covered by the
|
||||||
|
existing **IA-12** row (reuse, no new row).
|
||||||
|
- **Icon enum-mapper layers**: if the type-default-underlay / effect-overlay layers land as a
|
||||||
|
follow-up rather than in the first commit, add a register row noting the temporarily-absent
|
||||||
|
layers (and delete it when they land). The base + custom underlay/overlay layers are faithful
|
||||||
|
from the first commit.
|
||||||
|
- **Roadmap:** register **D.5.1 — Toolbar** under D.5 "Core panels" as plan step 0 (avoids the
|
||||||
|
retroactive-registration deviation that the D.2b importer hit at roadmap line 428).
|
||||||
|
|
||||||
|
## 9. Open items carried from research (resolve in the plan, before the dependent step)
|
||||||
|
|
||||||
|
- **Step 0 — `CreateObject` IconId for contained items** (synthesis risk #3): read ACE source
|
||||||
|
to confirm pack-item `CreateObject` carries `IconId`; if not, use the PD inventory block.
|
||||||
|
Gates §4.3/§4.4.
|
||||||
|
- **Use-item opcode** (synthesis risk #4): `ItemHolder::UseObject` dispatch is confirmed; the
|
||||||
|
precise `0x0035` vs `0x0036` branch was not traced to the send. acdream has both in
|
||||||
|
`InteractRequests`; the toolbar uses single-item use (`0x0036`). Reconcile when wiring §4.5.
|
||||||
|
- The empty-slot baseline is itself a valid visual verification even if `+Acdream` has no
|
||||||
|
persisted shortcuts; pinning real items to verify icons may require the inventory phase
|
||||||
|
(drag-to-add) or a server-side pre-pin.
|
||||||
|
|
||||||
|
## 10. Component boundary summary (isolation check)
|
||||||
|
|
||||||
|
| Unit | One purpose | Interface | Depends on |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `UiItemSlot` | render one item-in-a-slot | `ItemId` setter; standard `UiElement` draw/hit | `IconComposer`, render context |
|
||||||
|
| `UiItemList` | hold N item slots | `AddItem`/`Flush`/`GetNumUIItems`/`GetItem` | `UiItemSlot` |
|
||||||
|
| `IconComposer` | icon-id tuple → composited 32×32 texture | `GetIcon(iconIds) → texture` (cached) | `DatCollection`, GL upload |
|
||||||
|
| `CreateObject`/`ItemInstance` | carry `IconId` from wire to model | existing parse + fields | — |
|
||||||
|
| `ToolbarController` | bind + populate + use | `Bind(layout, deps)` | shortcuts, `ItemRepository`, slots, send path |
|
||||||
|
|
||||||
|
Each can be understood and tested without reading the others' internals; the controller is
|
||||||
|
the only unit that knows about wire + model, keeping the widgets pure-presentation.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue