Skip to main content

toast/
toast.rs

1//! toast — bundle-dump demo of the runtime-managed toast stack.
2//!
3//! Apps push `ToastSpec`s by accumulating them in their state and
4//! returning them from `App::drain_toasts`. The runtime stamps each
5//! with a monotonic id + an `expires_at` deadline, queues them on
6//! `UiState::toasts`, and synthesizes a `Kind::Custom("toast_stack")`
7//! floating layer at the El root each frame. The stack is bottom-right
8//! anchored; each card carries a level-coloured leading bar, the
9//! message, and a `toast-dismiss-{id}` button the runtime intercepts.
10//!
11//! This headless example seeds four toasts directly via
12//! `UiState::push_toast` (skipping the App wiring), then runs the
13//! same `synthesize_toasts` + `layout` + `draw_ops` path the live
14//! runner uses. Inspect the bundle artifacts to see:
15//!
16//! - `out/toast.tree.txt` — `toast_stack` layer with one `toast_card`
17//!   child per active toast.
18//! - `out/toast.draw_ops.txt` — surface quad + text glyph runs for
19//!   each card and the trailing `toast-dismiss-{id}` button.
20//!
21//! Run: `cargo run -p damascene-core --example toast`
22
23use std::time::Duration;
24// `web_time::Instant` is what `push_toast` expects — a re-export of
25// `std::time::Instant` on native, a working clock on wasm.
26use web_time::Instant;
27
28use damascene_core::layout::assign_ids;
29use damascene_core::prelude::*;
30use damascene_core::state::UiState;
31use damascene_core::toast::synthesize_toasts;
32
33fn fixture() -> El {
34    // Apps wrap their main view in `overlays(main, [])` so the
35    // runtime can append the synthesized toast layer as an overlay
36    // sibling — same convention as for popovers and modals.
37    overlays(
38        column([
39            h2("Toasts"),
40            paragraph(
41                "Apps queue toasts by returning ToastSpec values from \
42                 App::drain_toasts. The runtime stamps each with a TTL, \
43                 stacks them at the bottom-right corner, and dismisses \
44                 them on click or auto-expiry.",
45            )
46            .muted(),
47            row([
48                button("Save changes").key("save"),
49                button("Trigger error").key("err"),
50                button("Show info").key("info"),
51            ])
52            .gap(tokens::SPACE_2),
53        ])
54        .gap(tokens::SPACE_4)
55        .padding(tokens::SPACE_7)
56        .width(Size::Fill(1.0))
57        .height(Size::Fill(1.0)),
58        [],
59    )
60}
61
62fn main() -> std::io::Result<()> {
63    let viewport = Rect::new(0.0, 0.0, 720.0, 360.0);
64    // Seed the runtime's toast queue directly so the bundle dump
65    // shows the synthesized layer. In a live app the host calls
66    // `runner.push_toasts(app.drain_toasts())` once per frame.
67    let mut state = UiState::new();
68    let now = Instant::now();
69    let long_ttl = Duration::from_secs(60);
70    state.push_toast(ToastSpec::success("Settings saved").with_ttl(long_ttl), now);
71    state.push_toast(
72        ToastSpec::warning("Battery low — connect charger").with_ttl(long_ttl),
73        now,
74    );
75    state.push_toast(
76        ToastSpec::error("Failed to reach update server").with_ttl(long_ttl),
77        now,
78    );
79    state.push_toast(
80        ToastSpec::info("New version available").with_ttl(long_ttl),
81        now,
82    );
83
84    let mut tree = fixture();
85    assign_ids(&mut tree);
86    let _ = synthesize_toasts(&mut tree, &mut state, now);
87    let bundle = render_bundle_with(&mut tree, &mut state, viewport);
88
89    let out_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("out");
90    let written = write_bundle(&bundle, &out_dir, "toast")?;
91    for p in &written {
92        println!("wrote {}", p.display());
93    }
94
95    if !bundle.lint.findings.is_empty() {
96        eprintln!("\nlint findings ({}):", bundle.lint.findings.len());
97        eprint!("{}", bundle.lint.text());
98    }
99    Ok(())
100}