feat: v0.3.0 Persistent + Z-Tiers + Input-Block
Layer z_tiers (hud/normal/top), persistent windows, and input_block routing (none/self/all) on top of v0.2.0's multi-active + layout-slot model. Render iterates per-tier (hud->normal->top); input dispatch iterates reverse (top->hud, within-tier reverse-open-order); modal input_block='all' swallows misses. Persistent windows open at register-time and are exempt from arg-less close() (so ESC and default triggers cannot dismiss persistent toolbars/HUDs). Adds chromeless=true opt (suppresses panel-lib's own decoration so in-game HUD widgets like map-editor's toolbar/layers/palette can paint their own visual style over the full widget bounds). Adds public panel.point_in_any_panel(x, y) — module-side canvas-click gate that returns true if (x, y) falls within any open panel's bounds. Modules read input directly via engine.input.*; they can use this helper to skip canvas-paint when the click landed on a panel widget area.
This commit is contained in:
185
README.md
185
README.md
@@ -1,20 +1,24 @@
|
||||
# lib-core.panel
|
||||
|
||||
Generic overlay-panel framework. v0.2.0 supports multiple panels open at
|
||||
the same time with 11 named layout-slot templates + custom-fn hook for
|
||||
bespoke positioning. Handles input dispatch (mouse click + wheel,
|
||||
edge-detected with reverse-open-order hit-test), renders titled panel
|
||||
overlays, and supports a context-menu layer on top. Designed as the glue
|
||||
layer between game modules and the engine render/input surfaces.
|
||||
Generic overlay-panel framework. v0.3.0 layers **z-tiers** (hud / normal
|
||||
/ top), **persistent windows**, and **input-block routing** (none / self
|
||||
/ all) on top of v0.2.0's multi-active model with 11 named layout-slot
|
||||
templates + custom-fn hook. Render iterates per-tier (hud → normal →
|
||||
top); input dispatch iterates reverse (top → hud, within-tier reverse
|
||||
open-order); modal `input_block="all"` swallows misses.
|
||||
|
||||
The single-active model from v0.1.1 is preserved as a backward-
|
||||
compatibility shim — `panel.open(id)`, `panel.close()`, `panel.is_open()`
|
||||
without an id-arg keep their old semantics.
|
||||
without an id-arg keep their old semantics. v0.3.0 additionally makes
|
||||
`panel.close()` (no arg) a **no-op** when the focused window is
|
||||
`persistent=true` (so ESC-equivalents cannot dismiss persistent
|
||||
toolbars/HUDs); use `panel.close(id)` for explicit modder-controlled
|
||||
close.
|
||||
|
||||
**Version:** 0.2.0
|
||||
**Version:** 0.3.0
|
||||
**Lib-ID:** lib-core.panel
|
||||
**Requires:** engine.render.*, engine.input.*, lib-core.input (lazy, for default-trigger)
|
||||
**Tags:** panel, overlay, ui, input, context-menu
|
||||
**Tags:** panel, overlay, ui, input, context-menu, window-manager
|
||||
|
||||
## Topology
|
||||
|
||||
@@ -31,23 +35,36 @@ graph LR
|
||||
```
|
||||
<!-- topology:end -->
|
||||
|
||||
## Scope (v0.2.0)
|
||||
## Scope (v0.3.0)
|
||||
|
||||
v0.2.0 ships multi-active panels with layout-slot positioning,
|
||||
preserving full backward-compatibility with v0.1.1's single-active
|
||||
callers via shim semantics. The lib is the generic UI-Framework
|
||||
substrate — domain-free per ADR-0001/ADR-0049, consumed by Display-
|
||||
Libs (inventory-list-display, crafting-display, notify-display) and
|
||||
modules (vagrant-skeleton).
|
||||
v0.3.0 layers z-tiers + persistent + input-block on top of v0.2.0's
|
||||
multi-active model, fully backward-compatible with v0.2.0 and v0.1.1
|
||||
callers. The lib is the generic UI-Framework substrate — domain-free
|
||||
per ADR-0001/ADR-0049, consumed by Display-Libs (inventory-list-
|
||||
display, crafting-display, notify-display) and modules (vagrant-
|
||||
skeleton, map-editor).
|
||||
|
||||
### Supported (v0.2.0)
|
||||
### Supported (v0.3.0)
|
||||
|
||||
- Multi-active panels: multiple windows open simultaneously, each
|
||||
rendered + hit-tested independently.
|
||||
- 11 layout-slot templates (center, left, right, top, bottom, four
|
||||
corners, left-half, right-half) for common positioning.
|
||||
- Custom layout-fn hook `function(sw, sh) -> {x,y,w,h}` for bespoke
|
||||
positioning (HUD elements, status displays, custom tool UIs).
|
||||
- **Z-tiers**: render order hud → normal → top. Input dispatch reverse
|
||||
(top → hud, within-tier reverse open-order).
|
||||
- **Persistent windows**: `opts.persistent=true` auto-opens at register-
|
||||
time. Arg-less `panel.close()` becomes no-op when focused is
|
||||
persistent (so ESC/default-trigger cannot dismiss a persistent
|
||||
toolbar/HUD). Explicit `panel.close(id)` still closes persistent.
|
||||
- **Input-block routing**: `opts.input_block` = `"none"` (skip hit-test
|
||||
entirely — HUD passes clicks through), `"self"` (hit-test own bounds;
|
||||
miss falls through to next window), `"all"` (modal — hit-test self;
|
||||
miss is swallowed, never reaches game-layer). Defaults per tier:
|
||||
hud→none, normal→self, top→all.
|
||||
- **Public `panel.point_in_any_panel(x, y)`**: helper for module-side
|
||||
canvas-click logic — returns true if (x,y) falls within any open
|
||||
panel's bounds. Useful for modules that read input directly via
|
||||
`engine.input.*` and want to skip canvas-paint when the click landed
|
||||
inside a panel widget.
|
||||
- Multi-active panels: multiple windows open simultaneously (unchanged
|
||||
from v0.2.0).
|
||||
- 11 layout-slot templates + custom-fn hook (unchanged).
|
||||
- Bw-Compat-Shim: v0.1.1 `register(id, widget)` ohne opts works
|
||||
unchanged; `close()`/`is_open()` ohne arg map to focused = last-
|
||||
opened.
|
||||
@@ -55,13 +72,6 @@ modules (vagrant-skeleton).
|
||||
- Context-menu via `show_context_menu` (unchanged).
|
||||
- Default-trigger key-binding via `bind_default_trigger` (unchanged).
|
||||
|
||||
### Deferred (v0.3.0+)
|
||||
|
||||
- Z-order tiers (hud / normal / top) for layered rendering.
|
||||
- Persistent windows (always-open, exempt from ESC-close).
|
||||
- Input-block modes (none/self/all) for pass-through vs modal capture.
|
||||
- Map-editor-style multi-region UI as first-class persistent widgets.
|
||||
|
||||
### Deferred (v0.4.0+)
|
||||
|
||||
- Click-to-focus and explicit focus()/raise()/lower() API.
|
||||
@@ -83,7 +93,22 @@ modules (vagrant-skeleton).
|
||||
|
||||
**Syntax:** `panel.register(widget_id: string, widget_def: table, opts?: table) -> void`
|
||||
|
||||
`opts` is optional. Accepted keys: `layout` (slot-name string or `function(sw, sh) -> {x,y,w,h}`; defaults to `"center"`) and `z_tier` (`"normal"`; v0.2.0 only the normal tier is exposed). See the [Multi-Active + Layout-Slots](#v020--multi-active--layout-slots) section below for the full slot table.
|
||||
`opts` is optional. Accepted keys:
|
||||
- `layout` — slot-name string, custom `function(sw, sh) -> {x,y,w,h}`, or
|
||||
`nil` (defaults to `"center"`). See the [Layout-Slots](#layout-slots)
|
||||
table.
|
||||
- `z_tier` — `"hud"` | `"normal"` (default) | `"top"`. Controls render
|
||||
order (hud → normal → top) and hit-test order (top → normal → hud).
|
||||
- `persistent` — `bool` (default `false`). If true, the window is open
|
||||
from the moment of registration and is exempt from arg-less
|
||||
`panel.close()` (modder must close explicitly with `close(id)`).
|
||||
- `input_block` — `"none"` | `"self"` | `"all"`. Defaults per tier:
|
||||
hud→`"none"`, normal→`"self"`, top→`"all"`. See [Input-Block](#input-block-routing-v030).
|
||||
- `chromeless` — `bool` (default `false`). If true, panel-lib skips its
|
||||
own chrome (background, border, title-bar, close-X). The widget gets
|
||||
the FULL panel bounds as `ctx.bounds` (no title-bar inset) and must
|
||||
render its own background/border. Intended for persistent HUD widgets
|
||||
(toolbars, layer-pickers, palettes) that have their own visual style.
|
||||
|
||||
**Example:**
|
||||
```lua
|
||||
@@ -138,12 +163,39 @@ below for the migration recipe.
|
||||
|
||||
**Syntax:** `panel.close(widget_id?: string) -> void`
|
||||
|
||||
With `widget_id`: closes that specific window. Without arg (v0.1.1
|
||||
bw-compat): closes the focused (last-opened) window. Clears any open
|
||||
With `widget_id`: closes that specific window (DOES close persistent
|
||||
windows — explicit modder action). Without arg (v0.1.1 bw-compat):
|
||||
closes the focused (last-opened) window — **BUT** v0.3.0 makes this
|
||||
a no-op when the focused window is `persistent=true`. Clears any open
|
||||
context-menu when the last open window is closed.
|
||||
|
||||
---
|
||||
|
||||
### `panel.point_in_any_panel(x, y)`
|
||||
|
||||
**Syntax:** `panel.point_in_any_panel(x: number, y: number) -> bool`
|
||||
|
||||
**Example:**
|
||||
```lua
|
||||
-- In a module's M.update(dt):
|
||||
local mx, my = engine.input.get_mouse_pos()
|
||||
if engine.input.was_mouse_pressed(engine.input.MOUSE_LEFT) then
|
||||
if not panel.point_in_any_panel(mx, my) then
|
||||
-- click landed on canvas — paint!
|
||||
canvas.paint(mx, my)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Returns `true` if screen-coord `(x, y)` falls within the bounds of ANY
|
||||
currently-open panel window (regardless of z_tier or input_block).
|
||||
Intended for module-side canvas-click logic — modules read input
|
||||
directly via `engine.input.*` and can use this helper to skip
|
||||
canvas-paint when the click landed on a panel widget area. Returns
|
||||
`false` if no panels are open.
|
||||
|
||||
---
|
||||
|
||||
### `panel.toggle(widget_id)`
|
||||
|
||||
**Syntax:** `panel.toggle(widget_id: string) -> void`
|
||||
@@ -346,6 +398,73 @@ If you relied on the v0.1.1 "open(B) closes A" behavior, call
|
||||
window, or nil if not open.
|
||||
- `panel._test_get_screen_size()` — current screen size used by layouts.
|
||||
|
||||
## v0.3.0 — Z-Tiers, Persistent, Input-Block
|
||||
|
||||
### Z-Tiers
|
||||
|
||||
Three rendering tiers, drawn bottom-to-top:
|
||||
|
||||
| Tier | Render order | Default `input_block` | Use-case |
|
||||
|------|--------------|-----------------------|----------|
|
||||
| `"hud"` | first (background) | `"none"` | HP-bars, status displays, world-overlays |
|
||||
| `"normal"` | second | `"self"` | Toolbars, persistent panels, workbench windows |
|
||||
| `"top"` | third (foreground) | `"all"` | Modal dialogs, blocking confirmations |
|
||||
|
||||
Within a tier, windows render in `open_order` (later-opened = drawn
|
||||
later = on top of same-tier earlier-opened). Hit-test iterates reverse
|
||||
z-tier (top → normal → hud); within tier reverse-open-order
|
||||
(last-opened first). Unknown tier names raise a loud-error at
|
||||
`panel.register` (typos surface immediately).
|
||||
|
||||
### Persistent Windows
|
||||
|
||||
```lua
|
||||
panel.register("toolbar", widget, {
|
||||
z_tier = "normal",
|
||||
layout = "top",
|
||||
persistent = true, -- open at register-time
|
||||
input_block = "self",
|
||||
})
|
||||
```
|
||||
|
||||
`persistent=true` pushes the window onto `open_order` immediately and
|
||||
makes `panel.close()` (no arg) a **no-op** when the window is focused.
|
||||
Use this for in-game UI that must stay visible (toolbars, layer-pickers,
|
||||
palettes, HUDs). The modder can still close it explicitly with
|
||||
`panel.close(widget_id)`.
|
||||
|
||||
ESC-equivalent paths (default-trigger keys, arg-less `panel.close()`)
|
||||
will NOT dismiss persistent panels. This protects users from
|
||||
accidentally hiding their toolbar.
|
||||
|
||||
### Input-Block Routing (v0.3.0)
|
||||
|
||||
`opts.input_block` controls whether mouse events are absorbed:
|
||||
|
||||
| Value | Behavior |
|
||||
|-------|----------|
|
||||
| `"none"` | Window is never hit-tested. Mouse events pass through to lower windows. Use for HUD overlays. |
|
||||
| `"self"` | Hit-test the window's own bounds. Clicks inside → widget; clicks outside → fall through to next window. Default for normal tier. |
|
||||
| `"all"` | Modal. Hit-test self; clicks outside the window are swallowed (do not reach lower windows or the game-layer). Default for top tier. |
|
||||
|
||||
If any open window has `input_block="all"`, ALL outside-misses are
|
||||
swallowed at the bottom of dispatch — this is the "modal block"
|
||||
semantic. Game-layer code (canvas-paint, world-click) should consult
|
||||
`panel.point_in_any_panel(x, y)` before reacting to clicks, since
|
||||
panel-lib does not intercept clicks the module reads via `engine.input.*`
|
||||
directly.
|
||||
|
||||
Tier-defaults: hud→`"none"`, normal→`"self"`, top→`"all"`.
|
||||
|
||||
### Test backdoors (v0.3.0 additions)
|
||||
|
||||
- `panel._test_get_render_order()` — array of widget_ids in the order
|
||||
`M.render()` would iterate (per-tier, within-tier open-order).
|
||||
- `panel._test_get_input_block(id)` — resolved input_block for a
|
||||
registered window, or nil.
|
||||
- `panel._test_get_persistent(id)` — persistent flag for a registered
|
||||
window, or nil.
|
||||
|
||||
## Theme Schema
|
||||
|
||||
| Key | Default | Description |
|
||||
|
||||
Reference in New Issue
Block a user