Render stats as an overlay in game_of_life

Also allow toggling the grid lines
This commit is contained in:
Héctor Ramón Jiménez 2020-05-03 01:53:45 +02:00
parent c3c5161386
commit 5aaaea7c88

View file

@ -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()
}
}