|
|
|
|
@ -22,10 +22,10 @@ fn main() {
|
|
|
|
|
.init_resource::<Coordinates>()
|
|
|
|
|
.init_resource::<CellAssets>()
|
|
|
|
|
.init_resource::<Panning>()
|
|
|
|
|
.init_state::<SimulationState>()
|
|
|
|
|
.init_state::<SimulationPlayback>()
|
|
|
|
|
.init_state::<AudioPlayback>()
|
|
|
|
|
.insert_resource(GlobalVolume::new(Volume::Linear(1.0)))
|
|
|
|
|
.add_message::<Lifecycle>()
|
|
|
|
|
.add_message::<SimulationState>()
|
|
|
|
|
.add_systems(Startup, setup)
|
|
|
|
|
.add_systems(Startup, load_audio_tones)
|
|
|
|
|
.add_systems(Startup, load_materials)
|
|
|
|
|
@ -36,7 +36,9 @@ fn main() {
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
modulate_volume, // TODO: .run_if(any_component_added::<AudioSink>)
|
|
|
|
|
// Run if any component added/removed: AudioSink
|
|
|
|
|
// Run if state changed: AudioPlayback
|
|
|
|
|
control_volume,
|
|
|
|
|
)
|
|
|
|
|
.add_systems(Update, grid_gizmo)
|
|
|
|
|
.add_systems(
|
|
|
|
|
@ -57,12 +59,12 @@ fn main() {
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
simulation_step
|
|
|
|
|
.run_if(in_state(SimulationState::Run))
|
|
|
|
|
.run_if(in_state(SimulationPlayback::Run))
|
|
|
|
|
.run_if(cooldown_secs(FRAME_DURATION)),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
simulation_step.run_if(on_message::<SimulationState>),
|
|
|
|
|
simulation_step.run_if(state_changed::<SimulationPlayback>),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
@ -71,6 +73,14 @@ fn main() {
|
|
|
|
|
.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)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
toggle_state_entities::<SimulationPlayback>.run_if(state_changed::<SimulationPlayback>),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
toggle_state_entities::<AudioPlayback>.run_if(state_changed::<AudioPlayback>),
|
|
|
|
|
)
|
|
|
|
|
.add_observer(on_add_ui_button)
|
|
|
|
|
.add_observer(on_add_coordinates)
|
|
|
|
|
.run();
|
|
|
|
|
@ -78,6 +88,13 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
const SCALE: f32 = 100.0;
|
|
|
|
|
|
|
|
|
|
#[derive(States, Debug, Default, Hash, PartialEq, Eq, Clone, Component)]
|
|
|
|
|
enum AudioPlayback {
|
|
|
|
|
#[default]
|
|
|
|
|
Mute,
|
|
|
|
|
Play,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Component, PartialEq)]
|
|
|
|
|
#[require(Visibility, Transform)]
|
|
|
|
|
enum UiButton {
|
|
|
|
|
@ -87,6 +104,8 @@ enum UiButton {
|
|
|
|
|
Play,
|
|
|
|
|
ZoomOut,
|
|
|
|
|
ZoomIn,
|
|
|
|
|
PlayAudio,
|
|
|
|
|
MuteAudio,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl UiButton {
|
|
|
|
|
@ -98,6 +117,8 @@ impl UiButton {
|
|
|
|
|
Self::Play => "forward.png",
|
|
|
|
|
Self::ZoomOut => "zoomOut.png",
|
|
|
|
|
Self::ZoomIn => "zoomIn.png",
|
|
|
|
|
Self::PlayAudio => "audioOn.png",
|
|
|
|
|
Self::MuteAudio => "audioOff.png",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -171,32 +192,38 @@ fn setup(mut commands: Commands) {
|
|
|
|
|
))
|
|
|
|
|
.with_children(|parent| {
|
|
|
|
|
parent.spawn(UiButton::Power).observe(shutdown);
|
|
|
|
|
// TODO: Play/Pause Toggle
|
|
|
|
|
parent.spawn(UiButton::Pause).observe(pause_sim);
|
|
|
|
|
parent.spawn(UiButton::Play).observe(play_sim);
|
|
|
|
|
// TODO: Reset button
|
|
|
|
|
parent
|
|
|
|
|
.spawn((UiButton::Pause, SimulationPlayback::Run))
|
|
|
|
|
.observe(pause_sim);
|
|
|
|
|
parent
|
|
|
|
|
.spawn((UiButton::Play, SimulationPlayback::Pause))
|
|
|
|
|
.observe(play_sim);
|
|
|
|
|
parent.spawn(UiButton::Step).observe(step_sim);
|
|
|
|
|
parent.spawn(UiButton::ZoomOut).observe(zoom_out);
|
|
|
|
|
parent.spawn(UiButton::ZoomIn).observe(zoom_in);
|
|
|
|
|
// TODO: Reset button
|
|
|
|
|
// TODO: Mute/Unmute Toggle
|
|
|
|
|
parent
|
|
|
|
|
.spawn((UiButton::MuteAudio, AudioPlayback::Play))
|
|
|
|
|
.observe(mute_audio);
|
|
|
|
|
parent
|
|
|
|
|
.spawn((UiButton::PlayAudio, AudioPlayback::Mute))
|
|
|
|
|
.observe(play_audio);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fn shutdown(_trigger: On<Pointer<Press>>, mut writer: MessageWriter<AppExit>) {
|
|
|
|
|
writer.write(AppExit::Success);
|
|
|
|
|
}
|
|
|
|
|
fn pause_sim(_trigger: On<Pointer<Press>>, mut next: ResMut<NextState<SimulationState>>) {
|
|
|
|
|
next.set(SimulationState::Pause);
|
|
|
|
|
fn play_sim(_trigger: On<Pointer<Press>>, mut next: ResMut<NextState<SimulationPlayback>>) {
|
|
|
|
|
// Set simulation state
|
|
|
|
|
next.set(SimulationPlayback::Run);
|
|
|
|
|
}
|
|
|
|
|
fn step_sim(
|
|
|
|
|
_trigger: On<Pointer<Press>>,
|
|
|
|
|
mut writer: MessageWriter<SimulationState>,
|
|
|
|
|
mut next: ResMut<NextState<SimulationState>>,
|
|
|
|
|
) {
|
|
|
|
|
next.set(SimulationState::Step);
|
|
|
|
|
writer.write(SimulationState::Step);
|
|
|
|
|
fn pause_sim(_trigger: On<Pointer<Press>>, mut next: ResMut<NextState<SimulationPlayback>>) {
|
|
|
|
|
// Set simulation state
|
|
|
|
|
next.set(SimulationPlayback::Pause);
|
|
|
|
|
}
|
|
|
|
|
fn play_sim(_trigger: On<Pointer<Press>>, mut next: ResMut<NextState<SimulationState>>) {
|
|
|
|
|
next.set(SimulationState::Run);
|
|
|
|
|
fn step_sim(_trigger: On<Pointer<Press>>, mut next: ResMut<NextState<SimulationPlayback>>) {
|
|
|
|
|
next.set(SimulationPlayback::Step);
|
|
|
|
|
}
|
|
|
|
|
fn zoom_out(_trigger: On<Pointer<Press>>, mut projection: Query<&mut Projection>) {
|
|
|
|
|
projection.iter_mut().for_each(|mut p| match *p {
|
|
|
|
|
@ -214,6 +241,12 @@ fn setup(mut commands: Commands) {
|
|
|
|
|
_ => todo!(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
fn play_audio(_trigger: On<Pointer<Press>>, mut next: ResMut<NextState<AudioPlayback>>) {
|
|
|
|
|
next.set(AudioPlayback::Play);
|
|
|
|
|
}
|
|
|
|
|
fn mute_audio(_trigger: On<Pointer<Press>>, mut next: ResMut<NextState<AudioPlayback>>) {
|
|
|
|
|
next.set(AudioPlayback::Mute);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, Clone, PartialEq, Hash, Eq, Component, Resource)]
|
|
|
|
|
@ -398,8 +431,8 @@ fn assert_cell_uniqueness(q: Query<(Entity, &Coordinates), With<Cell>>) {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(States, Debug, Default, Hash, PartialEq, Eq, Clone, Message)]
|
|
|
|
|
enum SimulationState {
|
|
|
|
|
#[derive(States, Debug, Default, Hash, PartialEq, Eq, Clone, Component)]
|
|
|
|
|
enum SimulationPlayback {
|
|
|
|
|
#[default]
|
|
|
|
|
Pause,
|
|
|
|
|
Run,
|
|
|
|
|
@ -532,13 +565,13 @@ fn cooldown_secs(input: f32) -> impl FnMut(Local<Timer>, Res<Time>) -> bool + Cl
|
|
|
|
|
// If in Pause state, keep Pauses button light, or blinking or outlined or something.
|
|
|
|
|
fn visualize_ui_state(
|
|
|
|
|
mut query: Query<(&UiButton, &mut Outline)>,
|
|
|
|
|
state: Res<State<SimulationState>>,
|
|
|
|
|
state: Res<State<SimulationPlayback>>,
|
|
|
|
|
) {
|
|
|
|
|
query.iter_mut().for_each(|(uib, mut o)| {
|
|
|
|
|
o.color = match (uib, state.get()) {
|
|
|
|
|
(UiButton::Pause, SimulationState::Pause) => BLACK.into(),
|
|
|
|
|
(UiButton::Step, SimulationState::Step) => BLACK.into(),
|
|
|
|
|
(UiButton::Play, SimulationState::Run) => BLACK.into(),
|
|
|
|
|
(UiButton::Pause, SimulationPlayback::Pause) => BLACK.into(),
|
|
|
|
|
(UiButton::Step, SimulationPlayback::Step) => BLACK.into(),
|
|
|
|
|
(UiButton::Play, SimulationPlayback::Run) => BLACK.into(),
|
|
|
|
|
_ => Color::NONE,
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
@ -620,10 +653,19 @@ fn load_audio_tones(mut pitches: ResMut<Assets<Pitch>>, mut cell_assets: ResMut<
|
|
|
|
|
debug_assert!(cell_assets.pitches.iter().len() == 25);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Why does audio only play when running simulation?
|
|
|
|
|
fn modulate_volume(mut gv: ResMut<GlobalVolume>, active: Query<Entity, With<AudioSink>>) {
|
|
|
|
|
let ratio = 1.0 / active.count() as f32;
|
|
|
|
|
gv.volume = Volume::Linear(ratio);
|
|
|
|
|
fn control_volume(state: ResMut<State<AudioPlayback>>, mut sinks: Query<&mut AudioSink>) {
|
|
|
|
|
match state.get() {
|
|
|
|
|
AudioPlayback::Play => {
|
|
|
|
|
let ratio = 1.0 / sinks.count() as f32;
|
|
|
|
|
let volume = Volume::Linear(ratio);
|
|
|
|
|
sinks.iter_mut().for_each(|mut sink| {
|
|
|
|
|
sink.set_volume(volume);
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
AudioPlayback::Mute => sinks.iter_mut().for_each(|mut sink| {
|
|
|
|
|
sink.set_volume(Volume::SILENT);
|
|
|
|
|
}),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn load_materials(
|
|
|
|
|
@ -710,3 +752,18 @@ fn set_pan_state(
|
|
|
|
|
fn is_panning(panning: Res<Panning>) -> bool {
|
|
|
|
|
panning.0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn toggle_state_entities<S: States + Component>(
|
|
|
|
|
mut query: Query<(&mut Visibility, &S)>,
|
|
|
|
|
state: Res<State<S>>,
|
|
|
|
|
) {
|
|
|
|
|
query.iter_mut().for_each(|(mut v, s)| {
|
|
|
|
|
*v = if state.get() == s {
|
|
|
|
|
Visibility::Inherited
|
|
|
|
|
} else {
|
|
|
|
|
Visibility::Hidden
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Do not spawn cell when clicking UI
|
|
|
|
|
|