|
|
|
@ -35,7 +35,9 @@ fn main() {
|
|
|
|
.add_systems(Update, grid_gizmo)
|
|
|
|
.add_systems(Update, grid_gizmo)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
de_spawn_cells.run_if(input_just_pressed(MouseButton::Left)),
|
|
|
|
de_spawn_cells
|
|
|
|
|
|
|
|
.run_if(not(is_ui_hovered))
|
|
|
|
|
|
|
|
.run_if(input_just_released(MouseButton::Left)),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
@ -43,16 +45,22 @@ fn main() {
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
simulation_step.run_if(input_just_pressed(KeyCode::Space)),
|
|
|
|
simulation_step.run_if(input_just_pressed(KeyCode::ArrowRight)),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
simulation_step.run_if(in_state(SimulationState::Run)).run_if(cooldown_secs(1.0)),
|
|
|
|
simulation_step
|
|
|
|
|
|
|
|
.run_if(in_state(SimulationState::Run))
|
|
|
|
|
|
|
|
.run_if(cooldown_secs(0.5)),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
simulation_step.run_if(on_message::<SimulationState>),
|
|
|
|
simulation_step.run_if(on_message::<SimulationState>),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
.add_systems(
|
|
|
|
|
|
|
|
Update,
|
|
|
|
|
|
|
|
visualize_ui_state.run_if(on_message::<Pointer<Click>>),
|
|
|
|
|
|
|
|
)
|
|
|
|
.add_systems(Update, cell_lifecycle.run_if(on_message::<Lifecycle>))
|
|
|
|
.add_systems(Update, cell_lifecycle.run_if(on_message::<Lifecycle>))
|
|
|
|
.add_observer(on_add_ui_button)
|
|
|
|
.add_observer(on_add_ui_button)
|
|
|
|
.add_observer(on_add_coordinates)
|
|
|
|
.add_observer(on_add_coordinates)
|
|
|
|
@ -61,7 +69,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
|
|
const SCALE: f32 = 100.0;
|
|
|
|
const SCALE: f32 = 100.0;
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Component)]
|
|
|
|
#[derive(Debug, Component, PartialEq)]
|
|
|
|
#[require(Visibility, Transform)]
|
|
|
|
#[require(Visibility, Transform)]
|
|
|
|
enum UiButton {
|
|
|
|
enum UiButton {
|
|
|
|
Power,
|
|
|
|
Power,
|
|
|
|
@ -97,6 +105,17 @@ fn on_add_ui_button(
|
|
|
|
.insert((
|
|
|
|
.insert((
|
|
|
|
Button,
|
|
|
|
Button,
|
|
|
|
Hovered::default(),
|
|
|
|
Hovered::default(),
|
|
|
|
|
|
|
|
Outline {
|
|
|
|
|
|
|
|
width: Val::Px(5.0),
|
|
|
|
|
|
|
|
color: Color::NONE,
|
|
|
|
|
|
|
|
..default()
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
Node {
|
|
|
|
|
|
|
|
top: Val::Px(2.5),
|
|
|
|
|
|
|
|
left: Val::Px(2.5),
|
|
|
|
|
|
|
|
margin: UiRect::all(Val::Px(2.5)),
|
|
|
|
|
|
|
|
..default()
|
|
|
|
|
|
|
|
},
|
|
|
|
BackgroundColor(GRAY.into()),
|
|
|
|
BackgroundColor(GRAY.into()),
|
|
|
|
ImageNode {
|
|
|
|
ImageNode {
|
|
|
|
image: server.load(GREY_TILE),
|
|
|
|
image: server.load(GREY_TILE),
|
|
|
|
@ -105,9 +124,10 @@ fn on_add_ui_button(
|
|
|
|
},
|
|
|
|
},
|
|
|
|
children![(
|
|
|
|
children![(
|
|
|
|
Node {
|
|
|
|
Node {
|
|
|
|
top: Val::Px(0.0),
|
|
|
|
align_self: AlignSelf::Center,
|
|
|
|
left: Val::Px(0.0),
|
|
|
|
justify_self: JustifySelf::Center,
|
|
|
|
height: Val::Px(40.0),
|
|
|
|
width: Val::Percent(100.0),
|
|
|
|
|
|
|
|
height: Val::Percent(100.0),
|
|
|
|
..default()
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ImageNode {
|
|
|
|
ImageNode {
|
|
|
|
@ -155,30 +175,31 @@ fn setup(mut commands: Commands) {
|
|
|
|
fn pause_sim(_trigger: On<Pointer<Press>>, mut next: ResMut<NextState<SimulationState>>) {
|
|
|
|
fn pause_sim(_trigger: On<Pointer<Press>>, mut next: ResMut<NextState<SimulationState>>) {
|
|
|
|
next.set(SimulationState::Pause);
|
|
|
|
next.set(SimulationState::Pause);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn step_sim(_trigger: On<Pointer<Press>>, mut writer: MessageWriter<SimulationState>) {
|
|
|
|
fn step_sim(
|
|
|
|
|
|
|
|
_trigger: On<Pointer<Press>>,
|
|
|
|
|
|
|
|
mut writer: MessageWriter<SimulationState>,
|
|
|
|
|
|
|
|
mut next: ResMut<NextState<SimulationState>>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
next.set(SimulationState::Step);
|
|
|
|
writer.write(SimulationState::Step);
|
|
|
|
writer.write(SimulationState::Step);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn play_sim(_trigger: On<Pointer<Press>>, mut next: ResMut<NextState<SimulationState>>) {
|
|
|
|
fn play_sim(_trigger: On<Pointer<Press>>, mut next: ResMut<NextState<SimulationState>>) {
|
|
|
|
next.set(SimulationState::Run);
|
|
|
|
next.set(SimulationState::Run);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn zoom_out(trigger: On<Pointer<Press>>, mut projection: Query<&mut Projection>) {
|
|
|
|
fn zoom_out(_trigger: On<Pointer<Press>>, mut projection: Query<&mut Projection>) {
|
|
|
|
projection.iter_mut().for_each(|mut p| {
|
|
|
|
projection.iter_mut().for_each(|mut p| match *p {
|
|
|
|
match *p {
|
|
|
|
Projection::Orthographic(ref mut o) => {
|
|
|
|
Projection::Orthographic(ref mut o) => {
|
|
|
|
o.scale /= 0.5;
|
|
|
|
o.scale /= 0.5;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => todo!()
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => todo!(),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn zoom_in(trigger: On<Pointer<Press>>, mut projection: Query<&mut Projection>) {
|
|
|
|
fn zoom_in(_trigger: On<Pointer<Press>>, mut projection: Query<&mut Projection>) {
|
|
|
|
projection.iter_mut().for_each(|mut p| {
|
|
|
|
projection.iter_mut().for_each(|mut p| match *p {
|
|
|
|
match *p {
|
|
|
|
Projection::Orthographic(ref mut o) => {
|
|
|
|
Projection::Orthographic(ref mut o) => {
|
|
|
|
o.scale *= 0.5;
|
|
|
|
o.scale *= 0.5;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => todo!()
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => todo!(),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -315,20 +336,22 @@ fn de_spawn_cells(
|
|
|
|
coordinates: Res<Coordinates>,
|
|
|
|
coordinates: Res<Coordinates>,
|
|
|
|
cells: Query<(Entity, &Coordinates), With<Cell>>,
|
|
|
|
cells: Query<(Entity, &Coordinates), With<Cell>>,
|
|
|
|
mut lifecycle: MessageWriter<Lifecycle>,
|
|
|
|
mut lifecycle: MessageWriter<Lifecycle>,
|
|
|
|
hovered: Query<&Hovered>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
if hovered.iter().all(|Hovered(h)| !h) {
|
|
|
|
if let Some(e) = cells
|
|
|
|
if let Some(e) = cells
|
|
|
|
.iter()
|
|
|
|
.iter()
|
|
|
|
.find_map(|(e, c)| (*coordinates == *c).then_some(e))
|
|
|
|
.find_map(|(e, c)| (*coordinates == *c).then_some(e))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
lifecycle.write(Lifecycle::Dead(e));
|
|
|
|
lifecycle.write(Lifecycle::Dead(e));
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
lifecycle.write(Lifecycle::Alive(coordinates.clone()));
|
|
|
|
lifecycle.write(Lifecycle::Alive(coordinates.clone()));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Run condition if the UI is the active element on the screen
|
|
|
|
|
|
|
|
fn is_ui_hovered(hovered: Query<&Hovered>) -> bool {
|
|
|
|
|
|
|
|
hovered.iter().any(|Hovered(h)| *h)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn assert_cell_uniqueness(q: Query<(Entity, &Coordinates), With<Cell>>) {
|
|
|
|
fn assert_cell_uniqueness(q: Query<(Entity, &Coordinates), With<Cell>>) {
|
|
|
|
q.iter().for_each(|(e1, c1)| {
|
|
|
|
q.iter().for_each(|(e1, c1)| {
|
|
|
|
q.iter().for_each(|(e2, c2)| {
|
|
|
|
q.iter().for_each(|(e2, c2)| {
|
|
|
|
@ -443,10 +466,7 @@ enum Lifecycle {
|
|
|
|
Dead(Entity),
|
|
|
|
Dead(Entity),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn cell_lifecycle(
|
|
|
|
fn cell_lifecycle(mut reader: MessageReader<Lifecycle>, mut commands: Commands) {
|
|
|
|
mut reader: MessageReader<Lifecycle>,
|
|
|
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
reader.read().for_each(|msg| match msg {
|
|
|
|
reader.read().for_each(|msg| match msg {
|
|
|
|
Lifecycle::Alive(c) => {
|
|
|
|
Lifecycle::Alive(c) => {
|
|
|
|
debug!("creating {:?}", c);
|
|
|
|
debug!("creating {:?}", c);
|
|
|
|
@ -460,8 +480,7 @@ fn cell_lifecycle(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Run condition for running a system every `input` seconds
|
|
|
|
// Run condition for running a system every `input` seconds
|
|
|
|
fn cooldown_secs(input: f32) -> impl FnMut(Local<Timer>, Res<Time>) -> bool + Clone
|
|
|
|
fn cooldown_secs(input: f32) -> impl FnMut(Local<Timer>, Res<Time>) -> bool + Clone {
|
|
|
|
{
|
|
|
|
|
|
|
|
move |mut timer: Local<Timer>, time: Res<Time>| {
|
|
|
|
move |mut timer: Local<Timer>, time: Res<Time>| {
|
|
|
|
if *timer == Timer::default() {
|
|
|
|
if *timer == Timer::default() {
|
|
|
|
*timer = Timer::from_seconds(input, TimerMode::Repeating);
|
|
|
|
*timer = Timer::from_seconds(input, TimerMode::Repeating);
|
|
|
|
@ -469,10 +488,25 @@ fn cooldown_secs(input: f32) -> impl FnMut(Local<Timer>, Res<Time>) -> bool + Cl
|
|
|
|
timer.tick(time.delta());
|
|
|
|
timer.tick(time.delta());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
timer.is_finished()
|
|
|
|
timer.is_finished()
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If in Run state, keep Run button light, or blinking or outlined or something.
|
|
|
|
// Visualize active state (run vs pause):
|
|
|
|
// If in Pause state, keep Pauses button light, or blinking or outlined or something.
|
|
|
|
// If in Run state, keep Run button light, or blinking or outlined or something.
|
|
|
|
// TODO: How to get infinite tiling background...
|
|
|
|
// 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>>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
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(),
|
|
|
|
|
|
|
|
_ => Color::NONE,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Scale grid with background?
|
|
|
|
|
|
|
|
// Pan camera: click and drag
|
|
|
|
|