From 31e51ee8b754c0337476a2a997a1fa461c8608ee Mon Sep 17 00:00:00 2001 From: Calic Date: Sun, 14 Jun 2026 19:43:49 +0200 Subject: [PATCH] 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) --- README.md | 79 ++++++++++++++++++++++++++++++++++++++++++---------- init.lua | 77 ++++++++++++++++++++++++++++++++++++++++---------- manifest.lib | 2 +- 3 files changed, 128 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index ea4bc12..5b9268c 100644 --- a/README.md +++ b/README.md @@ -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 = , -- v0.2 NEU + container = , -- 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) diff --git a/init.lua b/init.lua index 76c973f..adb0978 100644 --- a/init.lua +++ b/init.lua @@ -1,5 +1,5 @@ -- ===================================================================== --- lib-core.crafting-display v0.1.0 — Recipe Panel-Widget +-- lib-core.crafting-display v0.2.0 — Recipe Panel-Widget -- -- Sits on top of lib-core.panel and reads lib-core.crafting + -- lib-core.inventory-list + lib-core.composition. Provides a ready-to- @@ -10,7 +10,14 @@ -- registered via M.register_action. -- -- Public API: --- display.create(container_entity, opts) -> widget_def +-- display.create(arg, opts) -> widget_def +-- `arg` is either: +-- - a bare entity-handle (Form-1, v0.1 bw-compat) — wrapped as +-- a constant locale_factory internally; or +-- - a `function() -> locale` (Form-2, v0.2) — invoked per frame +-- to obtain the currently-resolved locale, which itself may be +-- a bare entity-handle or a `{sources={...}, sink=...}` table +-- as understood by `lib-core.crafting`. -- display.register_action(widget_def, label, callback) -- display.unregister_action(widget_def, label) -- display.set_icon_resolver(widget_def, fn) @@ -87,6 +94,10 @@ end -- --------------------------------------------------------------------- local function build_rows(widget) + local locale = widget._locale_factory() + if locale == nil then + return {} + end local ctx = widget._ctx_factory() if type(ctx) ~= "table" then ctx = {} end local out = {} @@ -99,7 +110,7 @@ local function build_rows(widget) recipe.id, tostring(known))) end elseif known == true then - local match = crafting.can_craft(recipe.id, widget._container, ctx) + local match = crafting.can_craft(recipe.id, locale, ctx) out[#out + 1] = { recipe = recipe, available = match.ok == true, @@ -113,9 +124,15 @@ end -- Public API: create -- --------------------------------------------------------------------- ---- M.create(container_entity, opts) -> widget_def ---- Creates a crafting-recipe widget bound to `container_entity` (used as ---- the input source for can_craft availability checks). +--- M.create(arg, opts) -> widget_def +--- Creates a crafting-recipe widget. `arg` is either: +--- - a bare entity-handle (Form-1, bw-compat) used as the input source +--- for can_craft availability; internally wrapped as a constant +--- locale_factory `function() return arg end`; or +--- - a `function() -> locale` (Form-2) — called per frame to obtain +--- the current locale. The returned locale may itself be a bare +--- handle (Form-1 sub-semantics) or a `{sources={...}, sink=...}` +--- table as accepted by `lib-core.crafting`. --- opts = { --- title = string, default "Crafting" --- widget_id = string, default "crafting" @@ -125,14 +142,23 @@ end --- label_resolver = function(recipe) -> string --- summary_resolver = function(recipe) -> string --- } ---- Loud-error if container_entity is nil. -function M.create(container_entity, opts) - if container_entity == nil then - error("crafting-display.create: container must not be nil", 2) - end +--- Loud-error if `arg` is nil. +function M.create(arg, opts) opts = opts or {} + if arg == nil then + error("crafting-display.create: first arg (locale or " + .. "container) must not be nil", 2) + end + local locale_factory + if type(arg) == "function" then + locale_factory = arg + else + -- Bw-compat: bare entity-handle (userdata in Sporel) wrapped + -- as constant factory. + locale_factory = function() return arg end + end local widget = { - _container = container_entity, + _locale_factory = locale_factory, _opts = opts, _actions = {}, _ctx_factory = opts.ctx_factory or default_ctx_factory, @@ -143,6 +169,8 @@ function M.create(container_entity, opts) widget_id = opts.widget_id or "crafting", pause_on_open = opts.pause_on_open == true, } + -- widget.render(ctx) + widget.handle_input(ctx, event) — panel + -- contract per ADR-0049 / panel/README.md function widget.render(ctx) M._render_widget(widget, ctx) end @@ -158,7 +186,12 @@ end --- M.register_action(widget, label, callback) --- Registers a context-menu action shown on right-click of any row. ---- callback(recipe_id, context) where context = { container, close_menu, refresh }. +--- callback(recipe_id, ctx_inner) where +--- ctx_inner = { locale, container, close_menu, refresh } +--- `locale` is the currently-resolved locale (Form-2 table or Form-1 +--- bare handle, depending on what the locale_factory returns). +--- `container` is a bw-compat alias pointing at `locale.sink` (Form-2) +--- or the handle itself (Form-1). --- Loud-error on duplicate label or non-function callback. function M.register_action(widget, label, callback) if type(label) ~= "string" or label == "" then @@ -262,6 +295,14 @@ function M._handle_input_widget(widget, _ctx, event) end function M._invoke_context_menu(widget, recipe_id, mx, my) + local locale = widget._locale_factory() + local sink_alias + if type(locale) == "table" then + sink_alias = locale.sink + else + -- Form-1: bare handle is both source + sink. + sink_alias = locale + end local entries = {} for _, a in ipairs(widget._actions) do local cb = a.callback -- capture for closure @@ -269,8 +310,10 @@ function M._invoke_context_menu(widget, recipe_id, mx, my) label = a.label, callback = function(menu_ctx) cb(recipe_id, { - container = widget._container, - close_menu = menu_ctx.close_menu, + locale = locale, + container = sink_alias, -- bw-compat alias + close_menu = (menu_ctx and menu_ctx.close_menu) + or function() end, refresh = function() end, -- v0.1: free (next frame re-reads) }) end, @@ -290,6 +333,10 @@ function M._test_get_rows(widget) return build_rows(widget) end +function M._test_get_locale(widget) + return widget._locale_factory() +end + function M._test_resolve_icon(widget, recipe) return widget._icon_resolver(recipe) end diff --git a/manifest.lib b/manifest.lib index 12deed0..caf6e5f 100644 --- a/manifest.lib +++ b/manifest.lib @@ -1 +1 @@ -{"id":"lib-core.crafting-display","version":"0.1.0","api_min":"0.1","deps":[{"id":"lib-core.crafting","version":"0.1.0"},{"id":"lib-core.panel","version":"0.1.1"},{"id":"lib-core.inventory-list","version":"0.1.0"},{"id":"lib-core.composition","version":"0.3.0"}]} +{"id":"lib-core.crafting-display","version":"0.2.0","api_min":"0.1","deps":[{"id":"lib-core.crafting","version":"0.2.0"},{"id":"lib-core.panel","version":"0.1.1"},{"id":"lib-core.inventory-list","version":"0.1.0"},{"id":"lib-core.composition","version":"0.3.0"}]}