use rosace_core::types::{Point, Size};
use rosace_render::Color;
use rosace_state::Atom;
use super::{Widget, LayoutCtx, PaintCtx};
use super::container::draw_rounded_rect_pub;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ToastKind {
Info,
Success,
Error,
}
pub struct Toast {
pub message: String,
pub kind: ToastKind,
pub font_size: Option<f32>,
background: Option<Color>,
color: Option<Color>,
accent: Option<Color>,
radius: Option<f32>,
}
impl Toast {
pub fn info(message: impl Into<String>) -> Self {
Self { message: message.into(), kind: ToastKind::Info, font_size: None, background: None, color: None, accent: None, radius: None }
}
pub fn success(message: impl Into<String>) -> Self {
Self { message: message.into(), kind: ToastKind::Success, font_size: None, background: None, color: None, accent: None, radius: None }
}
pub fn error(message: impl Into<String>) -> Self {
Self { message: message.into(), kind: ToastKind::Error, font_size: None, background: None, color: None, accent: None, radius: None }
}
pub fn background(mut self, c: Color) -> Self { self.background = Some(c); self }
pub fn color(mut self, c: Color) -> Self { self.color = Some(c); self }
pub fn accent(mut self, c: Color) -> Self { self.accent = Some(c); self }
pub fn radius(mut self, r: f32) -> Self { self.radius = Some(r); self }
fn resolved_font_size(&self, theme: &rosace_theme::ThemeData) -> f32 {
self.font_size.unwrap_or(theme.typography.body_medium.size)
}
pub fn show(open: &Atom<bool>, secs: f32) {
open.set(true);
let open = open.clone();
rosace_state::fire_after_ms((secs * 1000.0) as u64, move || {
open.set(false);
});
}
fn resolve_accent(&self, ctx: &PaintCtx) -> Color {
if let Some(c) = self.accent { return c; }
let t = &ctx.theme.colors;
match self.kind {
ToastKind::Info => ctx.tc(t.primary),
ToastKind::Success => Color::rgb(72, 199, 116),
ToastKind::Error => ctx.tc(t.error),
}
}
}
const PAD_H: f32 = 16.0;
const PAD_V: f32 = 10.0;
const DOT: f32 = 8.0;
const GAP: f32 = 10.0;
impl Widget for Toast {
fn layout(&self, ctx: &LayoutCtx) -> Size {
let font_size = self.resolved_font_size(ctx.theme);
let text_w = ctx.font.measure_text(&self.message, font_size);
let line_h = ctx.font.line_height(font_size);
ctx.constraints.constrain(Size {
width: PAD_H * 2.0 + DOT + GAP + text_w,
height: PAD_V * 2.0 + line_h.max(DOT),
})
}
fn paint(&self, ctx: &mut PaintCtx) {
ctx.semantics(super::Semantics::new(rosace_core::Role::Alert).label(&self.message));
let r = ctx.rect;
let radius = self.radius.unwrap_or(r.size.height / 2.0);
let (bg, fg) = {
let t = &ctx.theme.colors;
(self.background.unwrap_or_else(|| ctx.tc(t.surface_variant)),
self.color.unwrap_or_else(|| ctx.tc(t.on_surface)))
};
ctx.fill_shadow_rrect(r, radius, Color::rgba(0, 0, 0, 90), 10.0);
draw_rounded_rect_pub(ctx, r, bg, radius);
let accent = self.resolve_accent(ctx);
ctx.fill_circle(Point {
x: r.origin.x + PAD_H + DOT / 2.0,
y: r.origin.y + r.size.height / 2.0,
}, DOT / 2.0, accent);
let font_size = self.resolved_font_size(&ctx.theme);
let line_h = ctx.font.line_height(font_size);
ctx.draw_text_at(
&self.message,
Point {
x: r.origin.x + PAD_H + DOT + GAP,
y: r.origin.y + (r.size.height - line_h) / 2.0,
},
fg,
font_size,
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use rosace_layout::Constraints;
#[test]
fn customization_builders_do_not_change_layout_size() {
let font = rosace_render::FontCache::embedded();
let theme = rosace_theme::built_in::dark_theme();
let ctx = LayoutCtx::new(Constraints::loose(400.0, 400.0), &font, &theme);
let base = Toast::info("Saved");
let customized = Toast::info("Saved")
.background(Color::rgb(10, 10, 10))
.color(Color::rgb(255, 255, 255))
.accent(Color::rgb(0, 200, 0))
.radius(4.0);
assert_eq!(base.layout(&ctx), customized.layout(&ctx));
}
}