use bevy::color::palettes::tailwind;
use bevy::prelude::*;
use bevy_sprinkles::prelude::*;
use crate::state::{PlaybackPlayEvent, PlaybackResetEvent};
use crate::ui::icons::{ICON_PAUSE, ICON_PLAY, ICON_REPEAT, ICON_STOP};
use crate::ui::tokens::{PRIMARY_COLOR, TEXT_BODY_COLOR};
use crate::ui::widgets::button::{
ButtonSize, ButtonVariant, IconButtonProps, icon_button, set_button_variant,
};
use crate::viewport::EditorParticlePreview;
pub fn plugin(app: &mut App) {
app.add_systems(
Update,
(
handle_play_pause_click,
handle_stop_click,
handle_loop_click,
update_play_pause_icon,
update_loop_button_style,
),
);
}
#[derive(Component, Default, Clone)]
pub struct EditorPlaybackControls;
#[derive(Component, Default, Clone)]
pub struct PlayPauseButton;
#[derive(Component, Default, Clone)]
pub struct StopButton;
#[derive(Component, Default, Clone)]
pub struct LoopButton;
pub fn playback_controls() -> impl Scene {
bsn! {
EditorPlaybackControls
Node {
align_items: { AlignItems::Center },
column_gap: px(6),
}
Children [
play_pause_button(),
stop_button(),
loop_button(),
]
}
}
fn play_pause_button() -> impl Scene {
bsn! {
PlayPauseButton
icon_button(
IconButtonProps::new(ICON_PAUSE)
.color(tailwind::GREEN_500)
.variant(ButtonVariant::Ghost)
.with_size(ButtonSize::Icon),
)
}
}
fn stop_button() -> impl Scene {
bsn! {
StopButton
icon_button(
IconButtonProps::new(ICON_STOP)
.color(TEXT_BODY_COLOR)
.variant(ButtonVariant::Ghost)
.with_size(ButtonSize::Icon),
)
}
}
fn loop_button() -> impl Scene {
bsn! {
LoopButton
icon_button(
IconButtonProps::new(ICON_REPEAT)
.color(PRIMARY_COLOR)
.variant(ButtonVariant::Active)
.with_size(ButtonSize::Icon),
)
}
}
fn handle_play_pause_click(
mut commands: Commands,
mut runtime_query: Query<&mut ParticleSystemRuntime, With<EditorParticlePreview>>,
button_query: Query<&Interaction, (Changed<Interaction>, With<PlayPauseButton>)>,
) {
for interaction in &button_query {
if *interaction == Interaction::Pressed {
for mut runtime in &mut runtime_query {
runtime.toggle();
if !runtime.paused {
commands.trigger(PlaybackPlayEvent);
}
}
}
}
}
fn handle_stop_click(
mut commands: Commands,
button_query: Query<&Interaction, (Changed<Interaction>, With<StopButton>)>,
) {
for interaction in &button_query {
if *interaction == Interaction::Pressed {
commands.trigger(PlaybackResetEvent);
}
}
}
fn handle_loop_click(
mut runtime_query: Query<&mut ParticleSystemRuntime, With<EditorParticlePreview>>,
button_query: Query<&Interaction, (Changed<Interaction>, With<LoopButton>)>,
) {
for interaction in &button_query {
if *interaction == Interaction::Pressed {
for mut runtime in &mut runtime_query {
runtime.force_loop = !runtime.force_loop;
}
}
}
}
fn update_play_pause_icon(
asset_server: Res<AssetServer>,
runtime_query: Query<
&ParticleSystemRuntime,
(Changed<ParticleSystemRuntime>, With<EditorParticlePreview>),
>,
button_query: Query<&Children, With<PlayPauseButton>>,
mut image_query: Query<&mut ImageNode>,
) {
let Some(runtime) = runtime_query.iter().next() else {
return;
};
let icon_path = if runtime.paused {
ICON_PLAY
} else {
ICON_PAUSE
};
for children in &button_query {
for child in children.iter() {
if let Ok(mut image) = image_query.get_mut(child) {
image.image = asset_server.load(icon_path);
}
}
}
}
fn update_loop_button_style(
runtime_query: Query<
&ParticleSystemRuntime,
(Changed<ParticleSystemRuntime>, With<EditorParticlePreview>),
>,
mut button_query: Query<
(
&Children,
&mut ButtonVariant,
&mut BackgroundColor,
&mut BorderColor,
),
With<LoopButton>,
>,
mut image_query: Query<&mut ImageNode>,
) {
let Some(runtime) = runtime_query.iter().next() else {
return;
};
let variant = if runtime.force_loop {
ButtonVariant::Active
} else {
ButtonVariant::Ghost
};
for (children, mut current_variant, mut bg, mut border) in &mut button_query {
if *current_variant != variant {
*current_variant = variant;
set_button_variant(variant, &mut bg, &mut border);
}
for child in children.iter() {
if let Ok(mut image) = image_query.get_mut(child) {
image.color = variant.text_color().into();
}
}
}
}