mod circle; mod mandelbrot; mod rectangle; use crate::color::set_alpha; use crate::component::code::{pseudocode, Code}; use crate::component::grid::Grid; use crate::egui::{Color32, Context, Frame, Ui}; use crate::fade_in::{fade_in_manual, fade_out_manual}; use crate::governor::Governor; use crate::slide::s4_automata::randomize_grid; use crate::slide::s5_fractals::circle::circle; use crate::slide::s5_fractals::mandelbrot::mandelbrot; use crate::slide::s5_fractals::rectangle::rectangle; use crate::slide::Slide; use eframe::egui::style::Margin; /// Mandelbrot, etc. pub struct Fractals { /// Where we are in the transitions. state: FractalsState, /// For showing more pixels. governor: Governor, /// Pseudocode display. code: Code, /// Persistent grid state. grid: Grid, } #[derive(Default)] #[allow(unused)] enum FractalsState { /// Nothing on screen (except header). #[default] Before, /// Fading in grid. Grid { /// When we started fading in the grid. fade_start: f64, }, /// Erasing the automata cells. Erase { /// When we started fading out the dots. fade_start: f64, }, Rectangle { /// How many pixels of the grid we've filled in. pixels: usize, }, Circle { /// How many pixels of the grid we've filled in. pixels: usize, }, Mandelbrot { /// How many pixels of the grid we've filled in. pixels: usize, }, } impl Default for Fractals { fn default() -> Self { const HORIZONTAL_OFFSET: f32 = 275.0; // Code goes on the left. let mut code = Code::default(); code.name = "fractal_code"; code.offset_x = -HORIZONTAL_OFFSET; // Grid goes on the right. let mut grid = Grid::default(); grid.name = "fractal_grid"; grid.offset_x = HORIZONTAL_OFFSET; grid.size_cells = 128; randomize_grid(&mut grid); // Grid cell stroke aliases too much at this resolution. grid.stroke_width = 0.0; Self { state: FractalsState::default(), governor: Governor::default(), code, grid, } } } impl Slide for Fractals { fn transition(&mut self, ctx: &Context) -> bool { // Increment state by one. match self.state { FractalsState::Before => { self.state = FractalsState::Grid { fade_start: ctx.input().time, } } FractalsState::Grid { .. } => { // Make sure we finished fading in. self.code.alpha = 1.0; self.grid.alpha = 1.0; self.state = FractalsState::Erase { fade_start: ctx.input().time, }; } FractalsState::Erase { .. } => self.state = FractalsState::Rectangle { pixels: 0 }, FractalsState::Rectangle { .. } => self.state = FractalsState::Circle { pixels: 0 }, FractalsState::Circle { .. } => self.state = FractalsState::Mandelbrot { pixels: 0 }, FractalsState::Mandelbrot { .. } => return true, } false } fn show(&mut self, ui: &mut Ui) { Frame::none().margin(Margin::same(20.0)).show(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("Fractals"); }); }); ui.ctx().request_repaint(); // The function that will be used to color the grid. // // It is dynamically typed (so that multiple distinct functions can be used). // // Note that we use indirection via a reference but not a heap-allocated [`Box`]. We can get // away with that since the function doesn't leave our stack frame. let algo: Option<&dyn Fn(f32, f32) -> bool>; // Limit to this many pixels. If [`None`], will render all pixels. // We use a mutable reference so we can increment in one place. let mut limit_pixels: Option<&mut usize> = None; match &mut self.state { FractalsState::Before => { // Don't proceed to render the code/grid. return; } &mut FractalsState::Grid { fade_start } => { // Fade in the code and grid. fade_in_manual(ui, fade_start, |_, alpha| { self.code.alpha = alpha; self.grid.alpha = alpha; }); algo = None; } &mut FractalsState::Erase { fade_start } => { fade_out_manual(ui, fade_start, |_, alpha| { // Fade out the automata cells. for (_, _, cell) in self.grid.iter_mut() { if cell.a() > 0 { *cell = set_alpha(Color32::BLACK, alpha); } } }); algo = None; } FractalsState::Rectangle { pixels } => { self.code.code = pseudocode(include_str!("s5_fractals/rectangle.rs")); algo = Some(&rectangle); limit_pixels = Some(pixels) } FractalsState::Circle { pixels } => { self.code.code = pseudocode(include_str!("s5_fractals/circle.rs")); algo = Some(&circle); limit_pixels = Some(pixels); } FractalsState::Mandelbrot { pixels } => { self.code.code = pseudocode(include_str!("s5_fractals/mandelbrot.rs")); algo = Some(&mandelbrot); limit_pixels = Some(pixels); } } // Double mutable borrow to avoid moving limit_pixels. if let Some(limit_pixels) = &mut limit_pixels { if **limit_pixels < self.grid.size_cells.pow(2) && self.governor.ready(ui.ctx().input().time, 0.02) { **limit_pixels += self.grid.size_cells.pow(2) * 33 / 32 / 32; } } // Paint the grid. if let Some(algo) = algo { // Effective limit for pixels. let effective_limit = limit_pixels.map(|l| *l).unwrap_or(usize::MAX); // Store the resolution before mutably borrowing self.grid. let size_cells = self.grid.size_cells; for (i, (gx, gy, c)) in self.grid.iter_mut().enumerate() { if i >= effective_limit { *c = Color32::TRANSPARENT; continue; } // 0 to 1. let nx = (gx as f32 + 0.5) / size_cells as f32; let ny = (gy as f32 + 0.5) / size_cells as f32; // -4 to 4. let x = nx * 4.0 - 2.0; let y = ny * 4.0 - 2.0; *c = if algo(x, y) { Color32::BLACK } else { Color32::WHITE }; } } self.code.show(ui.ctx()); self.grid.show(ui.ctx()); } }