rosace_widgets/tree/
toast.rs1use rosace_core::types::{Point, Size};
2use rosace_render::Color;
3use rosace_state::Atom;
4use super::{Widget, LayoutCtx, PaintCtx};
5use super::container::draw_rounded_rect_pub;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum ToastKind {
9 Info,
10 Success,
11 Error,
12}
13
14pub struct Toast {
26 pub message: String,
27 pub kind: ToastKind,
28 pub font_size: Option<f32>,
32 background: Option<Color>,
33 color: Option<Color>,
34 accent: Option<Color>,
35 radius: Option<f32>,
36}
37
38impl Toast {
39 pub fn info(message: impl Into<String>) -> Self {
40 Self { message: message.into(), kind: ToastKind::Info, font_size: None, background: None, color: None, accent: None, radius: None }
41 }
42
43 pub fn success(message: impl Into<String>) -> Self {
44 Self { message: message.into(), kind: ToastKind::Success, font_size: None, background: None, color: None, accent: None, radius: None }
45 }
46
47 pub fn error(message: impl Into<String>) -> Self {
48 Self { message: message.into(), kind: ToastKind::Error, font_size: None, background: None, color: None, accent: None, radius: None }
49 }
50
51 pub fn background(mut self, c: Color) -> Self { self.background = Some(c); self }
53 pub fn color(mut self, c: Color) -> Self { self.color = Some(c); self }
55 pub fn accent(mut self, c: Color) -> Self { self.accent = Some(c); self }
57 pub fn radius(mut self, r: f32) -> Self { self.radius = Some(r); self }
59
60 fn resolved_font_size(&self, theme: &rosace_theme::ThemeData) -> f32 {
61 self.font_size.unwrap_or(theme.typography.body_medium.size)
62 }
63
64 pub fn show(open: &Atom<bool>, secs: f32) {
70 open.set(true);
71 let open = open.clone();
72 rosace_state::fire_after_ms((secs * 1000.0) as u64, move || {
75 open.set(false);
76 });
77 }
78
79 fn resolve_accent(&self, ctx: &PaintCtx) -> Color {
80 if let Some(c) = self.accent { return c; }
81 let t = &ctx.theme.colors;
82 match self.kind {
83 ToastKind::Info => ctx.tc(t.primary),
84 ToastKind::Success => Color::rgb(72, 199, 116),
85 ToastKind::Error => ctx.tc(t.error),
86 }
87 }
88}
89
90const PAD_H: f32 = 16.0;
91const PAD_V: f32 = 10.0;
92const DOT: f32 = 8.0;
93const GAP: f32 = 10.0;
94
95impl Widget for Toast {
96 fn layout(&self, ctx: &LayoutCtx) -> Size {
97 let font_size = self.resolved_font_size(ctx.theme);
98 let text_w = ctx.font.measure_text(&self.message, font_size);
99 let line_h = ctx.font.line_height(font_size);
100 ctx.constraints.constrain(Size {
101 width: PAD_H * 2.0 + DOT + GAP + text_w,
102 height: PAD_V * 2.0 + line_h.max(DOT),
103 })
104 }
105
106 fn paint(&self, ctx: &mut PaintCtx) {
107 ctx.semantics(super::Semantics::new(rosace_core::Role::Alert).label(&self.message));
108 let r = ctx.rect;
109 let radius = self.radius.unwrap_or(r.size.height / 2.0);
110 let (bg, fg) = {
111 let t = &ctx.theme.colors;
112 (self.background.unwrap_or_else(|| ctx.tc(t.surface_variant)),
113 self.color.unwrap_or_else(|| ctx.tc(t.on_surface)))
114 };
115 ctx.fill_shadow_rrect(r, radius, Color::rgba(0, 0, 0, 90), 10.0);
116 draw_rounded_rect_pub(ctx, r, bg, radius);
117
118 let accent = self.resolve_accent(ctx);
119 ctx.fill_circle(Point {
120 x: r.origin.x + PAD_H + DOT / 2.0,
121 y: r.origin.y + r.size.height / 2.0,
122 }, DOT / 2.0, accent);
123
124 let font_size = self.resolved_font_size(&ctx.theme);
125 let line_h = ctx.font.line_height(font_size);
126 ctx.draw_text_at(
127 &self.message,
128 Point {
129 x: r.origin.x + PAD_H + DOT + GAP,
130 y: r.origin.y + (r.size.height - line_h) / 2.0,
131 },
132 fg,
133 font_size,
134 );
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use rosace_layout::Constraints;
142
143 #[test]
144 fn customization_builders_do_not_change_layout_size() {
145 let font = rosace_render::FontCache::embedded();
146 let theme = rosace_theme::built_in::dark_theme();
147 let ctx = LayoutCtx::new(Constraints::loose(400.0, 400.0), &font, &theme);
148 let base = Toast::info("Saved");
149 let customized = Toast::info("Saved")
150 .background(Color::rgb(10, 10, 10))
151 .color(Color::rgb(255, 255, 255))
152 .accent(Color::rgb(0, 200, 0))
153 .radius(4.0);
154 assert_eq!(base.layout(&ctx), customized.layout(&ctx));
155 }
156}