Implement zooming for game_of_life example
This commit is contained in:
parent
08b376c6d7
commit
404122e0b1
1 changed files with 96 additions and 59 deletions
|
|
@ -156,17 +156,17 @@ impl Application for GameOfLife {
|
||||||
|
|
||||||
mod grid {
|
mod grid {
|
||||||
use iced::{
|
use iced::{
|
||||||
canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path},
|
canvas::{self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path},
|
||||||
mouse, Color, Element, Length, Point, Rectangle, Size, Vector,
|
mouse, Color, Element, Length, Point, Rectangle, Size, Vector,
|
||||||
};
|
};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Grid {
|
pub struct Grid {
|
||||||
life: Life,
|
life: Life,
|
||||||
interaction: Interaction,
|
interaction: Interaction,
|
||||||
cache: canvas::Cache,
|
cache: Cache,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
|
scaling: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -174,6 +174,18 @@ mod grid {
|
||||||
Populate(Cell),
|
Populate(Cell),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Grid {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
life: Life::default(),
|
||||||
|
interaction: Interaction::default(),
|
||||||
|
cache: Cache::default(),
|
||||||
|
translation: Vector::default(),
|
||||||
|
scaling: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Grid {
|
impl Grid {
|
||||||
pub fn tick(&mut self) {
|
pub fn tick(&mut self) {
|
||||||
self.life.tick();
|
self.life.tick();
|
||||||
|
|
@ -195,6 +207,27 @@ mod grid {
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn visible_region(&self, size: Size) -> Rectangle {
|
||||||
|
let width = size.width / self.scaling;
|
||||||
|
let height = size.height / self.scaling;
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: -self.translation.x - width / 2.0,
|
||||||
|
y: -self.translation.y - height / 2.0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn project(&self, position: Point, size: Size) -> Point {
|
||||||
|
let region = self.visible_region(size);
|
||||||
|
|
||||||
|
Point::new(
|
||||||
|
position.x / self.scaling + region.x,
|
||||||
|
position.y / self.scaling + region.y,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> canvas::Program<Message> for Grid {
|
impl<'a> canvas::Program<Message> for Grid {
|
||||||
|
|
@ -209,7 +242,7 @@ mod grid {
|
||||||
}
|
}
|
||||||
|
|
||||||
let cursor_position = cursor.position_in(&bounds)?;
|
let cursor_position = cursor.position_in(&bounds)?;
|
||||||
let cell = Cell::at(cursor_position - self.translation);
|
let cell = Cell::at(self.project(cursor_position, bounds.size()));
|
||||||
|
|
||||||
let populate = if self.life.contains(&cell) {
|
let populate = if self.life.contains(&cell) {
|
||||||
None
|
None
|
||||||
|
|
@ -239,8 +272,9 @@ mod grid {
|
||||||
match self.interaction {
|
match self.interaction {
|
||||||
Interaction::Drawing => populate,
|
Interaction::Drawing => populate,
|
||||||
Interaction::Panning { translation, start } => {
|
Interaction::Panning { translation, start } => {
|
||||||
self.translation =
|
self.translation = translation
|
||||||
translation + (cursor_position - start);
|
+ (cursor_position - start)
|
||||||
|
* (1.0 / self.scaling);
|
||||||
|
|
||||||
self.cache.clear();
|
self.cache.clear();
|
||||||
|
|
||||||
|
|
@ -249,62 +283,65 @@ mod grid {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mouse::Event::WheelScrolled { delta } => match delta {
|
||||||
|
mouse::ScrollDelta::Lines { y, .. }
|
||||||
|
| mouse::ScrollDelta::Pixels { y, .. } => {
|
||||||
|
if y > 0.0 && self.scaling < 2.0
|
||||||
|
|| y < 0.0 && self.scaling > 0.25
|
||||||
|
{
|
||||||
|
self.scaling += y / 30.0;
|
||||||
|
|
||||||
|
self.cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry> {
|
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry> {
|
||||||
let cell_size = Size::new(1.0, 1.0);
|
let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
|
||||||
|
|
||||||
let life = self.cache.draw(bounds.size(), |frame| {
|
let life = self.cache.draw(bounds.size(), |frame| {
|
||||||
let background = Path::rectangle(Point::ORIGIN, frame.size());
|
let background = Path::rectangle(Point::ORIGIN, frame.size());
|
||||||
frame.fill(
|
frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B));
|
||||||
&background,
|
|
||||||
Color::from_rgb(
|
|
||||||
0x40 as f32 / 255.0,
|
|
||||||
0x44 as f32 / 255.0,
|
|
||||||
0x4B as f32 / 255.0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
frame.with_save(|frame| {
|
frame.with_save(|frame| {
|
||||||
|
frame.translate(center);
|
||||||
|
frame.scale(self.scaling);
|
||||||
frame.translate(self.translation);
|
frame.translate(self.translation);
|
||||||
frame.scale(Cell::SIZE as f32);
|
frame.scale(Cell::SIZE as f32);
|
||||||
|
|
||||||
let cells = Path::new(|p| {
|
let region = self.visible_region(frame.size());
|
||||||
let region = Rectangle {
|
|
||||||
x: -self.translation.x,
|
|
||||||
y: -self.translation.y,
|
|
||||||
width: frame.width(),
|
|
||||||
height: frame.height(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for cell in Cell::all_visible_in(region) {
|
for cell in self.life.visible_in(region) {
|
||||||
if self.life.contains(&cell) {
|
frame.fill_rectangle(
|
||||||
p.rectangle(
|
Point::new(cell.j as f32, cell.i as f32),
|
||||||
Point::new(cell.j as f32, cell.i as f32),
|
Size::UNIT,
|
||||||
cell_size,
|
Color::WHITE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
frame.fill(&cells, Color::WHITE);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let hovered_cell = {
|
let hovered_cell = {
|
||||||
let mut frame = Frame::new(bounds.size());
|
let mut frame = Frame::new(bounds.size());
|
||||||
|
|
||||||
|
frame.translate(center);
|
||||||
|
frame.scale(self.scaling);
|
||||||
frame.translate(self.translation);
|
frame.translate(self.translation);
|
||||||
frame.scale(Cell::SIZE as f32);
|
frame.scale(Cell::SIZE as f32);
|
||||||
|
|
||||||
if let Some(cursor_position) = cursor.position_in(&bounds) {
|
if let Some(cursor_position) = cursor.position_in(&bounds) {
|
||||||
let cell = Cell::at(cursor_position - self.translation);
|
let cell =
|
||||||
|
Cell::at(self.project(cursor_position, frame.size()));
|
||||||
|
|
||||||
let interaction = Path::rectangle(
|
let interaction = Path::rectangle(
|
||||||
Point::new(cell.j as f32, cell.i as f32),
|
Point::new(cell.j as f32, cell.i as f32),
|
||||||
cell_size,
|
Size::UNIT,
|
||||||
);
|
);
|
||||||
|
|
||||||
frame.fill(
|
frame.fill(
|
||||||
|
|
@ -344,14 +381,6 @@ mod grid {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Life {
|
impl Life {
|
||||||
fn contains(&self, cell: &Cell) -> bool {
|
|
||||||
self.cells.contains(cell)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn populate(&mut self, cell: Cell) {
|
|
||||||
self.cells.insert(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tick(&mut self) {
|
fn tick(&mut self) {
|
||||||
let mut adjacent_life = HashMap::new();
|
let mut adjacent_life = HashMap::new();
|
||||||
|
|
||||||
|
|
@ -377,6 +406,31 @@ mod grid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains(&self, cell: &Cell) -> bool {
|
||||||
|
self.cells.contains(cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn populate(&mut self, cell: Cell) {
|
||||||
|
self.cells.insert(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visible_in(&self, region: Rectangle) -> impl Iterator<Item = &Cell> {
|
||||||
|
let first_row = (region.y / Cell::SIZE as f32).floor() as isize;
|
||||||
|
let first_column = (region.x / Cell::SIZE as f32).floor() as isize;
|
||||||
|
|
||||||
|
let visible_rows =
|
||||||
|
(region.height / Cell::SIZE as f32).ceil() as isize;
|
||||||
|
let visible_columns =
|
||||||
|
(region.width / Cell::SIZE as f32).ceil() as isize;
|
||||||
|
|
||||||
|
let rows = first_row..=first_row + visible_rows;
|
||||||
|
let columns = first_column..=first_column + visible_columns;
|
||||||
|
|
||||||
|
self.cells.iter().filter(move |cell| {
|
||||||
|
rows.contains(&cell.i) && columns.contains(&cell.j)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -410,23 +464,6 @@ mod grid {
|
||||||
fn neighbors(cell: Cell) -> impl Iterator<Item = Cell> {
|
fn neighbors(cell: Cell) -> impl Iterator<Item = Cell> {
|
||||||
Cell::cluster(cell).filter(move |candidate| *candidate != cell)
|
Cell::cluster(cell).filter(move |candidate| *candidate != cell)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_visible_in(region: Rectangle) -> impl Iterator<Item = Cell> {
|
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
let first_row = (region.y / Cell::SIZE as f32).floor() as isize;
|
|
||||||
let first_column = (region.x / Cell::SIZE as f32).floor() as isize;
|
|
||||||
|
|
||||||
let visible_rows =
|
|
||||||
(region.height / Cell::SIZE as f32).ceil() as isize;
|
|
||||||
let visible_columns =
|
|
||||||
(region.width / Cell::SIZE as f32).ceil() as isize;
|
|
||||||
|
|
||||||
let rows = first_row..=first_row + visible_rows;
|
|
||||||
let columns = first_column..=first_column + visible_columns;
|
|
||||||
|
|
||||||
rows.cartesian_product(columns).map(|(i, j)| Cell { i, j })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Interaction {
|
enum Interaction {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue