1use crate::timer::{RepeatingTimer, TimerState};
4use lifers::{engine::ExecutionState, frontend::RenderCell, prelude::generic::Automaton};
5use raylib::{
6 color::Color, drawing::RaylibDraw, ffi::KeyboardKey, math::Vector2, RaylibHandle, RaylibThread,
7};
8use std::time::Duration;
9
10#[macro_export]
15macro_rules! map_vecs {
16 ($( $vec:expr ),+ => $f:expr) => {
17 Vector2::new($f($( $vec.x ),+), $f($( $vec.y ),+))
18 };
19}
20
21pub struct RaylibFrontend<S, D> {
23 automaton: Automaton<S, D>,
24 rl: RaylibHandle,
25 thread: RaylibThread,
26 timer: RepeatingTimer,
27 cell_margin: u32,
28 rect_size: f32,
29 center_translation: Vector2,
30}
31
32impl<S, D> RaylibFrontend<S, D> {
33 #[allow(clippy::as_conversions)]
38 pub fn new(
39 automaton: Automaton<S, D>,
40 update_rate: Duration,
41 cell_margin: u32,
42 window_size: (u32, u32),
43 ) -> Self {
44 let (rl, thread) = raylib::init()
45 .size(window_size.0 as i32, window_size.1 as i32)
46 .title("lifers")
47 .build();
48
49 let cell_margin_f = cell_margin as f32;
50 let window_size = Vector2::new(window_size.0 as f32, window_size.1 as f32);
51
52 let grid_dimensions = {
53 let (x, y) = automaton.grid_size();
54
55 Vector2::new(x as f32, y as f32)
56 };
57 let rect_size = {
58 let Vector2 { x, y } = map_vecs!(
59 window_size,
60 grid_dimensions
61 => |win, cells: f32| (cells + 1.).mul_add(-cell_margin_f, win) / cells
62 );
63 let side = x.min(y);
64
65 Vector2::new(side, side)
66 };
67 let grid_size = map_vecs!(
68 rect_size,
69 grid_dimensions
70 => |size, cells: f32| cells.mul_add(size, (cells + 1.) * cell_margin_f)
71 );
72 let grid_center = grid_size.scale_by(0.5);
73 let window_center = window_size.scale_by(0.5);
74
75 #[allow(clippy::arithmetic_side_effects)]
79 let center_translation = window_center - grid_center;
80
81 Self {
82 automaton,
83 rl,
84 thread,
85 timer: RepeatingTimer::new(update_rate),
86 cell_margin,
87 rect_size: rect_size.x,
88 center_translation,
89 }
90 }
91
92 pub fn window_should_close(&self) -> bool {
94 self.automaton.is_finished() || self.rl.window_should_close()
95 }
96
97 pub fn tick(&mut self) -> Option<ExecutionState> {
101 matches!(self.timer.update(), TimerState::Finished).then(|| self.automaton.step())
102 }
103
104 pub fn step(&mut self) -> ExecutionState {
108 self.automaton.step()
109 }
110
111 pub fn default_key_actions(&mut self) {
115 match self.rl.get_key_pressed() {
116 None => (),
117 Some(key) => match key {
118 KeyboardKey::KEY_SPACE => self.timer.toggle_pause(), KeyboardKey::KEY_MINUS => {
122 self.timer = RepeatingTimer::new(self.timer.rate() + Duration::from_millis(10))
123 }
124 KeyboardKey::KEY_EQUAL => {
125 let duration = self
126 .timer
127 .rate()
128 .checked_sub(Duration::from_millis(10))
129 .unwrap_or(Duration::from_millis(0));
130
131 self.timer = RepeatingTimer::new(duration);
132 }
133 _ => (),
134 },
135 }
136 }
137}
138
139impl<S: RenderCell<Color>, D> RaylibFrontend<S, D> {
140 pub fn display_grid(&mut self) {
145 let mut drawer = self.rl.begin_drawing(&self.thread);
146
147 drawer.clear_background(Color::GRAY);
148
149 #[allow(clippy::as_conversions)]
150 self.automaton
151 .cells()
152 .iter()
153 .enumerate()
154 .for_each(|(y, xs)| {
155 xs.iter().enumerate().for_each(|(x, cell)| {
156 let pos = map_vecs!(
157 Vector2::new(x as f32, y as f32),
158 self.center_translation
159 => |pos: f32, center_vec| pos.mul_add(self.rect_size, (pos + 1.) * self.cell_margin as f32) + center_vec
160 );
161
162 let rect = Vector2::new(self.rect_size, self.rect_size);
163 drawer.draw_rectangle_v(pos, rect, cell.render_cell());
164 });
165 });
166 }
167}
168
169pub struct FrontendBuilder {
171 window_size: (u32, u32),
172 cell_margin: u32,
173 update_rate: Duration,
174}
175
176impl FrontendBuilder {
177 #[must_use]
179 pub const fn new(window_size: (u32, u32)) -> Self {
180 Self {
181 window_size,
182 cell_margin: 5,
183 update_rate: Duration::from_millis(100),
184 }
185 }
186
187 #[must_use]
189 pub const fn cell_margin(self, cell_margin: u32) -> Self {
190 Self {
191 cell_margin,
192 ..self
193 }
194 }
195
196 #[must_use]
201 pub const fn update_rate(self, update_rate: Duration) -> Self {
202 Self {
203 update_rate,
204 ..self
205 }
206 }
207
208 pub fn finish<S, D>(self, automaton: Automaton<S, D>) -> RaylibFrontend<S, D> {
210 RaylibFrontend::new(
211 automaton,
212 self.update_rate,
213 self.cell_margin,
214 self.window_size,
215 )
216 }
217}