initial: inventory-list-display v0.1.0 — vertical list widget on lib-core.panel

5 public functions (create, register_action, unregister_action,
set_label_resolver, set_icon_resolver) plus 2 test backdoors
(_test_get_rows, _test_resolve_icon). Reads lib-core.inventory-list
contents each render frame; right-click rows open context-menu via
panel.show_context_menu. Default action-set is empty.
This commit is contained in:
Calic
2026-06-13 23:23:48 +02:00
commit 0921a03ede
4 changed files with 594 additions and 0 deletions

224
README.md Normal file
View File

@@ -0,0 +1,224 @@
# 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.0
**Lib-ID:** lib-core.inventory-list-display
**Requires:** lib-core.panel v0.1.0, lib-core.inventory-list v0.1.0, lib-core.composition v0.2.0
**Tags:** inventory, ui, panel, widget, list
## Topology
<!-- topology:start (auto-generated; do not edit) -->
```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
```
<!-- topology:end -->
## 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)
- Pretty item names (no `composition.template_of` accessor yet; override via `set_label_resolver`)
- Row selection highlight
- Multi-column layout
## 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
```