Adopt actions framework

main
Elijah Voigt 6 days ago
parent 494cdce42e
commit 09e6c66788

@ -12,9 +12,13 @@ pub use bevy::{
color::palettes::basic::*,
feathers::{
FeathersPlugins,
constants::{fonts, size},
containers::*,
controls::*,
dark_theme::create_dark_theme,
theme::{ThemeProps, ThemedText, UiTheme},
font_styles::InheritableFont,
theme::{InheritableThemeTextColor, ThemeProps, ThemedText, UiTheme},
tokens,
},
input::{
common_conditions::*,
@ -25,5 +29,10 @@ pub use bevy::{
platform::collections::{HashMap, HashSet},
prelude::*,
sprite_render::MeshMaterial2dTemplate,
ui_widgets::Activate,
text::FontWeight,
ui::{Checked, InteractionDisabled},
ui_widgets::{
Activate, ActivateOnPress, Checkbox, SliderPrecision, SliderStep, ValueChange,
checkbox_self_update, slider_self_update,
},
};

@ -0,0 +1,137 @@
use super::*;
pub(crate) struct GameActionsPlugin;
impl Plugin for GameActionsPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
ActionsPlugin::<SimAction>::new()
.bind(SimAction::Toggle, [Binding::Key(KeyCode::Space)])
.bind(SimAction::Step, [Binding::Key(KeyCode::KeyN)])
.bind(SimAction::Reset, [Binding::Key(KeyCode::KeyR)]),
ActionsPlugin::<AudioAction>::new()
.bind(AudioAction::Toggle, [Binding::Key(KeyCode::KeyM)]),
ActionsPlugin::<Action>::new()
.bind(Action::ZoomIn, [Binding::Key(KeyCode::Equal)])
.bind(Action::ZoomOut, [Binding::Key(KeyCode::Minus)])
.bind(Action::Quit, [Binding::Key(KeyCode::Escape)]),
));
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, GameAction, Default)]
pub(crate) enum SimAction {
#[action_handler(SimAction::toggle)]
Toggle,
#[action_handler(SimAction::step)]
Step,
#[action_handler(SimAction::reset)]
Reset,
#[action_handler(SimAction::wipe)]
Wipe,
#[default]
None,
}
impl SimAction {
/// When the user toggles between Play/Pause simulation, just update the state
fn toggle(
curr: Res<State<SimulationPlayback>>,
mut next: ResMut<NextState<SimulationPlayback>>,
mut last_board_state: ResMut<LastBoardState>,
coordinates: Query<&Coordinates, With<Cell>>,
) {
next.set(match curr.get() {
SimulationPlayback::Run | SimulationPlayback::Step => {
// Run | Step -> Pause
SimulationPlayback::Pause
}
SimulationPlayback::Pause => {
// Capture current state before running
last_board_state.coordinates = coordinates.iter().cloned().collect();
// Pause -> Run
SimulationPlayback::Run
}
});
}
// When the user wants to step the simulation, just enter the SimulationStep state
// TODO: Should switch back to Pause automatically after step if Step is the current state
fn step(mut next: ResMut<NextState<SimulationPlayback>>) {
next.set(SimulationPlayback::Step);
}
// Reset board to last saved state (before running, or before wiping)
fn reset(
last_board_state: Res<LastBoardState>,
mut lifecycle: MessageWriter<Lifecycle>,
cells: Query<Entity, With<Cell>>,
mut commands: Commands,
) {
// TODO: reconcile board instead of despawn/spawning the world
cells.iter().for_each(|e| {
commands.entity(e).despawn();
});
last_board_state.coordinates.iter().for_each(|c| {
lifecycle.write(Lifecycle::Alive(c.clone()));
});
}
// Wipe all cells from the board
fn wipe(cells: Query<Entity, With<Cell>>, mut commands: Commands) {
cells.iter().for_each(|e| {
commands.entity(e).despawn();
});
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, GameAction, Default)]
pub(crate) enum Action {
#[action_handler(Action::zoom_in)]
ZoomIn,
#[action_handler(Action::zoom_out)]
ZoomOut,
#[action_handler(Action::quit)]
Quit,
#[default]
None,
}
impl Action {
fn zoom_in(mut projection: Query<&mut Projection>) {
projection.iter_mut().for_each(|mut p| match *p {
Projection::Orthographic(ref mut o) => {
o.scale *= 0.5;
}
_ => todo!(),
});
}
fn zoom_out(mut projection: Query<&mut Projection>) {
projection.iter_mut().for_each(|mut p| match *p {
Projection::Orthographic(ref mut o) => {
o.scale /= 0.5;
}
_ => todo!(),
});
}
fn quit(mut writer: MessageWriter<AppExit>) {
writer.write(AppExit::Success);
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, GameAction, Default)]
pub(crate) enum AudioAction {
#[action_handler(AudioAction::toggle)]
Toggle,
#[default]
None,
}
impl AudioAction {
fn toggle(curr: Res<State<AudioPlayback>>, mut next: ResMut<NextState<AudioPlayback>>) {
next.set(match curr.get() {
AudioPlayback::Play => AudioPlayback::Mute,
AudioPlayback::Mute => AudioPlayback::Play,
});
}
}

