use bevy::prelude::*;
use crate::model::{
DIALOG_SLIDE_PX, DIALOG_SLIDE_SECONDS, POPOVER_SLIDE_PX, POPOVER_SLIDE_SECONDS, ease_out,
};
use crate::popover::PopoverPanel;
use crate::skin::Role;
#[derive(Resource, Clone, Debug, PartialEq, Reflect)]
#[reflect(Resource, Default, Debug, PartialEq, Clone)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(default)
)]
pub struct MotionSettings {
pub duration_scale: f32,
}
impl Default for MotionSettings {
fn default() -> Self {
Self {
duration_scale: 1.0,
}
}
}
impl MotionSettings {
pub const fn off() -> Self {
Self {
duration_scale: 0.0,
}
}
pub fn is_off(&self) -> bool {
self.duration_scale.is_nan() || self.duration_scale <= 0.0
}
}
#[derive(Component, Clone, Debug, PartialEq, Reflect)]
#[reflect(Component, Debug, PartialEq, Clone)]
pub struct SlideIn {
pub from: Vec2,
pub seconds: f32,
age: f32,
}
impl SlideIn {
pub const fn new(from: Vec2, seconds: f32) -> Self {
Self {
from,
seconds,
age: 0.0,
}
}
}
#[derive(Component, Clone, Debug, PartialEq)]
pub struct PopoverSlide {
age: f32,
progress: f32,
}
impl PopoverSlide {
pub fn reach(&self) -> f32 {
POPOVER_SLIDE_PX * (1.0 - self.progress)
}
}
type NewCard = (Entity, &'static Role, &'static ChildOf);
type NewCardFilter = (Or<(Added<Role>, Added<ChildOf>)>, Without<SlideIn>);
pub fn begin_entrances(
settings: Res<MotionSettings>,
mut commands: Commands,
popovers: Query<Entity, (Added<PopoverPanel>, Without<PopoverSlide>)>,
cards: Query<NewCard, NewCardFilter>,
roles: Query<&Role>,
) {
if settings.is_off() {
return;
}
for entity in &popovers {
commands.entity(entity).insert(PopoverSlide {
age: 0.0,
progress: 0.0,
});
}
for (entity, role, parent) in &cards {
if matches!(role, Role::Panel | Role::Palette)
&& roles.get(parent.parent()).is_ok_and(|r| *r == Role::Scrim)
{
commands.entity(entity).insert(SlideIn::new(
Vec2::new(0.0, DIALOG_SLIDE_PX),
DIALOG_SLIDE_SECONDS,
));
}
}
}
pub fn update_motion(
settings: Res<MotionSettings>,
time: Option<Res<Time<Real>>>,
mut commands: Commands,
mut slides: Query<(Entity, &mut SlideIn, &mut UiTransform, Option<&Visibility>)>,
mut popovers: Query<(Entity, &mut PopoverSlide, Option<&Visibility>)>,
) {
let delta = time.map_or(0.0, |time| time.delta_secs());
let off = settings.is_off();
for (entity, mut slide, mut transform, visibility) in &mut slides {
let length = slide.seconds * settings.duration_scale;
let done = off || length <= 0.0 || slide.age >= length;
if !done && visibility != Some(&Visibility::Hidden) {
slide.age += delta;
}
let progress = if done || slide.age >= length {
1.0
} else {
ease_out(slide.age, length)
};
let left = 1.0 - progress;
let want = Val2::px(slide.from.x * left, slide.from.y * left);
if transform.translation != want {
transform.translation = want;
}
if progress >= 1.0 {
commands.entity(entity).remove::<SlideIn>();
}
}
for (entity, mut slide, visibility) in &mut popovers {
if slide.progress >= 1.0 {
commands.entity(entity).remove::<PopoverSlide>();
continue;
}
let length = POPOVER_SLIDE_SECONDS * settings.duration_scale;
if !off && length > 0.0 && visibility != Some(&Visibility::Hidden) {
slide.age += delta;
}
let progress = if off || length <= 0.0 || slide.age >= length {
1.0
} else {
ease_out(slide.age, length)
};
if slide.progress != progress {
slide.progress = progress;
}
}
}