# lib-core.inventory-list-display Inventory vertical-list widget. Reads a composition list-container via `lib-core.inventory-list` and renders its contents as a panel widget managed by `lib-core.panel`. Provides icon + label + count rows with right-click context-menu support. **Version:** 0.1.1 **Lib-ID:** lib-core.inventory-list-display **Requires:** lib-core.panel v0.4.0, lib-core.inventory-list v0.1.0, lib-core.composition v0.4.0 **Tags:** inventory, ui, panel, widget, list ## Topology ```mermaid graph LR this["lib-core.inventory-list-display"] panel["lib-core.panel"] inv["lib-core.inventory-list"] comp["lib-core.composition"] this --> panel this --> inv this --> comp ``` ## Scope (v0.1.0) v0.1 ships a minimal vertical-list inventory widget: - `create(container_entity, opts)` — builds a panel-compatible widget_def - `register_action` / `unregister_action` — context-menu actions (right-click on row) - `set_label_resolver` / `set_icon_resolver` — override default row content - Render: icon (Atlas-UV via `draw_sprite_transform`) + label + "×1" count per row - Input: right-click row opens context-menu via `panel.show_context_menu` **Intentional non-goals (deferred):** - Row scrolling (wheel event silently ignored) - Stack-count display (`stack_mode="stack"` not supported in v0.1) - Row selection highlight - Multi-column layout ## Item-Template-Convention This widget reads optional player-facing properties from the item's composition-template. All are optional; missing values fall back to defaults. | Property | Type | Default | Used by | |---|---|---|---| | `name` | string | template-id-fallback / `"Item"` | default `label_resolver` — shown in the row | | `description` | string | (empty) | consumer-side actions (e.g. Inspect) — flavor text | | `weight` | number | 0 | reserved — read by `inventory-grid` (Phase F/G) | | `volume` | number | 0 | reserved — read by `inventory-grid` (Phase F/G) | | `composition.` | number (0-1) | (absent) | crafting recipes (Phase E); see `architecture/composition-model.md §4` for material catalog | | `sprite_atlas` | string | (empty) | default `icon_resolver` — atlas path for icon | | `sprite_uv` | `{x,y,w,h}` | (absent) | default `icon_resolver` — UV rect within atlas | | `stack_mode` | string | required by `inventory-list` | `"individual"` only in v0.x | | `position.x/y` | number | required for world placement | rendered by `lib-core.render` | **Skill-gated reveals on top of `description`** are handled by a separate `lib-core.hints` lib (designed, not yet implemented). When hints land, the base `description` stays untouched; hints attach predicate-gated extra reveals at examine/tooltip time. Modules without skill systems use `description` as the only flavor text. ## API ### `display.create(container_entity, opts)` **Syntax:** `display.create(container_entity: entity, opts: table|nil) -> widget_def` **Example:** ```lua local display = require("lib-core.inventory-list-display") local panel = require("lib-core.panel") local widget = display.create(backpack_entity, { title = "Backpack", widget_id = "backpack", pause_on_open = true, }) panel.register(widget.widget_id, widget) panel.bind_default_trigger("i", widget.widget_id) ``` Creates a widget_def bound to `container_entity`. `opts` keys: - `title` (string, default `"Inventory"`) — panel title bar text - `widget_id` (string, default `"inventory"`) — key for `panel.register` - `pause_on_open` (bool, default `false`) — passed to panel for `is_pausing()` - `icon_resolver` (function) — overrides default icon resolver at creation time - `label_resolver` (function) — overrides default label resolver at creation time Loud-error if `container_entity` is `nil` or has no container block with `kind="list"`. --- ### `display.register_action(widget_def, label, callback)` **Syntax:** `display.register_action(widget_def: table, label: string, callback: function) -> void` **Example:** ```lua display.register_action(widget, "Drop", function(item, ctx) inventory.remove(ctx.container, item) ctx.close_menu() end) ``` Registers a context-menu action shown on right-click of any row. `callback` receives `(item, context)` where `context = { container, close_menu, refresh }`. Loud-error on duplicate `label` or if `callback` is not a function. The default action-set is empty — all actions must be registered explicitly. --- ### `display.unregister_action(widget_def, label)` **Syntax:** `display.unregister_action(widget_def: table, label: string) -> void` Removes a context-menu action. Idempotent: no error if `label` was never registered. --- ### `display.set_label_resolver(widget_def, fn)` **Syntax:** `display.set_label_resolver(widget_def: table, fn: function) -> void` **Example:** ```lua display.set_label_resolver(widget, function(item) return item:get_property("display_name") or "Unknown" end) ``` Replaces the label resolver. `fn(item) -> string`. Called each render frame per row. Default returns `"Item"` for all items (no `composition.template_of` in v0.1). Loud-error if `fn` is not a function. --- ### `display.set_icon_resolver(widget_def, fn)` **Syntax:** `display.set_icon_resolver(widget_def: table, fn: function) -> void` **Example:** ```lua display.set_icon_resolver(widget, function(item) local atlas = item:get_property("sprite_atlas") if not atlas then return nil end return { atlas = atlas, uv = { x=0, y=0, w=16, h=16 }, } end) ``` Replaces the icon resolver. `fn(item) -> {atlas=string, uv={x,y,w,h}} or nil`. Default reads `sprite_atlas` and `sprite_uv.*` directly from item properties. Loud-error if `fn` is not a function. --- ### Test backdoors - `display._test_get_rows(widget_def)` — returns `inventory.contents(container)` directly - `display._test_resolve_icon(widget_def, item)` — invokes the current icon resolver Both are for test modules only and should not be called in production code. ## Row Layout ``` ctx.bounds.x | +--+----------------------------------+------+ | | Label ×1 | | |32| | | row_height (24px) +--+----------------------------------+------+ ^36px ^right-aligned (measure_text + padding) icon (32×32 or placeholder rect + "?") ``` - Icon column: 32 px wide, left-aligned at `ctx.bounds.x` - Label: at `ctx.bounds.x + 36`, `row_y + 4` - Count ("×1"): right-aligned using `measure_text`; `ctx.bounds.x + ctx.bounds.w - text_w - padding` - Row height: `ctx.theme.row_height` (default 24 px from panel theme) ## Item-Template-Convention Items displayed by this widget must satisfy the inventory-list contract: | Property | Required | Description | |----------|----------|-------------| | `stack_mode` | yes (`"individual"`) | inventory-list requirement | | `sprite_atlas` | recommended | path to atlas PNG (default icon resolver) | | `sprite_uv.x/y/w/h` | recommended | UV rect within atlas (default icon resolver) | | `position` | yes | world anchor (inventory-list requirement) | Tags: `{"renderable", "item"}` (renderable tag removed on add, restored on remove). If `sprite_atlas` is absent or empty, the default icon resolver returns `nil` and a placeholder rect + "?" glyph is drawn instead. ## Default Action-Set The default action-set is **empty**. No "Drop", "Use", or "Inspect" actions are registered automatically. Consuming modules must call `display.register_action` to populate the right-click context-menu. ## Glue-Pattern Minimal module setup: ```lua local display = require("lib-core.inventory-list-display") local panel = require("lib-core.panel") local inventory = require("lib-core.inventory-list") -- 1. Create widget (assumes backpack_entity is a list-container) local widget = display.create(backpack_entity, { title = "Backpack", widget_id = "backpack", pause_on_open = true, }) -- 2. Register actions display.register_action(widget, "Drop", function(item, ctx) inventory.remove(ctx.container, item) ctx.close_menu() end) -- 3. Override label if template names are available display.set_label_resolver(widget, function(item) return item:get_property("display_name") or "Item" end) -- 4. Register with panel + bind toggle key panel.register(widget.widget_id, widget) panel.bind_default_trigger("i", widget.widget_id) -- 5. Wire into update + render function M.update(dt) panel.update(dt) end function M.render() panel.render() end ```