@ -11,23 +11,11 @@
// Only because of FromTemplat macros unfortunately...
#![allow(dead_code)]
use bevy::{
feathers::{
constants::{fonts, size},
containers::*,
font_styles::InheritableFont,
theme::{InheritableThemeTextColor, ThemeProps},
tokens,
},
text::FontWeight,
ui::{Checked, InteractionDisabled},
ui_widgets::{
ActivateOnPress, Checkbox, SliderPrecision, SliderStep, ValueChange, checkbox_self_update,
slider_self_update,
},
};
use engine::*;
mod actions;
use actions::*;
const TILE: &str = "tileGrey_01.png";
const SIM_FRAME_DURATION: f32 = 0.5;
@ -38,6 +26,7 @@ fn main() {
..default()
}))
.add_plugins(FeathersPlugins)
.add_plugins(GameActionsPlugin)
.init_resource::<NextCoordinates>()
.init_resource::<CellAssets>()
.init_resource::<Panning>()
@ -58,42 +47,60 @@ fn main() {
load_meshes,
),
)
// Cell management/Simulation
.add_systems(
Update,
(
cell_lifecycle.run_if(on_message::<Lifecycle>),
update_grid_position.run_if(on_message::<CursorMoved>),
// Run if any component added/removed: AudioSink
// Run if state changed: AudioPlayback
control_volume,
toggle_audio_state.run_if(input_just_pressed(KeyCode::KeyM)),
toggle_playback_state.run_if(input_just_pressed(KeyCode::Space)),
de_spawn_cells
.run_if(not(is_ui_hovered))
.run_if(not(is_panning))
.run_if(input_just_released(MouseButton::Left)),
assert_cell_uniqueness.run_if(any_with_component::<Cell>),
simulation_step.run_if(input_just_pressed(KeyCode::ArrowRight)),
simulation_step
.run_if(in_state(SimulationPlayback::Run))
.run_if(cooldown_secs(SIM_FRAME_DURATION)),
simulation_step.run_if(state_changed::<SimulationPlayback>),
grid_gizmo,
// TODO: Visualize audio play/mute buttons enable/disable
toggle_interactive::<SimulationPlayback>,
// TODO Visualize simulation run/pause buttons enable/disable
toggle_interactive::<AudioPlayback>,
pause_simulation.run_if(in_state(SimulationPlayback::Step)),
update_material,
),
)
// Audio Systems
.add_systems(
Update,
(
control_volume,
update_pitch_map_resource_ui.run_if(resource_changed::<PitchMap>),
reassign_cell_pitch.run_if(resource_changed::<PitchMap>),
update_audio,
update_material,
),
)
// Debugging
.add_systems(
Update,
(
assert_cell_uniqueness.run_if(any_with_component::<Cell>),
grid_gizmo,
update_debug_info_cell_count,
update_debug_info_coordinates,
),
)
// Ui Controllers
.add_systems(
Update,
(
// TODO: Visualize audio play/mute buttons enable/disable
manage_interactive::<SimulationPlayback>,
// TODO Visualize simulation run/pause buttons enable/disable
manage_interactive::<AudioPlayback>,
update_note_interactivity,
),
)
.add_systems(Update, cell_lifecycle.run_if(on_message::<Lifecycle>))
.add_systems(Update, mouse_pan.run_if(on_message::<MouseMotion>))
.add_systems(Update, set_pan_state)
// Input handling
.add_systems(
Update,
(mouse_pan.run_if(on_message::<MouseMotion>), set_pan_state),
)
.run();
}
@ -354,81 +361,29 @@ fn primary_ui() -> impl Scene {
}
Children [
ui_button(UiButton::Power)
on(|_: On<Activate>, mut writer: MessageWriter<AppExit>| {
writer.write(AppExit::Success);
}),
ActionSource<Action>(Action::Quit),
ui_button(UiButton::Pause)
SimulationPlayback::Pause
on(|_: On<Activate>, mut next: ResMut<NextState<SimulationPlayback>>| {
// Set simulation state
next.set(SimulationPlayback::Pause);
}),
ActionSource<SimAction>(SimAction::Toggle),
ui_button(UiButton::Play)
SimulationPlayback::Run
on(|
_: On<Activate>,
mut next: ResMut<NextState<SimulationPlayback>>,
mut last_board_state: ResMut<LastBoardState>,
coordinates: Query<&Coordinates, With<Cell>>
| {
last_board_state.coordinates = coordinates.iter().cloned().collect();
// Set simulation state
next.set(SimulationPlayback::Run);
}),
ActionSource<SimAction>(SimAction::Toggle),
ui_button(UiButton::Step)
on(|_: On<Activate>, mut next: ResMut<NextState<SimulationPlayback>>| {
next.set(SimulationPlayback::Step);
}),
ActionSource<SimAction>(SimAction::Step),
ui_button(UiButton::Reset)
on(|
_: On<Activate>,
last_board_state: Res<LastBoardState>,
mut lifecycle: MessageWriter<Lifecycle>,
cells: Query<Entity, With<Cell>>,
mut commands: Commands,
| {
// Todo: reconcile board instead of despawn/spawning the world
cells.iter().for_each(|e| {
commands.entity(e).despawn();
});
last_board_state.coordinates.iter().for_each(|c| {
lifecycle.write(Lifecycle::Alive(c.clone()));
});
}),
ActionSource<SimAction>(SimAction::Reset),
ui_button(UiButton::Wipe)
on(|_: On<Activate>, cells: Query<Entity, With<Cell>>, mut commands: Commands| {
cells.iter().for_each(|e| {
commands.entity(e).despawn();
});
}),
ActionSource<SimAction>(SimAction::Wipe),
ui_button(UiButton::ZoomOut)
on(|_: On<Activate>, mut projection: Query<&mut Projection>| {
projection.iter_mut().for_each(|mut p| match *p {
Projection::Orthographic(ref mut o) => {
o.scale /= 0.5;
}
_ => todo!(),
});
}),
ActionSource<Action>(Action::ZoomOut),
ui_button(UiButton::ZoomIn)
on(|_: On<Activate>, mut projection: Query<&mut Projection>| {
projection.iter_mut().for_each(|mut p| match *p {
Projection::Orthographic(ref mut o) => {
o.scale *= 0.5;
}
_ => todo!(),
});
}),
ActionSource<Action>(Action::ZoomIn),
ui_button(UiButton::MuteAudio)
AudioPlayback::Mute
on(|_: On<Activate>, mut next: ResMut<NextState<AudioPlayback>>| {
next.set(AudioPlayback::Mute);
}),
ActionSource<AudioAction>(AudioAction::Toggle),
ui_button(UiButton::PlayAudio)
AudioPlayback::Play
on(|_: On<Activate>, mut next: ResMut<NextState<AudioPlayback>>| {
next.set(AudioPlayback::Play);
}),
ActionSource<AudioAction>(AudioAction::Toggle),
]
],
subpane()
@ -983,35 +938,6 @@ fn load_audio_tones(mut pitches: ResMut<Assets<Pitch>>, mut cell_assets: ResMut<
debug_assert_eq!(cell_assets.pitches.iter().len(), 132);
}
// When volume is toggled on/off make the following updates:
// * AudioPlayback state is flipped
fn toggle_audio_state(
mut next_state: ResMut<NextState<AudioPlayback>>,
curr_state: Res<State<AudioPlayback>>,
) {
// Set next audio playback state
next_state.set(
match curr_state.get() {
AudioPlayback::Play => AudioPlayback::Mute,
AudioPlayback::Mute => AudioPlayback::Play,
}
);
}
fn toggle_playback_state(
mut next_state: ResMut<NextState<SimulationPlayback>>,
curr_state: Res<State<SimulationPlayback>>,
) {
// Set next audio playback state
next_state.set(
match curr_state.get() {
SimulationPlayback::Run => SimulationPlayback::Pause,
SimulationPlayback::Step => SimulationPlayback::Pause,
SimulationPlayback::Pause => SimulationPlayback::Run,
}
);
}
fn control_volume(
audio_state: Res<State<AudioPlayback>>,
simulation_state: Res<State<SimulationPlayback>>,
@ -1131,7 +1057,7 @@ fn create_light_theme() -> ThemeProps {
)
}
fn toggle_interactive<T: Component + States + PartialEq>(
fn manage_interactive<T: Component + States + PartialEq>(
q: Query<(Entity, &T)>,
s: Res<State<T>>,
mut commands: Commands,
@ -1259,3 +1185,8 @@ fn update_note_interactivity(
});
}
}
// Sets the sim state to paused
fn pause_simulation(mut next: ResMut<NextState<SimulationPlayback>>) {
next.set(SimulationPlayback::Pause)
}

Loading…
Cancel
Save