191 lines
4.3 KiB
Rust
191 lines
4.3 KiB
Rust
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
|
|
|
|
use glam::{Mat4, Vec3};
|
|
use wgui::{
|
|
animation::{Animation, AnimationEasing},
|
|
components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR},
|
|
drawing::Color,
|
|
globals::WguiGlobals,
|
|
i18n::Translation,
|
|
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
|
|
renderer_vk::{
|
|
text::{FontWeight, TextStyle},
|
|
util::centered_matrix,
|
|
},
|
|
taffy::{
|
|
self,
|
|
prelude::{length, percent},
|
|
},
|
|
widget::{
|
|
div::WidgetDiv,
|
|
label::{WidgetLabel, WidgetLabelParams},
|
|
rectangle::{WidgetRectangle, WidgetRectangleParams},
|
|
util::WLength,
|
|
},
|
|
};
|
|
|
|
struct MountedToast {
|
|
#[allow(dead_code)]
|
|
id_root: WidgetID, // decorations of a toast
|
|
layout_tasks: LayoutTasks,
|
|
}
|
|
|
|
struct State {
|
|
toast: Option<MountedToast>,
|
|
queue: VecDeque<Translation>,
|
|
timeout: u32, // in ticks
|
|
}
|
|
|
|
pub struct ToastManager {
|
|
state: Rc<RefCell<State>>,
|
|
needs_tick: bool,
|
|
}
|
|
|
|
impl Drop for MountedToast {
|
|
fn drop(&mut self) {
|
|
self.layout_tasks.push(LayoutTask::RemoveWidget(self.id_root));
|
|
}
|
|
}
|
|
|
|
const TOAST_DURATION_TICKS: u32 = 90;
|
|
|
|
impl ToastManager {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
state: Rc::new(RefCell::new(State {
|
|
toast: None,
|
|
timeout: 0,
|
|
queue: VecDeque::new(),
|
|
})),
|
|
needs_tick: false,
|
|
}
|
|
}
|
|
|
|
fn mount_toast(
|
|
&self,
|
|
globals: &WguiGlobals,
|
|
layout: &mut Layout,
|
|
state: &mut State,
|
|
content: Translation,
|
|
) -> anyhow::Result<()> {
|
|
let mut globals = globals.get();
|
|
|
|
let (root, _) = layout.add_topmost_child(
|
|
WidgetDiv::create(),
|
|
taffy::Style {
|
|
position: taffy::Position::Absolute,
|
|
size: taffy::Size {
|
|
width: percent(1.0),
|
|
height: percent(0.8),
|
|
},
|
|
align_items: Some(taffy::AlignItems::End),
|
|
justify_content: Some(taffy::JustifyContent::Center),
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
|
|
let (rect, _) = layout.add_child(
|
|
root.id,
|
|
WidgetRectangle::create(WidgetRectangleParams {
|
|
color: TOOLTIP_COLOR,
|
|
border_color: TOOLTIP_BORDER_COLOR,
|
|
border: 2.0,
|
|
round: WLength::Percent(1.0),
|
|
..Default::default()
|
|
}),
|
|
taffy::Style {
|
|
position: taffy::Position::Relative,
|
|
gap: length(4.0),
|
|
padding: taffy::Rect {
|
|
left: length(16.0),
|
|
right: length(16.0),
|
|
top: length(8.0),
|
|
bottom: length(8.0),
|
|
},
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
|
|
let (label, _) = layout.add_child(
|
|
rect.id,
|
|
WidgetLabel::create(
|
|
&mut globals,
|
|
WidgetLabelParams {
|
|
content,
|
|
style: TextStyle {
|
|
weight: Some(FontWeight::Bold),
|
|
..Default::default()
|
|
},
|
|
},
|
|
),
|
|
taffy::Style { ..Default::default() },
|
|
)?;
|
|
|
|
// show-up animation
|
|
layout.animations.add(Animation::new(
|
|
rect.id,
|
|
160, // does not use anim_mult
|
|
AnimationEasing::Linear,
|
|
Box::new(move |common, data| {
|
|
let pos_showup = AnimationEasing::OutQuint.interpolate((data.pos * 4.0).min(1.0));
|
|
let opacity = 1.0 - AnimationEasing::OutQuint.interpolate(((data.pos - 0.75) * 4.0).clamp(0.0, 1.0));
|
|
let scale = AnimationEasing::OutBack.interpolate((data.pos * 4.0).min(1.0));
|
|
|
|
{
|
|
let mtx = Mat4::from_translation(Vec3::new(0.0, (1.0 - pos_showup) * 100.0, 0.0))
|
|
* Mat4::from_scale(Vec3::new(scale, scale, 1.0));
|
|
data.data.transform = centered_matrix(data.widget_boundary.size, &mtx);
|
|
}
|
|
|
|
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
|
|
rect.params.color.a = opacity;
|
|
rect.params.border_color.a = opacity;
|
|
|
|
let mut label = common.state.widgets.get_as::<WidgetLabel>(label.id).unwrap();
|
|
label.set_color(common, Color::new(1.0, 1.0, 1.0, opacity), true);
|
|
common.alterables.mark_redraw();
|
|
}),
|
|
));
|
|
|
|
state.toast = Some(MountedToast {
|
|
id_root: root.id,
|
|
layout_tasks: layout.tasks.clone(),
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn tick(&mut self, globals: &WguiGlobals, layout: &mut Layout) -> anyhow::Result<()> {
|
|
if !self.needs_tick {
|
|
return Ok(());
|
|
}
|
|
|
|
let mut state = self.state.borrow_mut();
|
|
|
|
if state.timeout > 0 {
|
|
state.timeout -= 1;
|
|
}
|
|
|
|
if state.timeout == 0 {
|
|
state.toast = None;
|
|
state.timeout = TOAST_DURATION_TICKS;
|
|
// mount next
|
|
if let Some(content) = state.queue.pop_front() {
|
|
self.mount_toast(globals, layout, &mut state, content)?;
|
|
}
|
|
}
|
|
|
|
if state.queue.is_empty() && state.toast.is_none() {
|
|
self.needs_tick = false;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn push(&mut self, content: Translation) {
|
|
let mut state = self.state.borrow_mut();
|
|
state.queue.push_back(content);
|
|
self.needs_tick = true;
|
|
}
|
|
}
|