--- /dev/null
+use iced::executor;
+use iced::theme::{self, Theme};
+use iced::time;
+use iced::widget::{button, column, container, row, slider, text};
+use iced::{Alignment, Application, Command, Element, Length, Subscription};
+use std::time::{Duration, Instant};
+
+mod map;
+
+use map::*;
+
+#[derive(Default)]
+pub struct CellSeq {
+ grid: Map,
+ is_playing: bool,
+ queued_ticks: usize,
+ speed: usize,
+ next_speed: Option<usize>,
+ version: usize,
+}
+
+#[derive(Debug, Clone)]
+pub enum Message {
+ Grid(map::Message, usize),
+ Tick(Instant),
+ TogglePlayback,
+ Next,
+ Clear,
+ SpeedChanged(f32),
+}
+
+impl Application for CellSeq {
+ type Message = Message;
+ type Theme = Theme;
+ type Executor = executor::Default;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Self, Command<Message>) {
+ (
+ Self {
+ speed: 5,
+ ..Self::default()
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("Game of Life - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::Grid(message, version) => {
+ if version == self.version {
+ self.grid.update(message);
+ }
+ }
+ Message::Tick(_) | Message::Next => {
+ self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
+
+ if let Some(task) = self.grid.tick(self.queued_ticks) {
+ if let Some(speed) = self.next_speed.take() {
+ self.speed = speed;
+ }
+
+ self.queued_ticks = 0;
+
+ let version = self.version;
+
+ return Command::perform(task, move |message| Message::Grid(message, version));
+ }
+ }
+ Message::TogglePlayback => {
+ self.is_playing = !self.is_playing;
+ }
+ Message::Clear => {
+ self.grid.clear();
+ self.version += 1;
+ }
+ Message::SpeedChanged(speed) => {
+ if self.is_playing {
+ self.next_speed = Some(speed.round() as usize);
+ } else {
+ self.speed = speed.round() as usize;
+ }
+ }
+ }
+
+ Command::none()
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ if self.is_playing {
+ time::every(Duration::from_millis(1000 / self.speed as u64)).map(Message::Tick)
+ } else {
+ Subscription::none()
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ let version = self.version;
+ let selected_speed = self.next_speed.unwrap_or(self.speed);
+ let controls = view_controls(self.is_playing, selected_speed);
+
+ let content = column![
+ self.grid
+ .view()
+ .map(move |message| Message::Grid(message, version)),
+ controls,
+ ];
+
+ container(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .into()
+ }
+
+ fn theme(&self) -> Theme {
+ Theme::Dark
+ }
+}
+
+fn view_controls<'a>(is_playing: bool, speed: usize) -> Element<'a, Message> {
+ let playback_controls = row![
+ button(if is_playing { "Pause" } else { "Play" }).on_press(Message::TogglePlayback),
+ button("Next")
+ .on_press(Message::Next)
+ .style(theme::Button::Secondary),
+ ]
+ .spacing(10);
+
+ let speed_controls = row![
+ slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
+ text(format!("x{speed}")).size(16),
+ ]
+ .width(Length::Fill)
+ .align_items(Alignment::Center)
+ .spacing(10);
+
+ row![
+ playback_controls,
+ speed_controls,
+ button("Clear")
+ .on_press(Message::Clear)
+ .style(theme::Button::Destructive),
+ ]
+ .padding(10)
+ .spacing(20)
+ .align_items(Alignment::Center)
+ .into()
+}
-//! This example showcases an interactive version of the Game of Life, invented
-//! by John Conway. It leverages a `Canvas` together with other widgets.
-mod grid;
+use cellseq::*;
-use grid::Map;
-
-use iced::executor;
-use iced::theme::{self, Theme};
-use iced::time;
-use iced::widget::{button, checkbox, column, container, row, slider, text};
-use iced::window;
-use iced::{Alignment, Application, Command, Element, Length, Settings, Subscription};
-use std::time::{Duration, Instant};
+use iced::{window, Application, Settings};
pub fn main() -> iced::Result {
CellSeq::run(Settings {
..Settings::default()
})
}
-
-#[derive(Default)]
-struct CellSeq {
- grid: Map,
- is_playing: bool,
- queued_ticks: usize,
- speed: usize,
- next_speed: Option<usize>,
- version: usize,
-}
-
-#[derive(Debug, Clone)]
-enum Message {
- Grid(grid::Message, usize),
- Tick(Instant),
- TogglePlayback,
- ToggleGrid(bool),
- Next,
- Clear,
- SpeedChanged(f32),
-}
-
-impl Application for CellSeq {
- type Message = Message;
- type Theme = Theme;
- type Executor = executor::Default;
- type Flags = ();
-
- fn new(_flags: ()) -> (Self, Command<Message>) {
- (
- Self {
- speed: 5,
- ..Self::default()
- },
- Command::none(),
- )
- }
-
- fn title(&self) -> String {
- String::from("Game of Life - Iced")
- }
-
- fn update(&mut self, message: Message) -> Command<Message> {
- match message {
- Message::Grid(message, version) => {
- if version == self.version {
- self.grid.update(message);
- }
- }
- Message::Tick(_) | Message::Next => {
- self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
-
- if let Some(task) = self.grid.tick(self.queued_ticks) {
- if let Some(speed) = self.next_speed.take() {
- self.speed = speed;
- }
-
- self.queued_ticks = 0;
-
- let version = self.version;
-
- return Command::perform(task, move |message| Message::Grid(message, version));
- }
- }
- Message::TogglePlayback => {
- self.is_playing = !self.is_playing;
- }
- Message::ToggleGrid(show_grid_lines) => {
- self.grid.toggle_lines(show_grid_lines);
- }
- Message::Clear => {
- self.grid.clear();
- self.version += 1;
- }
- Message::SpeedChanged(speed) => {
- if self.is_playing {
- self.next_speed = Some(speed.round() as usize);
- } else {
- self.speed = speed.round() as usize;
- }
- }
- }
-
- Command::none()
- }
-
- fn subscription(&self) -> Subscription<Message> {
- if self.is_playing {
- time::every(Duration::from_millis(1000 / self.speed as u64)).map(Message::Tick)
- } else {
- Subscription::none()
- }
- }
-
- fn view(&self) -> Element<Message> {
- let version = self.version;
- let selected_speed = self.next_speed.unwrap_or(self.speed);
- let controls = view_controls(
- self.is_playing,
- self.grid.are_lines_visible(),
- selected_speed,
- );
-
- let content = column![
- self.grid
- .view()
- .map(move |message| Message::Grid(message, version)),
- controls,
- ];
-
- container(content)
- .width(Length::Fill)
- .height(Length::Fill)
- .into()
- }
-
- fn theme(&self) -> Theme {
- Theme::Dark
- }
-}
-
-fn view_controls<'a>(
- is_playing: bool,
- is_grid_enabled: bool,
- speed: usize,
-) -> Element<'a, Message> {
- let playback_controls = row![
- button(if is_playing { "Pause" } else { "Play" }).on_press(Message::TogglePlayback),
- button("Next")
- .on_press(Message::Next)
- .style(theme::Button::Secondary),
- ]
- .spacing(10);
-
- let speed_controls = row![
- slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
- text(format!("x{speed}")).size(16),
- ]
- .width(Length::Fill)
- .align_items(Alignment::Center)
- .spacing(10);
-
- row![
- playback_controls,
- speed_controls,
- checkbox("Grid", is_grid_enabled, Message::ToggleGrid)
- .size(16)
- .spacing(5)
- .text_size(16),
- button("Clear")
- .on_press(Message::Clear)
- .style(theme::Button::Destructive),
- ]
- .padding(10)
- .spacing(20)
- .align_items(Alignment::Center)
- .into()
-}
+++ /dev/null
-#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
-pub enum Preset {
- Custom,
- #[default]
- Xkcd,
- Glider,
- SmallExploder,
- Exploder,
- TenCellRow,
- LightweightSpaceship,
- Tumbler,
- GliderGun,
- Acorn,
-}
-
-pub static ALL: &[Preset] = &[
- Preset::Custom,
- Preset::Xkcd,
- Preset::Glider,
- Preset::SmallExploder,
- Preset::Exploder,
- Preset::TenCellRow,
- Preset::LightweightSpaceship,
- Preset::Tumbler,
- Preset::GliderGun,
- Preset::Acorn,
-];
-
-impl Preset {
- pub fn life(self) -> Vec<(isize, isize)> {
- #[rustfmt::skip]
- let cells = match self {
- Preset::Custom => vec![],
- Preset::Xkcd => vec![
- " xxx ",
- " x x ",
- " x x ",
- " x ",
- "x xxx ",
- " x x x ",
- " x x",
- " x x ",
- " x x ",
- ],
- Preset::Glider => vec![
- " x ",
- " x",
- "xxx"
- ],
- Preset::SmallExploder => vec![
- " x ",
- "xxx",
- "x x",
- " x ",
- ],
- Preset::Exploder => vec![
- "x x x",
- "x x",
- "x x",
- "x x",
- "x x x",
- ],
- Preset::TenCellRow => vec![
- "xxxxxxxxxx",
- ],
- Preset::LightweightSpaceship => vec![
- " xxxxx",
- "x x",
- " x",
- "x x ",
- ],
- Preset::Tumbler => vec![
- " xx xx ",
- " xx xx ",
- " x x ",
- "x x x x",
- "x x x x",
- "xx xx",
- ],
- Preset::GliderGun => vec![
- " x ",
- " x x ",
- " xx xx xx",
- " x x xx xx",
- "xx x x xx ",
- "xx x x xx x x ",
- " x x x ",
- " x x ",
- " xx ",
- ],
- Preset::Acorn => vec![
- " x ",
- " x ",
- "xx xxx",
- ],
- };
-
- let start_row = -(cells.len() as isize / 2);
-
- cells
- .into_iter()
- .enumerate()
- .flat_map(|(i, cells)| {
- let start_column = -(cells.len() as isize / 2);
-
- cells
- .chars()
- .enumerate()
- .filter(|(_, c)| !c.is_whitespace())
- .map(move |(j, _)| {
- (start_row + i as isize, start_column + j as isize)
- })
- })
- .collect()
- }
-}
-
-impl std::fmt::Display for Preset {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(
- f,
- "{}",
- match self {
- Preset::Custom => "Custom",
- Preset::Xkcd => "xkcd #2293",
- Preset::Glider => "Glider",
- Preset::SmallExploder => "Small Exploder",
- Preset::Exploder => "Exploder",
- Preset::TenCellRow => "10 Cell Row",
- Preset::LightweightSpaceship => "Lightweight spaceship",
- Preset::Tumbler => "Tumbler",
- Preset::GliderGun => "Gosper Glider Gun",
- Preset::Acorn => "Acorn",
- }
- )
- }
-}