bevy_sprinkles_editor 0.3.0

GPU particle system editor for Bevy
use std::time::Duration;

use bevy::color::palettes::tailwind;
use bevy::prelude::*;
use bevy::text::FontSourceTemplate;

use crate::ui::icons::{ICON_CHECKBOX_CIRCLE, ICON_CLOSE, ICON_CLOSE_CIRCLE, ICON_INFORMATION};
use crate::ui::tokens::{CORNER_RADIUS, FONT_PATH, TEXT_BODY_COLOR, TEXT_SIZE};
use crate::ui::widgets::button::{ButtonVariant, IconButtonProps, icon_button};

pub const TOAST_BOTTOM_OFFSET: f32 = 12.0;
pub const DEFAULT_TOAST_DURATION: Duration = Duration::from_millis(3000);

#[derive(Component, Default, Clone)]
pub struct EditorToast;

#[derive(Component)]
pub struct ToastCloseButton(pub Entity);

#[derive(Component, Default, Clone)]
pub struct ToastIcon;

#[derive(Component, Default, Clone)]
pub struct ToastText;

#[derive(Component, Default, Clone, Copy)]
pub enum ToastVariant {
    #[default]
    Info,
    Success,
    Error,
}

impl ToastVariant {
    pub fn bg_color(&self) -> Srgba {
        match self {
            Self::Info => tailwind::ZINC_700,
            Self::Success => tailwind::GREEN_800,
            Self::Error => tailwind::RED_800,
        }
    }

    pub fn icon(&self) -> &'static str {
        match self {
            Self::Info => ICON_INFORMATION,
            Self::Success => ICON_CHECKBOX_CIRCLE,
            Self::Error => ICON_CLOSE_CIRCLE,
        }
    }
}

#[derive(Component, Default, Clone)]
pub struct ToastDuration(pub Timer);

pub fn toast(variant: ToastVariant, content: impl Into<String>, duration: Duration) -> impl Scene {
    let content: String = content.into();
    let icon = variant.icon();
    let bg = variant.bg_color().with_alpha(0.);

    bsn! {
        EditorToast
        template_value(variant)
        Interaction
        template_value(ToastDuration(Timer::new(duration, TimerMode::Once)))
        Node {
            position_type: { PositionType::Absolute },
            left: percent(50),
            bottom: px(TOAST_BOTTOM_OFFSET),
            column_gap: px(12),
            padding: { UiRect::axes(px(12), px(6)) },
            border: { UiRect::all(px(1)) },
            border_radius: { BorderRadius::all(CORNER_RADIUS) },
            box_sizing: { BoxSizing::BorderBox },
            align_items: { AlignItems::Center },
        }
        template_value(UiTransform {
            translation: Val2 {
                x: percent(-50),
                y: px(24),
            },
            scale: Vec2::splat(0.75),
            ..default()
        })
        BackgroundColor({ bg })
        template_value(BorderColor::all(TEXT_BODY_COLOR.with_alpha(0.)))
        Children [
            (
                ToastIcon
                ImageNode {
                    image: { icon },
                    color: { TEXT_BODY_COLOR.with_alpha(0.) },
                }
                Node {
                    width: px(18),
                    height: px(18),
                }
            ),
            (
                ToastText
                Text({ content })
                TextFont {
                    font: { FontSourceTemplate::Handle(FONT_PATH.into()) },
                    font_size: TEXT_SIZE,
                }
                TextColor({ TEXT_BODY_COLOR.with_alpha(0.) })
            ),
            (
                Node {
                    column_gap: px(6),
                    align_items: { AlignItems::Center },
                }
                Children [
                    Node {
                        width: px(1),
                    },
                    icon_button(
                        IconButtonProps::new(ICON_CLOSE)
                            .variant(ButtonVariant::Ghost)
                            .with_alpha(0.),
                    ),
                ]
            ),
        ]
    }
}