203 lines
6.8 KiB
Markdown
203 lines
6.8 KiB
Markdown
# 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.4.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).
|
||
`create_log_widget` returns a widget-def for the caller to register with
|
||
`lib-core.panel` via `panel.register` (the lib itself never calls `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:
|
||
- 0–10% of TTL: fade in
|
||
- 10–85% of TTL: fully opaque
|
||
- 85–100% 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 } }
|
||
```
|