feat: v0.2.0 locale_factory replaces single container

M.create now accepts either a function (locale_factory, called per
frame to resolve the current crafting locale) or a bare entity-handle
(wrapped internally as a constant factory for v0.1 bw-compat). The
factory's return value is forwarded to crafting.can_craft as the new
multi-source / single-sink locale, enabling dynamic topologies
(adjacent-container sets that change as the actor moves) without
recreating the widget.

Action callbacks now receive ctx_inner with both `locale` (the
currently-resolved locale, either the Form-2 table or the Form-1 bare
handle) and `container` as a bw-compat alias pointing at locale.sink
(Form-2) or the handle itself (Form-1). Existing v0.1 callers using
ctx.container keep working unchanged.

New backdoor _test_get_locale exposes the locale_factory's current
return value. README documents both Form-1 and Form-2 with examples
plus the updated callback contract.

Depends on lib-core.crafting v0.2.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Calic
2026-06-14 19:43:49 +02:00
parent b42b707a8e
commit 31e51ee8b7
3 changed files with 128 additions and 30 deletions

View File

@@ -7,9 +7,9 @@ per-row availability (full-colour when craftable, dim when blocked).
Right-clicking a row opens a context-menu populated from actions
registered via `register_action`.
**Version:** 0.1.0
**Version:** 0.2.0
**Lib-ID:** lib-core.crafting-display
**Requires:** lib-core.crafting v0.1.0, lib-core.panel v0.1.1, lib-core.inventory-list v0.1.0, lib-core.composition v0.3.0
**Requires:** lib-core.crafting v0.2.0, lib-core.panel v0.1.1, lib-core.inventory-list v0.1.0, lib-core.composition v0.3.0
**Tags:** crafting, ui, panel, widget, recipes
## Topology
@@ -86,7 +86,11 @@ For every frame, the widget rebuilds its row-list by walking
| Gate | Source | Effect on row |
|-----------------|------------------------------------------|----------------------------------------------|
| **Visibility** | `recipe.is_known(ctx)` | `false` → row is **hidden** entirely |
| **Availability**| `crafting.can_craft(id, container, ctx)` | `ok=false` → row visible but **dim colour** |
| **Availability**| `crafting.can_craft(id, locale, ctx)` | `ok=false` → row visible but **dim colour** |
The `locale` here is whatever the widget's locale-factory returns for
the current frame (v0.2: per-frame fresh; v0.1 bw-compat: constant
single-container).
`ctx` is built from `opts.ctx_factory()` (default `function() return {} end`).
Override `ctx_factory` to wire in actor-state, skill-level, faction-membership,
@@ -99,11 +103,21 @@ there propagate (consistent with rest of the lib stack).
## API
### `display.create(container_entity, opts)`
### `display.create(arg, opts) -> widget_def`
**Syntax:** `display.create(container_entity: entity, opts: table|nil) -> widget_def`
Creates a crafting widget. **`arg`** ist eines von:
**Example:**
- **Form 1 (Bw-Compat v0.1):** bare container-entity-handle. Intern
als constant locale_factory gewrappt (`function() return arg end`).
- **Form 2 (v0.2 NEU):** `function() -> locale` — wird pro Frame
aufgerufen. Locale ist entweder ein entity-handle (Form-1 sub-
semantik) oder eine Table `{sources={...}, sink=...}`. Forward-
compat zu dynamischen Topologien (player-bewegt-sich →
adjacent-Container-Set ändert sich, ohne widget-Recreate).
**Syntax:** `display.create(arg: entity | function() -> locale, opts: table|nil) -> widget_def`
**Example (Form 1, bw-compat):**
```lua
local display = require("lib-core.crafting-display")
local panel = require("lib-core.panel")
@@ -120,8 +134,17 @@ panel.register(widget.widget_id, widget)
panel.bind_default_trigger("c", widget.widget_id)
```
Creates a widget_def bound to `container_entity` (used as the input
source for `can_craft` availability checks). `opts` keys:
**Example (Form 2, locale-factory):**
```lua
local widget = display.create(function()
return {
sources = adjacent_containers(player),
sink = player_backpack,
}
end, { title = "Workbench" })
```
`opts` keys:
- `title` (string, default `"Crafting"`) — panel title bar text
- `widget_id` (string, default `"crafting"`) — key for `panel.register`
- `pause_on_open` (bool, default `false`) — passed to panel for `is_pausing()`
@@ -130,7 +153,28 @@ source for `can_craft` availability checks). `opts` keys:
- `label_resolver` (function) — overrides default label resolver at creation time
- `summary_resolver` (function) — overrides default summary resolver at creation time
Loud-error if `container_entity` is `nil`.
Loud-error if `arg` is `nil`.
---
### Action-Callback Signatur (v0.2.0 Update)
Callbacks erhalten `(recipe_id, ctx_inner)` wo `ctx_inner`:
```lua
{
locale = <currently-resolved locale>, -- v0.2 NEU
container = <sink (Form-2) or handle (Form-1)>, -- bw-compat alias
close_menu = function() ... end,
refresh = function() ... end,
}
```
Modul-Aktion ruft typisch `crafting.craft(recipe_id,
ctx_inner.locale, ctx)`. Existing v0.1 callers die
`ctx_inner.container` benutzen funktionieren weiter via Form-1
bw-compat-shim (sink-handle wird intern als single-container
behandelt).
---
@@ -141,14 +185,18 @@ Loud-error if `container_entity` is `nil`.
**Example:**
```lua
display.register_action(widget, "Craft", function(recipe_id, ctx)
crafting.craft(recipe_id, ctx.container, {})
-- v0.2: prefer ctx.locale; ctx.container is a bw-compat alias.
crafting.craft(recipe_id, ctx.locale, {})
ctx.close_menu()
end)
```
Registers a context-menu action shown on right-click of any row. `callback`
receives `(recipe_id, context)` where
`context = { container, close_menu, refresh }`.
receives `(recipe_id, ctx_inner)` where
`ctx_inner = { locale, container, close_menu, refresh }`.
`locale` carries the currently-resolved locale (table for Form-2, bare
handle for Form-1). `container` is a bw-compat alias pointing at
`locale.sink` (Form-2) or the handle itself (Form-1).
Loud-error on duplicate `label` or if `callback` is not a function.
The default action-set is empty — all actions must be registered explicitly.
@@ -222,11 +270,13 @@ suffixing `"×N"` when `count>1`.
- `display._test_get_rows(widget_def)` — returns the per-frame row list
(`{recipe, available}` pairs after is_known filtering)
- `display._test_get_locale(widget_def)` — invokes the configured
locale_factory and returns whatever it produces (v0.2)
- `display._test_resolve_icon(widget_def, recipe)` — invokes current icon resolver
- `display._test_resolve_label(widget_def, recipe)` — invokes current label resolver
- `display._test_resolve_summary(widget_def, recipe)` — invokes current summary resolver
All four are for test modules only and should not be called in
All five are for test modules only and should not be called in
production code.
## Glue-Pattern
@@ -258,7 +308,8 @@ local widget = display.create(workbench_entity, {
-- 3. Register actions
display.register_action(widget, "Craft", function(recipe_id, ctx)
crafting.craft(recipe_id, ctx.container, {})
-- v0.2: prefer ctx.locale (ctx.container is a bw-compat alias).
crafting.craft(recipe_id, ctx.locale, {})
ctx.close_menu()
end)