Render stats as an overlay in game_of_life
Also allow toggling the grid lines
This commit is contained in:
parent
c3c5161386
commit
5aaaea7c88
1 changed files with 101 additions and 80 deletions
|
|
@ -7,8 +7,8 @@ use iced::{
|
||||||
button::{self, Button},
|
button::{self, Button},
|
||||||
executor,
|
executor,
|
||||||
slider::{self, Slider},
|
slider::{self, Slider},
|
||||||
time, Align, Application, Column, Command, Container, Element, Length, Row,
|
time, Align, Application, Checkbox, Column, Command, Container, Element,
|
||||||
Settings, Subscription, Text,
|
Length, Row, Settings, Subscription, Text,
|
||||||
};
|
};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -23,7 +23,6 @@ pub fn main() {
|
||||||
struct GameOfLife {
|
struct GameOfLife {
|
||||||
grid: Grid,
|
grid: Grid,
|
||||||
controls: Controls,
|
controls: Controls,
|
||||||
statistics: Statistics,
|
|
||||||
is_playing: bool,
|
is_playing: bool,
|
||||||
queued_ticks: usize,
|
queued_ticks: usize,
|
||||||
speed: usize,
|
speed: usize,
|
||||||
|
|
@ -34,7 +33,8 @@ struct GameOfLife {
|
||||||
enum Message {
|
enum Message {
|
||||||
Grid(grid::Message),
|
Grid(grid::Message),
|
||||||
Tick(Instant),
|
Tick(Instant),
|
||||||
Toggle,
|
TogglePlayback,
|
||||||
|
ToggleGrid(bool),
|
||||||
Next,
|
Next,
|
||||||
Clear,
|
Clear,
|
||||||
SpeedChanged(f32),
|
SpeedChanged(f32),
|
||||||
|
|
@ -62,9 +62,7 @@ impl Application for GameOfLife {
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Grid(message) => {
|
Message::Grid(message) => {
|
||||||
if let Some(tick_duration) = self.grid.update(message) {
|
self.grid.update(message);
|
||||||
self.statistics.tick_duration = tick_duration;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Message::Tick(_) | Message::Next => {
|
Message::Tick(_) | Message::Next => {
|
||||||
self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
|
self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
|
||||||
|
|
@ -74,15 +72,17 @@ impl Application for GameOfLife {
|
||||||
self.speed = speed;
|
self.speed = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.statistics.last_queued_ticks = self.queued_ticks;
|
|
||||||
self.queued_ticks = 0;
|
self.queued_ticks = 0;
|
||||||
|
|
||||||
return Command::perform(task, Message::Grid);
|
return Command::perform(task, Message::Grid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Toggle => {
|
Message::TogglePlayback => {
|
||||||
self.is_playing = !self.is_playing;
|
self.is_playing = !self.is_playing;
|
||||||
}
|
}
|
||||||
|
Message::ToggleGrid(show_grid_lines) => {
|
||||||
|
self.grid.toggle_lines(show_grid_lines);
|
||||||
|
}
|
||||||
Message::Clear => {
|
Message::Clear => {
|
||||||
self.grid.clear();
|
self.grid.clear();
|
||||||
}
|
}
|
||||||
|
|
@ -110,9 +110,8 @@ impl Application for GameOfLife {
|
||||||
fn view(&mut self) -> Element<Message> {
|
fn view(&mut self) -> Element<Message> {
|
||||||
let selected_speed = self.next_speed.unwrap_or(self.speed);
|
let selected_speed = self.next_speed.unwrap_or(self.speed);
|
||||||
let controls = self.controls.view(
|
let controls = self.controls.view(
|
||||||
&self.grid,
|
|
||||||
&self.statistics,
|
|
||||||
self.is_playing,
|
self.is_playing,
|
||||||
|
self.grid.are_lines_visible(),
|
||||||
selected_speed,
|
selected_speed,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -130,8 +129,11 @@ impl Application for GameOfLife {
|
||||||
|
|
||||||
mod grid {
|
mod grid {
|
||||||
use iced::{
|
use iced::{
|
||||||
canvas::{self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path},
|
canvas::{
|
||||||
mouse, Color, Element, Length, Point, Rectangle, Size, Vector,
|
self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path, Text,
|
||||||
|
},
|
||||||
|
mouse, Color, Element, HorizontalAlignment, Length, Point, Rectangle,
|
||||||
|
Size, Vector, VerticalAlignment,
|
||||||
};
|
};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
@ -145,6 +147,9 @@ mod grid {
|
||||||
grid_cache: Cache,
|
grid_cache: Cache,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
scaling: f32,
|
scaling: f32,
|
||||||
|
show_lines: bool,
|
||||||
|
last_tick_duration: Duration,
|
||||||
|
last_queued_ticks: usize,
|
||||||
version: usize,
|
version: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,6 +177,9 @@ mod grid {
|
||||||
grid_cache: Cache::default(),
|
grid_cache: Cache::default(),
|
||||||
translation: Vector::default(),
|
translation: Vector::default(),
|
||||||
scaling: 1.0,
|
scaling: 1.0,
|
||||||
|
show_lines: true,
|
||||||
|
last_tick_duration: Duration::default(),
|
||||||
|
last_queued_ticks: 0,
|
||||||
version: 0,
|
version: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,10 +189,6 @@ mod grid {
|
||||||
const MIN_SCALING: f32 = 0.1;
|
const MIN_SCALING: f32 = 0.1;
|
||||||
const MAX_SCALING: f32 = 2.0;
|
const MAX_SCALING: f32 = 2.0;
|
||||||
|
|
||||||
pub fn cell_count(&self) -> usize {
|
|
||||||
self.state.cell_count()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(
|
pub fn tick(
|
||||||
&mut self,
|
&mut self,
|
||||||
amount: usize,
|
amount: usize,
|
||||||
|
|
@ -192,6 +196,8 @@ mod grid {
|
||||||
let version = self.version;
|
let version = self.version;
|
||||||
let tick = self.state.tick(amount)?;
|
let tick = self.state.tick(amount)?;
|
||||||
|
|
||||||
|
self.last_queued_ticks = amount;
|
||||||
|
|
||||||
Some(async move {
|
Some(async move {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let result = tick.await;
|
let result = tick.await;
|
||||||
|
|
@ -205,20 +211,11 @@ mod grid {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn update(&mut self, message: Message) {
|
||||||
self.state = State::default();
|
|
||||||
self.version += 1;
|
|
||||||
|
|
||||||
self.life_cache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, message: Message) -> Option<Duration> {
|
|
||||||
match message {
|
match message {
|
||||||
Message::Populate(cell) => {
|
Message::Populate(cell) => {
|
||||||
self.state.populate(cell);
|
self.state.populate(cell);
|
||||||
self.life_cache.clear();
|
self.life_cache.clear();
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
Message::Ticked {
|
Message::Ticked {
|
||||||
result: Ok(life),
|
result: Ok(life),
|
||||||
|
|
@ -228,16 +225,14 @@ mod grid {
|
||||||
self.state.update(life);
|
self.state.update(life);
|
||||||
self.life_cache.clear();
|
self.life_cache.clear();
|
||||||
|
|
||||||
Some(tick_duration)
|
self.last_tick_duration = tick_duration;
|
||||||
}
|
}
|
||||||
Message::Ticked {
|
Message::Ticked {
|
||||||
result: Err(error), ..
|
result: Err(error), ..
|
||||||
} => {
|
} => {
|
||||||
dbg!(error);
|
dbg!(error);
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
Message::Ticked { .. } => None,
|
Message::Ticked { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,7 +243,22 @@ mod grid {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visible_region(&self, size: Size) -> Region {
|
pub fn clear(&mut self) {
|
||||||
|
self.state = State::default();
|
||||||
|
self.version += 1;
|
||||||
|
|
||||||
|
self.life_cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_lines(&mut self, enabled: bool) {
|
||||||
|
self.show_lines = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn are_lines_visible(&self) -> bool {
|
||||||
|
self.show_lines
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visible_region(&self, size: Size) -> Region {
|
||||||
let width = size.width / self.scaling;
|
let width = size.width / self.scaling;
|
||||||
let height = size.height / self.scaling;
|
let height = size.height / self.scaling;
|
||||||
|
|
||||||
|
|
@ -260,7 +270,7 @@ mod grid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn project(&self, position: Point, size: Size) -> Point {
|
fn project(&self, position: Point, size: Size) -> Point {
|
||||||
let region = self.visible_region(size);
|
let region = self.visible_region(size);
|
||||||
|
|
||||||
Point::new(
|
Point::new(
|
||||||
|
|
@ -388,33 +398,64 @@ mod grid {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let hovered_cell = {
|
let overlay = {
|
||||||
let mut frame = Frame::new(bounds.size());
|
let mut frame = Frame::new(bounds.size());
|
||||||
|
|
||||||
frame.translate(center);
|
let hovered_cell =
|
||||||
frame.scale(self.scaling);
|
cursor.position_in(&bounds).map(|position| {
|
||||||
frame.translate(self.translation);
|
Cell::at(self.project(position, frame.size()))
|
||||||
frame.scale(Cell::SIZE as f32);
|
});
|
||||||
|
|
||||||
if let Some(cursor_position) = cursor.position_in(&bounds) {
|
if let Some(cell) = hovered_cell {
|
||||||
let cell =
|
frame.with_save(|frame| {
|
||||||
Cell::at(self.project(cursor_position, frame.size()));
|
frame.translate(center);
|
||||||
|
frame.scale(self.scaling);
|
||||||
|
frame.translate(self.translation);
|
||||||
|
frame.scale(Cell::SIZE as f32);
|
||||||
|
|
||||||
frame.fill_rectangle(
|
frame.fill_rectangle(
|
||||||
Point::new(cell.j as f32, cell.i as f32),
|
Point::new(cell.j as f32, cell.i as f32),
|
||||||
Size::UNIT,
|
Size::UNIT,
|
||||||
Color {
|
Color {
|
||||||
a: 0.5,
|
a: 0.5,
|
||||||
..Color::BLACK
|
..Color::BLACK
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let text = Text {
|
||||||
|
color: Color::WHITE,
|
||||||
|
size: 14.0,
|
||||||
|
position: Point::new(frame.width(), frame.height()),
|
||||||
|
horizontal_alignment: HorizontalAlignment::Right,
|
||||||
|
vertical_alignment: VerticalAlignment::Bottom,
|
||||||
|
..Text::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(cell) = hovered_cell {
|
||||||
|
frame.fill_text(Text {
|
||||||
|
content: format!("({}, {})", cell.i, cell.j),
|
||||||
|
position: text.position - Vector::new(0.0, 16.0),
|
||||||
|
..text
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.fill_text(Text {
|
||||||
|
content: format!(
|
||||||
|
"{} cells @ {:?} ({})",
|
||||||
|
self.state.cell_count(),
|
||||||
|
self.last_tick_duration,
|
||||||
|
self.last_queued_ticks
|
||||||
|
),
|
||||||
|
..text
|
||||||
|
});
|
||||||
|
|
||||||
frame.into_geometry()
|
frame.into_geometry()
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.scaling < 0.2 {
|
if self.scaling < 0.2 || !self.show_lines {
|
||||||
vec![life, hovered_cell]
|
vec![life, overlay]
|
||||||
} else {
|
} else {
|
||||||
let grid = self.grid_cache.draw(bounds.size(), |frame| {
|
let grid = self.grid_cache.draw(bounds.size(), |frame| {
|
||||||
frame.translate(center);
|
frame.translate(center);
|
||||||
|
|
@ -449,7 +490,7 @@ mod grid {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![life, grid, hovered_cell]
|
vec![life, grid, overlay]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -677,9 +718,8 @@ struct Controls {
|
||||||
impl Controls {
|
impl Controls {
|
||||||
fn view<'a>(
|
fn view<'a>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
grid: &Grid,
|
|
||||||
statistics: &'a Statistics,
|
|
||||||
is_playing: bool,
|
is_playing: bool,
|
||||||
|
is_grid_enabled: bool,
|
||||||
speed: usize,
|
speed: usize,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let playback_controls = Row::new()
|
let playback_controls = Row::new()
|
||||||
|
|
@ -689,7 +729,7 @@ impl Controls {
|
||||||
&mut self.toggle_button,
|
&mut self.toggle_button,
|
||||||
Text::new(if is_playing { "Pause" } else { "Play" }),
|
Text::new(if is_playing { "Pause" } else { "Play" }),
|
||||||
)
|
)
|
||||||
.on_press(Message::Toggle)
|
.on_press(Message::TogglePlayback)
|
||||||
.style(style::Button),
|
.style(style::Button),
|
||||||
)
|
)
|
||||||
.push(
|
.push(
|
||||||
|
|
@ -719,7 +759,12 @@ impl Controls {
|
||||||
.align_items(Align::Center)
|
.align_items(Align::Center)
|
||||||
.push(playback_controls)
|
.push(playback_controls)
|
||||||
.push(speed_controls)
|
.push(speed_controls)
|
||||||
.push(statistics.view(grid))
|
.push(
|
||||||
|
Checkbox::new(is_grid_enabled, "Grid", Message::ToggleGrid)
|
||||||
|
.size(16)
|
||||||
|
.spacing(5)
|
||||||
|
.text_size(16),
|
||||||
|
)
|
||||||
.push(
|
.push(
|
||||||
Button::new(&mut self.clear_button, Text::new("Clear"))
|
Button::new(&mut self.clear_button, Text::new("Clear"))
|
||||||
.on_press(Message::Clear)
|
.on_press(Message::Clear)
|
||||||
|
|
@ -728,27 +773,3 @@ impl Controls {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Statistics {
|
|
||||||
tick_duration: Duration,
|
|
||||||
last_queued_ticks: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Statistics {
|
|
||||||
fn view(&self, grid: &Grid) -> Element<Message> {
|
|
||||||
Column::new()
|
|
||||||
.width(Length::Units(150))
|
|
||||||
.align_items(Align::Center)
|
|
||||||
.spacing(2)
|
|
||||||
.push(Text::new(format!("{} cells", grid.cell_count())).size(14))
|
|
||||||
.push(
|
|
||||||
Text::new(format!(
|
|
||||||
"{:?} ({})",
|
|
||||||
self.tick_duration, self.last_queued_ticks
|
|
||||||
))
|
|
||||||
.size(14),
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue