summaryrefslogtreecommitdiffstats
path: root/examples/game_of_life
diff options
context:
space:
mode:
Diffstat (limited to 'examples/game_of_life')
-rw-r--r--examples/game_of_life/src/main.rs289
-rw-r--r--examples/game_of_life/src/preset.rs10
-rw-r--r--examples/game_of_life/src/style.rs189
3 files changed, 152 insertions, 336 deletions
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index ab8b80e4..a2030275 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -1,20 +1,20 @@
//! This example showcases an interactive version of the Game of Life, invented
//! by John Conway. It leverages a `Canvas` together with other widgets.
mod preset;
-mod style;
use grid::Grid;
-use iced::button::{self, Button};
+use preset::Preset;
+
use iced::executor;
-use iced::pick_list::{self, PickList};
-use iced::slider::{self, Slider};
+use iced::theme::{self, Theme};
use iced::time;
+use iced::widget::{
+ button, checkbox, column, container, pick_list, row, slider, text,
+};
use iced::window;
use iced::{
- Alignment, Application, Checkbox, Column, Command, Container, Element,
- Length, Row, Settings, Subscription, Text,
+ Alignment, Application, Command, Element, Length, Settings, Subscription,
};
-use preset::Preset;
use std::time::{Duration, Instant};
pub fn main() -> iced::Result {
@@ -33,7 +33,6 @@ pub fn main() -> iced::Result {
#[derive(Default)]
struct GameOfLife {
grid: Grid,
- controls: Controls,
is_playing: bool,
queued_ticks: usize,
speed: usize,
@@ -55,6 +54,7 @@ enum Message {
impl Application for GameOfLife {
type Message = Message;
+ type Theme = Theme;
type Executor = executor::Default;
type Flags = ();
@@ -131,39 +131,87 @@ impl Application for GameOfLife {
}
}
- fn view(&mut self) -> Element<Message> {
+ fn view(&self) -> Element<Message> {
let version = self.version;
let selected_speed = self.next_speed.unwrap_or(self.speed);
- let controls = self.controls.view(
+ let controls = view_controls(
self.is_playing,
self.grid.are_lines_visible(),
selected_speed,
self.grid.preset(),
);
- let content = Column::new()
- .push(
- self.grid
- .view()
- .map(move |message| Message::Grid(message, version)),
- )
- .push(controls);
+ let content = column![
+ self.grid
+ .view()
+ .map(move |message| Message::Grid(message, version)),
+ controls
+ ];
- Container::new(content)
+ container(content)
.width(Length::Fill)
.height(Length::Fill)
- .style(style::Container)
.into()
}
+
+ fn theme(&self) -> Theme {
+ Theme::Dark
+ }
+}
+
+fn view_controls<'a>(
+ is_playing: bool,
+ is_grid_enabled: bool,
+ speed: usize,
+ preset: Preset,
+) -> 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),
+ pick_list(preset::ALL, Some(preset), Message::PresetPicked)
+ .padding(8)
+ .text_size(16),
+ button("Clear")
+ .on_press(Message::Clear)
+ .style(theme::Button::Destructive),
+ ]
+ .padding(10)
+ .spacing(20)
+ .align_items(Alignment::Center)
+ .into()
}
mod grid {
use crate::Preset;
+ use iced::widget::canvas;
+ use iced::widget::canvas::event::{self, Event};
+ use iced::widget::canvas::{
+ Cache, Canvas, Cursor, Frame, Geometry, Path, Text,
+ };
use iced::{
- alignment,
- canvas::event::{self, Event},
- canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text},
- mouse, Color, Element, Length, Point, Rectangle, Size, Vector,
+ alignment, mouse, Color, Element, Length, Point, Rectangle, Size,
+ Theme, Vector,
};
use rustc_hash::{FxHashMap, FxHashSet};
use std::future::Future;
@@ -173,7 +221,6 @@ mod grid {
pub struct Grid {
state: State,
preset: Preset,
- interaction: Interaction,
life_cache: Cache,
grid_cache: Cache,
translation: Vector,
@@ -187,6 +234,8 @@ mod grid {
pub enum Message {
Populate(Cell),
Unpopulate(Cell),
+ Translated(Vector),
+ Scaled(f32, Option<Vector>),
Ticked {
result: Result<Life, TickError>,
tick_duration: Duration,
@@ -218,7 +267,6 @@ mod grid {
.collect(),
),
preset,
- interaction: Interaction::None,
life_cache: Cache::default(),
grid_cache: Cache::default(),
translation: Vector::default(),
@@ -263,6 +311,22 @@ mod grid {
self.preset = Preset::Custom;
}
+ Message::Translated(translation) => {
+ self.translation = translation;
+
+ self.life_cache.clear();
+ self.grid_cache.clear();
+ }
+ Message::Scaled(scaling, translation) => {
+ self.scaling = scaling;
+
+ if let Some(translation) = translation {
+ self.translation = translation;
+ }
+
+ self.life_cache.clear();
+ self.grid_cache.clear();
+ }
Message::Ticked {
result: Ok(life),
tick_duration,
@@ -280,7 +344,7 @@ mod grid {
}
}
- pub fn view<'a>(&'a mut self) -> Element<'a, Message> {
+ pub fn view(&self) -> Element<Message> {
Canvas::new(self)
.width(Length::Fill)
.height(Length::Fill)
@@ -328,15 +392,18 @@ mod grid {
}
}
- impl<'a> canvas::Program<Message> for Grid {
+ impl canvas::Program<Message> for Grid {
+ type State = Interaction;
+
fn update(
- &mut self,
+ &self,
+ interaction: &mut Interaction,
event: Event,
bounds: Rectangle,
cursor: Cursor,
) -> (event::Status, Option<Message>) {
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
- self.interaction = Interaction::None;
+ *interaction = Interaction::None;
}
let cursor_position =
@@ -360,7 +427,7 @@ mod grid {
mouse::Event::ButtonPressed(button) => {
let message = match button {
mouse::Button::Left => {
- self.interaction = if is_populated {
+ *interaction = if is_populated {
Interaction::Erasing
} else {
Interaction::Drawing
@@ -369,7 +436,7 @@ mod grid {
populate.or(unpopulate)
}
mouse::Button::Right => {
- self.interaction = Interaction::Panning {
+ *interaction = Interaction::Panning {
translation: self.translation,
start: cursor_position,
};
@@ -382,23 +449,20 @@ mod grid {
(event::Status::Captured, message)
}
mouse::Event::CursorMoved { .. } => {
- let message = match self.interaction {
+ let message = match *interaction {
Interaction::Drawing => populate,
Interaction::Erasing => unpopulate,
Interaction::Panning { translation, start } => {
- self.translation = translation
- + (cursor_position - start)
- * (1.0 / self.scaling);
-
- self.life_cache.clear();
- self.grid_cache.clear();
-
- None
+ Some(Message::Translated(
+ translation
+ + (cursor_position - start)
+ * (1.0 / self.scaling),
+ ))
}
_ => None,
};
- let event_status = match self.interaction {
+ let event_status = match interaction {
Interaction::None => event::Status::Ignored,
_ => event::Status::Captured,
};
@@ -413,30 +477,38 @@ mod grid {
{
let old_scaling = self.scaling;
- self.scaling = (self.scaling
- * (1.0 + y / 30.0))
+ let scaling = (self.scaling * (1.0 + y / 30.0))
.max(Self::MIN_SCALING)
.min(Self::MAX_SCALING);
- if let Some(cursor_to_center) =
- cursor.position_from(bounds.center())
- {
- let factor = self.scaling - old_scaling;
-
- self.translation = self.translation
- - Vector::new(
- cursor_to_center.x * factor
- / (old_scaling * old_scaling),
- cursor_to_center.y * factor
- / (old_scaling * old_scaling),
- );
- }
-
- self.life_cache.clear();
- self.grid_cache.clear();
+ let translation =
+ if let Some(cursor_to_center) =
+ cursor.position_from(bounds.center())
+ {
+ let factor = scaling - old_scaling;
+
+ Some(
+ self.translation
+ - Vector::new(
+ cursor_to_center.x * factor
+ / (old_scaling
+ * old_scaling),
+ cursor_to_center.y * factor
+ / (old_scaling
+ * old_scaling),
+ ),
+ )
+ } else {
+ None
+ };
+
+ (
+ event::Status::Captured,
+ Some(Message::Scaled(scaling, translation)),
+ )
+ } else {
+ (event::Status::Captured, None)
}
-
- (event::Status::Captured, None)
}
},
_ => (event::Status::Ignored, None),
@@ -445,7 +517,13 @@ mod grid {
}
}
- fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry> {
+ fn draw(
+ &self,
+ _interaction: &Interaction,
+ _theme: &Theme,
+ bounds: Rectangle,
+ cursor: Cursor,
+ ) -> Vec<Geometry> {
let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
let life = self.life_cache.draw(bounds.size(), |frame| {
@@ -571,10 +649,11 @@ mod grid {
fn mouse_interaction(
&self,
+ interaction: &Interaction,
bounds: Rectangle,
cursor: Cursor,
) -> mouse::Interaction {
- match self.interaction {
+ match interaction {
Interaction::Drawing => mouse::Interaction::Crosshair,
Interaction::Erasing => mouse::Interaction::Crosshair,
Interaction::Panning { .. } => mouse::Interaction::Grabbing,
@@ -803,90 +882,16 @@ mod grid {
}
}
- enum Interaction {
+ pub enum Interaction {
None,
Drawing,
Erasing,
Panning { translation: Vector, start: Point },
}
-}
-
-#[derive(Default)]
-struct Controls {
- toggle_button: button::State,
- next_button: button::State,
- clear_button: button::State,
- speed_slider: slider::State,
- preset_list: pick_list::State<Preset>,
-}
-impl Controls {
- fn view<'a>(
- &'a mut self,
- is_playing: bool,
- is_grid_enabled: bool,
- speed: usize,
- preset: Preset,
- ) -> Element<'a, Message> {
- let playback_controls = Row::new()
- .spacing(10)
- .push(
- Button::new(
- &mut self.toggle_button,
- Text::new(if is_playing { "Pause" } else { "Play" }),
- )
- .on_press(Message::TogglePlayback)
- .style(style::Button),
- )
- .push(
- Button::new(&mut self.next_button, Text::new("Next"))
- .on_press(Message::Next)
- .style(style::Button),
- );
-
- let speed_controls = Row::new()
- .width(Length::Fill)
- .align_items(Alignment::Center)
- .spacing(10)
- .push(
- Slider::new(
- &mut self.speed_slider,
- 1.0..=1000.0,
- speed as f32,
- Message::SpeedChanged,
- )
- .style(style::Slider),
- )
- .push(Text::new(format!("x{}", speed)).size(16));
-
- Row::new()
- .padding(10)
- .spacing(20)
- .align_items(Alignment::Center)
- .push(playback_controls)
- .push(speed_controls)
- .push(
- Checkbox::new(is_grid_enabled, "Grid", Message::ToggleGrid)
- .size(16)
- .spacing(5)
- .text_size(16),
- )
- .push(
- PickList::new(
- &mut self.preset_list,
- preset::ALL,
- Some(preset),
- Message::PresetPicked,
- )
- .padding(8)
- .text_size(16)
- .style(style::PickList),
- )
- .push(
- Button::new(&mut self.clear_button, Text::new("Clear"))
- .on_press(Message::Clear)
- .style(style::Clear),
- )
- .into()
+ impl Default for Interaction {
+ fn default() -> Self {
+ Self::None
+ }
}
}
diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs
index 05157b6a..964b9120 100644
--- a/examples/game_of_life/src/preset.rs
+++ b/examples/game_of_life/src/preset.rs
@@ -1,7 +1,7 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Preset {
Custom,
- XKCD,
+ Xkcd,
Glider,
SmallExploder,
Exploder,
@@ -14,7 +14,7 @@ pub enum Preset {
pub static ALL: &[Preset] = &[
Preset::Custom,
- Preset::XKCD,
+ Preset::Xkcd,
Preset::Glider,
Preset::SmallExploder,
Preset::Exploder,
@@ -30,7 +30,7 @@ impl Preset {
#[rustfmt::skip]
let cells = match self {
Preset::Custom => vec![],
- Preset::XKCD => vec![
+ Preset::Xkcd => vec![
" xxx ",
" x x ",
" x x ",
@@ -116,7 +116,7 @@ impl Preset {
impl Default for Preset {
fn default() -> Preset {
- Preset::XKCD
+ Preset::Xkcd
}
}
@@ -127,7 +127,7 @@ impl std::fmt::Display for Preset {
"{}",
match self {
Preset::Custom => "Custom",
- Preset::XKCD => "xkcd #2293",
+ Preset::Xkcd => "xkcd #2293",
Preset::Glider => "Glider",
Preset::SmallExploder => "Small Exploder",
Preset::Exploder => "Exploder",
diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs
deleted file mode 100644
index be9a0e96..00000000
--- a/examples/game_of_life/src/style.rs
+++ /dev/null
@@ -1,189 +0,0 @@
-use iced::{button, container, pick_list, slider, Background, Color};
-
-const ACTIVE: Color = Color::from_rgb(
- 0x72 as f32 / 255.0,
- 0x89 as f32 / 255.0,
- 0xDA as f32 / 255.0,
-);
-
-const DESTRUCTIVE: Color = Color::from_rgb(
- 0xC0 as f32 / 255.0,
- 0x47 as f32 / 255.0,
- 0x47 as f32 / 255.0,
-);
-
-const HOVERED: Color = Color::from_rgb(
- 0x67 as f32 / 255.0,
- 0x7B as f32 / 255.0,
- 0xC4 as f32 / 255.0,
-);
-
-const BACKGROUND: Color = Color::from_rgb(
- 0x2F as f32 / 255.0,
- 0x31 as f32 / 255.0,
- 0x36 as f32 / 255.0,
-);
-
-pub struct Container;
-
-impl container::StyleSheet for Container {
- fn style(&self) -> container::Style {
- container::Style {
- background: Some(Background::Color(Color::from_rgb8(
- 0x36, 0x39, 0x3F,
- ))),
- text_color: Some(Color::WHITE),
- ..container::Style::default()
- }
- }
-}
-
-pub struct Button;
-
-impl button::StyleSheet for Button {
- fn active(&self) -> button::Style {
- button::Style {
- background: Some(Background::Color(ACTIVE)),
- border_radius: 3.0,
- text_color: Color::WHITE,
- ..button::Style::default()
- }
- }
-
- fn hovered(&self) -> button::Style {
- button::Style {
- background: Some(Background::Color(HOVERED)),
- text_color: Color::WHITE,
- ..self.active()
- }
- }
-
- fn pressed(&self) -> button::Style {
- button::Style {
- border_width: 1.0,
- border_color: Color::WHITE,
- ..self.hovered()
- }
- }
-}
-
-pub struct Clear;
-
-impl button::StyleSheet for Clear {
- fn active(&self) -> button::Style {
- button::Style {
- background: Some(Background::Color(DESTRUCTIVE)),
- border_radius: 3.0,
- text_color: Color::WHITE,
- ..button::Style::default()
- }
- }
-
- fn hovered(&self) -> button::Style {
- button::Style {
- background: Some(Background::Color(Color {
- a: 0.5,
- ..DESTRUCTIVE
- })),
- text_color: Color::WHITE,
- ..self.active()
- }
- }
-
- fn pressed(&self) -> button::Style {
- button::Style {
- border_width: 1.0,
- border_color: Color::WHITE,
- ..self.hovered()
- }
- }
-}
-
-pub struct Slider;
-
-impl slider::StyleSheet for Slider {
- fn active(&self) -> slider::Style {
- slider::Style {
- rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }),
- handle: slider::Handle {
- shape: slider::HandleShape::Circle { radius: 9.0 },
- color: ACTIVE,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- }
- }
-
- fn hovered(&self) -> slider::Style {
- let active = self.active();
-
- slider::Style {
- handle: slider::Handle {
- color: HOVERED,
- ..active.handle
- },
- ..active
- }
- }
-
- fn dragging(&self) -> slider::Style {
- let active = self.active();
-
- slider::Style {
- handle: slider::Handle {
- color: Color::from_rgb(0.85, 0.85, 0.85),
- ..active.handle
- },
- ..active
- }
- }
-}
-
-pub struct PickList;
-
-impl pick_list::StyleSheet for PickList {
- fn menu(&self) -> pick_list::Menu {
- pick_list::Menu {
- text_color: Color::WHITE,
- background: BACKGROUND.into(),
- border_width: 1.0,
- border_color: Color {
- a: 0.7,
- ..Color::BLACK
- },
- selected_background: Color {
- a: 0.5,
- ..Color::BLACK
- }
- .into(),
- selected_text_color: Color::WHITE,
- }
- }
-
- fn active(&self) -> pick_list::Style {
- pick_list::Style {
- text_color: Color::WHITE,
- background: BACKGROUND.into(),
- border_width: 1.0,
- border_color: Color {
- a: 0.6,
- ..Color::BLACK
- },
- border_radius: 2.0,
- icon_size: 0.5,
- ..pick_list::Style::default()
- }
- }
-
- fn hovered(&self) -> pick_list::Style {
- let active = self.active();
-
- pick_list::Style {
- border_color: Color {
- a: 0.9,
- ..Color::BLACK
- },
- ..active
- }
- }
-}