bevy_sprinkles_editor 0.2.0

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

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

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};
use crate::ui::widgets::separator::{SeparatorProps, separator};

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

#[derive(Component)]
pub struct EditorToast;

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

#[derive(Component)]
pub struct ToastIcon;

#[derive(Component)]
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)]
pub struct ToastDuration(pub Timer);

pub fn toast(
    variant: ToastVariant,
    content: impl Into<String>,
    duration: Duration,
    asset_server: &AssetServer,
) -> impl Bundle {
    let font: Handle<Font> = asset_server.load(FONT_PATH);

    (
        EditorToast,
        variant,
        Interaction::None,
        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,
            ..default()
        },
        UiTransform {
            translation: Val2 {
                x: percent(-50),
                y: px(24),
            },
            scale: Vec2::splat(0.75),
            ..default()
        },
        BackgroundColor(variant.bg_color().with_alpha(0.).into()),
        BorderColor::all(TEXT_BODY_COLOR.with_alpha(0.)),
        children![
            (
                ToastIcon,
                ImageNode::new(asset_server.load(variant.icon()))
                    .with_color(TEXT_BODY_COLOR.with_alpha(0.).into()),
                Node {
                    width: px(18),
                    height: px(18),
                    ..default()
                },
            ),
            (
                ToastText,
                Text::new(content),
                TextFont {
                    font: font.into(),
                    font_size: TEXT_SIZE,
                    ..default()
                },
                TextColor(TEXT_BODY_COLOR.with_alpha(0.).into()),
            ),
            (
                Node {
                    column_gap: px(6),
                    align_items: AlignItems::Center,
                    ..default()
                },
                children![
                    separator(SeparatorProps::vertical().with_alpha(0.)),
                    icon_button(
                        IconButtonProps::new(ICON_CLOSE)
                            .variant(ButtonVariant::Ghost)
                            .with_alpha(0.),
                        asset_server
                    ),
                ],
            ),
        ],
    )
}