use egui::{Color32, Context, Id, Response};
use crate::theme::{mix, Theme};
pub const FLASH_DURATION: f64 = 0.8;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum FlashKind {
Success,
Error,
}
pub trait ResponseFlashExt {
fn flash_success(&self);
fn flash_error(&self);
fn clear_flash(&self);
}
impl ResponseFlashExt for Response {
fn flash_success(&self) {
start(&self.ctx, self.id, FlashKind::Success);
}
fn flash_error(&self) {
start(&self.ctx, self.id, FlashKind::Error);
}
fn clear_flash(&self) {
self.ctx.data_mut(|d| d.remove::<FlashState>(self.id));
self.ctx.request_repaint();
}
}
pub fn flash_success(ctx: &Context, id: Id) {
start(ctx, id, FlashKind::Success);
}
pub fn flash_error(ctx: &Context, id: Id) {
start(ctx, id, FlashKind::Error);
}
#[derive(Clone, Copy)]
pub(crate) struct FlashState {
kind: FlashKind,
started_at: f64,
}
pub(crate) fn start(ctx: &Context, id: Id, kind: FlashKind) {
let now = ctx.input(|i| i.time);
ctx.data_mut(|d| {
d.insert_temp(
id,
FlashState {
kind,
started_at: now,
},
);
});
ctx.request_repaint();
}
pub(crate) fn active_flash(ctx: &Context, id: Id) -> Option<(FlashKind, f32)> {
let state = ctx.data(|d| d.get_temp::<FlashState>(id))?;
let now = ctx.input(|i| i.time);
let elapsed = now - state.started_at;
if !(0.0..FLASH_DURATION).contains(&elapsed) {
ctx.data_mut(|d| d.remove::<FlashState>(id));
return None;
}
ctx.request_repaint();
Some((state.kind, (elapsed / FLASH_DURATION) as f32))
}
pub(crate) fn background_fill(
theme: &Theme,
base_fill: Color32,
flash: Option<(FlashKind, f32)>,
) -> Color32 {
let Some((kind, progress)) = flash else {
return base_fill;
};
let remaining = (1.0 - progress).powi(2);
const PEAK_MIX: f32 = 0x40 as f32 / 255.0;
let accent = match kind {
FlashKind::Success => theme.palette.green,
FlashKind::Error => theme.palette.red,
};
mix(base_fill, accent, PEAK_MIX * remaining)
}