Engine resolver does exact strcmp on dep versions, so consumer manifests must pin the new lib-core.panel 0.4.0 to keep load-time resolution green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.1.0, lib-core.inventory-list v0.1.0, lib-core.composition v0.2.0 Tags: inventory, ui, panel, widget, list
Topology
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_defregister_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.<material> |
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:
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 textwidget_id(string, default"inventory") — key forpanel.registerpause_on_open(bool, defaultfalse) — passed to panel foris_pausing()icon_resolver(function) — overrides default icon resolver at creation timelabel_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:
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:
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:
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)— returnsinventory.contents(container)directlydisplay._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:
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