use crate::app::settings::GameSettings;
use crate::app::{Playback, Screen, menu_ui, palette};
use bevy::prelude::*;
#[derive(Resource)]
pub struct PlaybackSpeed(pub u8);
impl Default for PlaybackSpeed {
fn default() -> Self {
PlaybackSpeed(1)
}
}
impl PlaybackSpeed {
pub const STEPS: [u8; 3] = [1, 2, 4];
pub fn stepped(self) -> PlaybackSpeed {
let next = Self::STEPS
.iter()
.position(|&s| s == self.0)
.map_or(0, |at| (at + 1) % Self::STEPS.len());
PlaybackSpeed(Self::STEPS[next])
}
}
pub fn playback_speed_input(
keys: Res<ButtonInput<KeyCode>>,
caps: Res<crate::app::keycaps::KeyCaps>,
pads: crate::app::gamepad::face::PadsOnFace,
playback: Res<Playback>,
mut speed: ResMut<PlaybackSpeed>,
) {
let asked =
caps.just_pressed(&keys, 'S') || pads.pressed(crate::app::gamepad::scheme::Does::Speed);
if playback.0.is_some() && asked {
*speed = PlaybackSpeed(speed.0).stepped();
}
}
#[derive(Component)]
pub struct ReplayBar;
#[derive(Component)]
pub struct ReplayProgress;
#[derive(Component)]
pub struct ReplayReadout;
const TRACK: f32 = 420.0;
pub fn tend_replay_bar(
mut commands: Commands,
playback: Res<Playback>,
screen: Res<State<Screen>>,
bar: Query<Entity, With<ReplayBar>>,
) {
let watching = playback.0.is_some() && *screen.get() == Screen::Versus;
if !watching {
for entity in &bar {
commands.entity(entity).despawn();
}
return;
}
if !bar.is_empty() {
return;
}
commands
.spawn((
ReplayBar,
Node {
position_type: PositionType::Absolute,
bottom: Val::Px(56.0),
left: Val::Px(0.0),
width: Val::Percent(100.0),
flex_direction: FlexDirection::Column,
align_items: AlignItems::Center,
row_gap: Val::Px(5.0),
..default()
},
))
.with_children(|column| {
column
.spawn((
Node {
width: Val::Px(TRACK),
height: Val::Px(6.0),
border_radius: BorderRadius::all(Val::Px(3.0)),
..default()
},
BackgroundColor(palette::BAR_TRACK),
))
.with_children(|rail| {
rail.spawn((
ReplayProgress,
Node {
width: Val::Percent(0.0),
height: Val::Percent(100.0),
border_radius: BorderRadius::all(Val::Px(3.0)),
..default()
},
BackgroundColor(palette::GOLD),
));
});
column
.spawn((
Node {
padding: UiRect::axes(Val::Px(12.0), Val::Px(3.0)),
border_radius: BorderRadius::all(Val::Px(10.0)),
..default()
},
BackgroundColor(palette::PILL_FILL),
))
.with_children(|pill| {
pill.spawn((
ReplayReadout,
Text::new(String::new()),
TextFont {
font_size: FontSize::Px(menu_ui::type_scale::FINE),
..default()
},
TextLayout::no_wrap(),
TextColor(palette::PARCHMENT),
));
});
});
}
fn stamp(ticks: usize) -> String {
let seconds = ticks / crate::sim::TICKS_PER_SECOND as usize;
format!("{}:{:02}", seconds / 60, seconds % 60)
}
pub fn update_replay_bar(
playback: Res<Playback>,
paused: Res<crate::app::Paused>,
speed: Res<PlaybackSpeed>,
settings: Res<GameSettings>,
mut track: Query<&mut Node, With<ReplayProgress>>,
mut readout: Query<&mut Text, With<ReplayReadout>>,
) {
let Some((replay, at)) = &playback.0 else {
return;
};
let total = replay.inputs.len().max(1);
let through = (*at).min(total);
let width = Val::Percent(through as f32 / total as f32 * 100.0);
for mut node in &mut track {
if node.width != width {
node.width = width;
}
}
let tr = settings.tr();
let line = format!(
"{} {}x {} / {}",
if paused.0 {
tr.replay_paused
} else {
tr.replay_playing
},
speed.0,
stamp(through),
stamp(total)
);
for mut text in &mut readout {
menu_ui::set_text(&mut text, &line);
}
}
pub fn playback_pause_input(
keys: Res<ButtonInput<KeyCode>>,
pads: crate::app::gamepad::face::PadsOnFace,
playback: Res<Playback>,
mut paused: ResMut<crate::app::Paused>,
) {
if playback.0.is_none() {
return;
}
if keys.just_pressed(KeyCode::Space)
|| pads.pressed(crate::app::gamepad::scheme::Does::PlayPause)
{
paused.0 = !paused.0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_transport_clock_pads_its_seconds() {
assert_eq!(stamp(0), "0:00", "the very start");
assert_eq!(stamp(crate::sim::TICKS_PER_SECOND as usize * 7), "0:07");
assert_eq!(stamp(crate::sim::TICKS_PER_SECOND as usize * 59), "0:59");
assert_eq!(stamp(crate::sim::TICKS_PER_SECOND as usize * 60), "1:00");
assert_eq!(stamp(crate::sim::TICKS_PER_SECOND as usize * 185), "3:05");
}
#[test]
fn the_transport_clock_counts_in_ticks() {
let one_second = crate::sim::TICKS_PER_SECOND as usize;
assert_eq!(stamp(one_second - 1), "0:00", "part of a second is not one");
assert_eq!(stamp(one_second), "0:01", "and a whole one is");
}
#[test]
fn the_speed_key_cycles_the_steps() {
let mut speed = PlaybackSpeed::default();
assert_eq!(speed.0, 1);
for want in [2, 4, 1, 2] {
speed = PlaybackSpeed(speed.0).stepped();
assert_eq!(speed.0, want);
}
}
}