Files
sporel-lib-core.notify-display/README.md

202 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# lib-core.notify-display
Screen-space display layer for `lib-core.notify`. Routes channel messages into three
UI modes: log (panel-widget list), toast (always-on HUD overlay), detail (modal
panel that auto-opens on new message).
**Version:** 0.1.0
**Lib-ID:** lib-core.notify-display
**Requires:** lib-core.notify v0.1.0, lib-core.panel v0.1.0
**Tags:** notify, display, ui, toast, log, detail, hud, overlay
## Topology
<!-- topology:start (auto-generated; do not edit) -->
```mermaid
graph LR
this["lib-core.notify-display"]
lib_notify["lib-core.notify"]
lib_panel["lib-core.panel"]
engine_render["engine.render.*"]
this --> lib_notify
this --> lib_panel
this --> engine_render
```
<!-- topology:end -->
## Scope (v0.1.0)
v0.1 ships three display modes driven by `notify` channel `display_mode` declarations:
- **log** — panel widget that renders channel history as a sorted list (newest first).
Plugs into `lib-core.panel` via `create_log_widget` + `panel.register`.
- **toast** — always-on HUD overlay with fade-in/hold/fade-out animation and
configurable position, max-visible cap, and per-message TTL.
- **detail** — subscribes to `display_mode="detail"` channels; on each new message
sets `current_detail` and auto-opens a registered panel widget.
**Intentional non-goals (deferred to v0.2+):**
- Scroll/pagination in log widget
- Multiple simultaneous toast columns
- Toast queue persistence (survives mode detach/re-attach)
- Per-severity toast theming overrides
- Keyboard dismiss for detail panel
## API
### `display.attach_mode(mode, opts)`
Subscribes to existing `notify` channels that match `mode`. Must be called
after `notify.create_channel` for those channels (channels created later are
not automatically picked up in v0.1).
| mode | required opts | optional opts |
|------|---------------|---------------|
| `"log"` | `panel_widget_id: string` | — |
| `"toast"` | — | `position`, `max_visible`, `default_ttl` |
| `"detail"` | `panel_widget_id: string` | — |
**Toast opts schema:**
```lua
{
position = "top-right", -- default; also: top-center, top-left,
-- bottom-right, bottom-center, bottom-left
max_visible = 5, -- oldest evicted when limit exceeded
default_ttl = 3.0, -- seconds; overridden per-msg by msg.ttl
}
```
Loud-error on unknown `mode` or invalid `position`. Missing `panel_widget_id` for
log/detail is a loud-error. Nil/missing `position` in toast silently defaults to
`"top-right"`.
---
### `display.detach_mode(mode)`
Unsubscribes all subscriptions held for channels whose `display_mode == mode`,
sets `mode_opts[mode] = nil`, and clears mode-specific state:
- toast → empties `active_toasts`
- detail → clears `current_detail`
- log → clears `widget_channels`
---
### `display.create_log_widget(opts) → widget_def`
Returns a widget definition table compatible with `panel.register`.
```lua
opts = {
widget_id = "game-log", -- default
title = "Game Log", -- default
pause_on_open = false, -- default
}
```
The returned `widget_def.render(ctx)` reads from `notify.get_history` for all
channels mapped to this `widget_id` (via `attach_mode("log", {panel_widget_id=...})`),
sorts by timestamp descending, and renders up to `floor(h / row_height)` rows.
Severity tinting: warn = `0xFFD000FF`, error = `0xFF4040FF`.
---
### `display.create_detail_widget(opts) → widget_def`
Returns a widget definition table compatible with `panel.register`.
```lua
opts = {
widget_id = "detail", -- default
title = "Detail", -- default
pause_on_open = false, -- default
}
```
Renders `current_detail` (the last message posted to any `display_mode="detail"`
channel). Shows `msg.text` as title, `msg.data.description` as body, remaining
`msg.data.*` keys as key-value rows. No-op if no detail message has arrived yet.
**Note:** Register the widget with `panel.register` **before** calling
`attach_mode("detail", ...)` so that the auto-open `panel.open` call succeeds
on the first message. If registered after attach, the first message silently
fails to open the panel (pcall-guarded), but `current_detail` is still set.
---
### `display.update(dt)`
Must be called each game-update frame. Removes expired toasts from `active_toasts`
(compares `engine.time.now()` against `spawn_time + ttl`). No-op if toast mode
is not attached.
---
### `display.render()`
Must be called each render frame (typically after `panel.render()`). Draws the
toast HUD overlay. No-op if toast mode is not attached or no toasts are active.
Toast geometry: 280 × 32 px, 8 px padding from screen edge. Positions stack in
the growth direction of the chosen anchor (top-* grows down, bottom-* grows up).
Alpha animation per toast:
- 010% of TTL: fade in
- 1085% of TTL: fully opaque
- 85100% of TTL: fade out
Screen dimensions: 1280 × 720 fallback (engine.render does not expose
`get_screen_size` to Lua in v0.1 — same limitation as lib-core.panel).
---
## Theme keys consumed
`notify-display` renders toasts using hardcoded colors (no own theme keys in v0.1).
Log and detail widgets render via `ctx.theme` supplied by `lib-core.panel`, consuming
the standard panel theme keys: `text_color`, `text_color_dim`, `font_size_title`,
`font_size_body`, `padding`, `row_height`.
---
## Glue pattern example
```lua
local notify = require("lib-core.notify")
local panel = require("lib-core.panel")
local display = require("lib-core.notify-display")
-- 1. Declare channels
notify.create_channel{ id = "game-events", filter = {"event"}, display_mode = "log" }
notify.create_channel{ id = "alerts", filter = {"alert"}, display_mode = "toast" }
notify.create_channel{ id = "item-detail", filter = {"item"}, display_mode = "detail" }
-- 2. Create and register widgets
local log_def = display.create_log_widget { widget_id = "game-log" }
local detail_def = display.create_detail_widget{ widget_id = "item-detail" }
panel.register("game-log", log_def)
panel.register("item-detail", detail_def)
-- 3. Attach modes (after register so detail auto-open works immediately)
display.attach_mode("log", { panel_widget_id = "game-log" })
display.attach_mode("toast", { position = "bottom-right", default_ttl = 4.0 })
display.attach_mode("detail", { panel_widget_id = "item-detail" })
-- 4. Per-frame calls
function game_update(dt)
panel.update(dt)
display.update(dt)
end
function game_render()
panel.render()
display.render() -- toast overlay on top
end
-- 5. Posting messages
notify.post{ tags = {"event"}, text = "Player entered the forest" }
notify.post{ tags = {"alert"}, text = "Low health!", ttl = 2.0, severity = "warn" }
notify.post{ tags = {"item"}, text = "Iron Sword",
data = { description = "A sturdy blade.", damage = 12 } }
```