Calic 901a4e0e55 chore: bump lib-core.panel dep to 0.2.0
Track lib-core.panel v0.2.0 (multi-active panels with layout-slots).
No behavioral change in this consumer — the v0.1.1 call sites remain
compatible via the panel framework's bw-compat shim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-15 01:46:42 +02:00

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_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.<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 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:

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) — 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:

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
Description
No description provided
Readme 64 KiB
Languages
Lua 100%