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 aetna-core --example toast`
22
23use std::time::Duration;
24use std::time::Instant;
25
26use aetna_core::layout::assign_ids;
27use aetna_core::prelude::*;
28use aetna_core::state::UiState;
29use aetna_core::toast::synthesize_toasts;
30
31fn fixture() -> El {
32    // Apps wrap their main view in `overlays(main, [])` so the
33    // runtime can append the synthesized toast layer as an overlay
34    // sibling — same convention as for popovers and modals.
35    overlays(
36        column([
37            h2("Toasts"),
38            paragraph(
39                "Apps queue toasts by returning ToastSpec values from \
40                 App::drain_toasts. The runtime stamps each with a TTL, \
41                 stacks them at the bottom-right corner, and dismisses \
42                 them on click or auto-expiry.",
43            )
44            .muted(),
45            row([
46                button("Save changes").key("save"),
47                button("Trigger error").key("err"),
48                button("Show info").key("info"),
49            ])
50            .gap(tokens::SPACE_2),
51        ])
52        .gap(tokens::SPACE_4)
53        .padding(tokens::SPACE_7)
54        .width(Size::Fill(1.0))
55        .height(Size::Fill(1.0)),
56        [],
57    )
58}
59
60fn main() -> std::io::Result<()> {
61    let viewport = Rect::new(0.0, 0.0, 720.0, 360.0);
62    // Seed the runtime's toast queue directly so the bundle dump
63    // shows the synthesized layer. In a live app the host calls
64    // `runner.push_toasts(app.drain_toasts())` once per frame.
65    let mut state = UiState::new();
66    let now = Instant::now();
67    let long_ttl = Duration::from_secs(60);
68    state.push_toast(ToastSpec::success("Settings saved").with_ttl(long_ttl), now);
69    state.push_toast(
70        ToastSpec::warning("Battery low — connect charger").with_ttl(long_ttl),
71        now,
72    );
73    state.push_toast(
74        ToastSpec::error("Failed to reach update server").with_ttl(long_ttl),
75        now,
76    );
77    state.push_toast(
78        ToastSpec::info("New version available").with_ttl(long_ttl),
79        now,
80    );
81
82    let mut tree = fixture();
83    assign_ids(&mut tree);
84    let _ = synthesize_toasts(&mut tree, &mut state, now);
85    let bundle = render_bundle_with(&mut tree, &mut state, viewport);
86
87    let out_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("out");
88    let written = write_bundle(&bundle, &out_dir, "toast")?;
89    for p in &written {
90        println!("wrote {}", p.display());
91    }
92
93    if !bundle.lint.findings.is_empty() {
94        eprintln!("\nlint findings ({}):", bundle.lint.findings.len());
95        eprint!("{}", bundle.lint.text());
96    }
97    Ok(())
98}