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

6.7 KiB
Raw Blame History

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

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

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:

{
    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.

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.

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

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 } }