diff --git a/src/component.rs b/src/component.rs index f7a1b46d277d4650c443e92631f5e09ed5c21b10..5dfb4bb76406604feec6bd004522c8162c73b4ca 100644 --- a/src/component.rs +++ b/src/component.rs @@ -1 +1,2 @@ +pub mod code; pub mod grid; diff --git a/src/component/code.rs b/src/component/code.rs new file mode 100644 index 0000000000000000000000000000000000000000..bea7172485e82c75efc7688dc68142c58c5cfde0 --- /dev/null +++ b/src/component/code.rs @@ -0,0 +1,48 @@ +use eframe::egui::{Align2, Color32, Context, Vec2, Window}; + +#[derive(Clone)] +pub struct Code { + /// Must be unique. + pub name: &'static str, + /// Horizontal offset from middle. + pub offset_x: f32, + /// Vertical offset from middle. + pub offset_y: f32, + /// How tall and wide. + pub size_pixels: f32, + /// Background color. Transparent disables background. + pub background: Color32, + /// Code. + pub code: String, +} + +impl Default for Code { + fn default() -> Self { + Self { + name: "grid", + offset_x: 0.0, + offset_y: 0.0, + size_pixels: 400.0, + background: Color32::from_rgb(230, 230, 230), + code: String::from(r#"println!("Hello world!");"#), + } + } +} + +impl Code { + /// Render to UI. + pub fn show(&self, ctx: &Context) { + Window::new(self.name) + .title_bar(false) + //.frame(Frame::none()) + .anchor( + Align2::CENTER_CENTER, + Vec2::new(self.offset_x, self.offset_y), + ) + .fixed_size(Vec2::splat(self.size_pixels)) + .show(ctx, |ui| { + //let (_id, _rect) = ui.allocate_space(Vec2::splat(self.size_pixels)); + ui.code_editor(&mut self.code.as_str()); + }); + } +} diff --git a/src/component/grid.rs b/src/component/grid.rs index 229157e5c2c7db9f8787e112d0272a4c3ab13b74..fe1144e57d3e16ae0db74cbb23584c52baae7b0f 100644 --- a/src/component/grid.rs +++ b/src/component/grid.rs @@ -1,19 +1,26 @@ -use eframe::egui::{ - Align2, Color32, Context, Pos2, Rect, Rounding, Shape, Stroke, Ui, Vec2, Window, -}; +use eframe::egui::{Align2, Color32, Context, Pos2, Rect, Rounding, Shape, Stroke, Vec2, Window}; use eframe::emath; use eframe::epaint::RectShape; #[derive(Clone)] pub struct Grid { + /// Must be unique. pub name: &'static str, + /// Horizontal offset from middle. pub offset_x: f32, + /// Vertical offset from middle. pub offset_y: f32, + /// How many rows and columns. pub size_cells: usize, + /// How tall and wide. pub size_pixels: f32, + /// Stroke color. Transparent disables stroke. pub stroke: Color32, + /// Stroke width. Zero disables stroke. pub stroke_width: f32, + /// Background color. Transparent disables background. pub background: Color32, + /// Fill color of each pixel (use getters and setters). fill: Vec<Color32>, } @@ -34,12 +41,14 @@ impl Default for Grid { } impl Grid { + /// X and y coordinate of a cell to its index in [`self.fill`]. fn index(&self, x: usize, y: usize) -> usize { assert!(x < self.size_cells); assert!(y < self.size_cells); x + y * self.size_cells } + /// Gets the fill color at a cell, which defaults to transparent. pub fn fill(&self, x: usize, y: usize) -> Color32 { self.fill .get(self.index(x, y)) @@ -47,28 +56,56 @@ impl Grid { .unwrap_or(Color32::TRANSPARENT) } - pub fn set_fill(&mut self, x: usize, y: usize, fill: Color32) { + /// Gets mutable reference to the fill color at a cell, which defaults to transparent. + pub fn fill_mut(&mut self, x: usize, y: usize) -> &mut Color32 { let idx = self.index(x, y); loop { match self.fill.get_mut(idx) { - Some(f) => { - *f = fill; - return; + // TODO: Fix lifetime issue. + Some(_) => break, + None => { + self.fill.push(Color32::TRANSPARENT); } - None => self.fill.push(Color32::TRANSPARENT), } } + self.fill.get_mut(idx).unwrap() } + /// Sets the fill color at a cell. + pub fn set_fill(&mut self, x: usize, y: usize, fill: Color32) { + *self.fill_mut(x, y) = fill; + } + + /// Reset all cell fill colors to transparent. pub fn clear_fill(&mut self) { self.fill.clear(); } + pub fn iter(&self) -> impl Iterator<Item = (usize, usize, Color32)> + '_ { + (0..self.size_cells) + .flat_map(move |x| (0..self.size_cells).map(move |y| (x, y, self.fill(x, y)))) + } + + pub fn iter_mut(&mut self) -> impl Iterator<Item = (usize, usize, &mut Color32)> + '_ { + // Ensure fill is populated. + self.fill_mut(self.size_cells - 1, self.size_cells - 1); + + self.fill + .iter_mut() + .take(self.size_cells.pow(2)) + .enumerate() + .map(|(i, c)| (i % self.size_cells, i / self.size_cells, c)) + } + + /// Render to UI. pub fn show(&self, ctx: &Context) { Window::new(self.name) .title_bar(false) //.frame(Frame::none()) - .anchor(Align2::CENTER_CENTER, Vec2::ZERO) + .anchor( + Align2::CENTER_CENTER, + Vec2::new(self.offset_x, self.offset_y), + ) .fixed_size(Vec2::splat(self.size_pixels)) .show(ctx, |ui| { let (_id, rect) = ui.allocate_space(Vec2::splat(self.size_pixels)); @@ -93,10 +130,11 @@ impl Grid { if let Some(fill) = self.fill.get(idx) { if fill.a() > 0 { let x_coord = x as f32 / self.size_cells as f32; + const TOLERANCE: f32 = 0.001; ui.painter().add(Shape::Rect(RectShape::filled( to_screen.transform_rect(Rect::from_x_y_ranges( - x_coord..=x_coord + cell_width, - y_coord..=y_coord + cell_width, + x_coord - TOLERANCE..=x_coord + cell_width + TOLERANCE, + y_coord - TOLERANCE..=y_coord + cell_width + TOLERANCE, )), Rounding::none(), *fill, diff --git a/src/main.rs b/src/main.rs index e37c8908ebb7ba53c498d75ae89da5825cd0cb23..f30a73b3c55e5f8cc67d4e7c1c6f160c4d578854 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,15 +9,13 @@ pub mod slide; use crate::slide::s1_title::Title; use crate::slide::s2_introduction::Introduction; use crate::slide::s3_complexity::Complexity; -use crate::slide::s4_conway::Conway; +use crate::slide::s4_automata::Automata; +use crate::slide::s5_fractals::Fractals; use crate::slide::Slide; -use eframe::egui::style::{Margin, Widgets}; -use eframe::egui::{ - Align, Direction, FontFamily, Key, Layout, Pos2, Rect, Style, TextStyle, Vec2, Visuals, -}; +use eframe::egui::style::Margin; +use eframe::egui::{Key, Style, TextStyle, Visuals}; use eframe::epi::{Frame, Storage}; use eframe::{egui, epi}; -use std::collections::VecDeque; fn main() { let app = Cartoon::default(); @@ -60,7 +58,8 @@ fn create_slides() -> Vec<Box<dyn Slide>> { Box::new(Title::default()) as Box<dyn Slide>, Box::new(Introduction::default()), Box::new(Complexity::default()), - Box::new(Conway::default()), + Box::new(Automata::default()), + Box::new(Fractals::default()), ] } @@ -90,7 +89,7 @@ impl epi::App for Cartoon { } /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`. - fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { // For inspiration and more examples, go to https://emilk.github.io/egui /* diff --git a/src/slide.rs b/src/slide.rs index de7d7f2f8fc37cacce6cf76e3c2ae8916d03f815..a3620978bbac48ed6100f396863ecb97c8faff22 100644 --- a/src/slide.rs +++ b/src/slide.rs @@ -1,16 +1,16 @@ use eframe::egui::{Context, Ui}; -use eframe::{egui, epi}; pub mod s1_title; pub mod s2_introduction; pub mod s3_complexity; -pub mod s4_conway; +pub mod s4_automata; +pub mod s5_fractals; pub trait Slide { fn show(&mut self, ui: &mut Ui, ctx: &Context); /// Returns whether "done." /// Repaint automatically requested. - fn transition(&mut self, ctx: &Context) -> bool { + fn transition(&mut self, _ctx: &Context) -> bool { true } } diff --git a/src/slide/s1_title.rs b/src/slide/s1_title.rs index 90f7cd2c017388369bbb7ffd34b9e1f121f8553f..a6a2375db5277e818ddcc58e1278f24b21e27546 100644 --- a/src/slide/s1_title.rs +++ b/src/slide/s1_title.rs @@ -1,8 +1,7 @@ use crate::egui::{Align2, Context}; use crate::{ctx_img, img, Margin, Slide}; use eframe::egui; -use eframe::egui::{Align, Color32, Frame, Layout, ScrollArea, Style, Ui, Vec2, Window}; -use rand::Rng; +use eframe::egui::{Frame, Ui, Vec2, Window}; #[derive(Default)] pub struct Title { diff --git a/src/slide/s2_introduction.rs b/src/slide/s2_introduction.rs index ca13b1139a429422850fdfcaae378022678ac1cc..56753da9e8d910a0bc0613a412bddbd352d48c73 100644 --- a/src/slide/s2_introduction.rs +++ b/src/slide/s2_introduction.rs @@ -1,6 +1,5 @@ use crate::{Margin, Slide}; use eframe::egui::{Context, Frame, Ui}; -use eframe::{egui, epi}; #[derive(Default)] pub struct Introduction { @@ -8,7 +7,7 @@ pub struct Introduction { } impl Slide for Introduction { - fn transition(&mut self, ctx: &Context) -> bool { + fn transition(&mut self, _ctx: &Context) -> bool { let done = self.strengths; self.strengths = true; done diff --git a/src/slide/s3_complexity.rs b/src/slide/s3_complexity.rs index 9886bd0df577d0fa8b739e39a1c68d991ca7a6e2..c208e48a9e70c15e8a57463a1750014f516a605b 100644 --- a/src/slide/s3_complexity.rs +++ b/src/slide/s3_complexity.rs @@ -1,9 +1,8 @@ use crate::egui::{Context, Ui, Window}; -use crate::{Key, Slide}; -use eframe::egui::text_edit::{CCursorRange, CursorRange, TextEditState}; -use eframe::egui::{Align, Align2, Frame, Layout, TextEdit, Vec2}; -use eframe::epaint::text::cursor::{CCursor, Cursor}; -use std::ops::RangeInclusive; +use crate::Slide; +use eframe::egui::text_edit::{CCursorRange, TextEditState}; +use eframe::egui::{Align2, Frame, TextEdit, Vec2}; +use eframe::epaint::text::cursor::CCursor; #[derive(Default)] pub struct Complexity { @@ -21,7 +20,7 @@ enum ComplexityState { } impl Slide for Complexity { - fn transition(&mut self, ctx: &Context) -> bool { + fn transition(&mut self, _ctx: &Context) -> bool { if self.state == ComplexityState::Before { self.state = ComplexityState::Selecting(0); false @@ -30,7 +29,7 @@ impl Slide for Complexity { } } - fn show(&mut self, ui: &mut Ui, ctx: &Context) { + fn show(&mut self, _ui: &mut Ui, ctx: &Context) { Window::new("complexity") .title_bar(false) .resizable(false) diff --git a/src/slide/s4_conway.rs b/src/slide/s4_automata.rs similarity index 90% rename from src/slide/s4_conway.rs rename to src/slide/s4_automata.rs index 2e2babcef23c0c0ccfecbf1c11e8c622e4502a09..67b625cc48ecd0c295a6a01f36d831cae612594d 100644 --- a/src/slide/s4_conway.rs +++ b/src/slide/s4_automata.rs @@ -2,19 +2,18 @@ use crate::component::grid::Grid; use crate::governor::Governor; use crate::Slide; use eframe::egui::style::Margin; -use eframe::egui::{Align2, Color32, Context, Frame, Pos2, Rect, Shape, Stroke, Ui, Vec2, Window}; -use eframe::emath; +use eframe::egui::{Color32, Context, Frame, Ui}; use rand::Rng; -pub struct Conway { +pub struct Automata { life: Grid, governor: Governor, } -impl Default for Conway { +impl Default for Automata { fn default() -> Self { let mut life = Grid::default(); - + life.name = "life"; for y in 0..life.size_cells { for x in 0..life.size_cells { if rand::thread_rng().gen_bool(0.5) { @@ -37,7 +36,7 @@ impl Default for Conway { } } -impl Slide for Conway { +impl Slide for Automata { fn show(&mut self, ui: &mut Ui, ctx: &Context) { Frame::none().margin(Margin::same(20.0)).show(ui, |ui| { ui.vertical_centered(|ui| { @@ -47,13 +46,13 @@ impl Slide for Conway { ctx.request_repaint(); if self.governor.ready(ctx.input().time, 0.2) { - self.life = conway(&self.life); + self.life = conways_game_of_life(&self.life); } self.life.show(ctx); } } -fn conway(grid: &Grid) -> Grid { +fn conways_game_of_life(grid: &Grid) -> Grid { let mut new = grid.clone(); new.clear_fill(); diff --git a/src/slide/s5_fractals.rs b/src/slide/s5_fractals.rs new file mode 100644 index 0000000000000000000000000000000000000000..5535ac8703a49996b2933981089b2f04f1272dc7 --- /dev/null +++ b/src/slide/s5_fractals.rs @@ -0,0 +1,100 @@ +use crate::component::code::Code; +use crate::component::grid::Grid; +use crate::egui::{Color32, Context, Frame, Ui}; +use crate::Slide; +use eframe::egui::style::Margin; + +pub struct Fractals { + code: Code, + grid: Grid, +} + +impl Default for Fractals { + fn default() -> Self { + const HORIZONTAL_OFFSET: f32 = 275.0; + + let mut code = Code::default(); + + code.code = String::from( + r###" +fn mandelbrot(x0, y0) -> color { + MAX := 512 + x := 0.0; + y := 0.0; + i := 0; + + while x^2 + y^2 <= 4 and i < MAX { + x_temp := x^2 - y^2 + x0 + y = 2.0 * x * y + y0 + x = x_temp + i += 1 + } + + if i == MAX { + return black; + } else { + return white; + } +} +"###, + ); + + code.offset_x = -HORIZONTAL_OFFSET; + + let mut grid = Grid::default(); + + const RESOLUTION: usize = 256; + + grid.name = "fractal"; + grid.size_cells = RESOLUTION; + grid.stroke_width = 0.0; + grid.offset_x = HORIZONTAL_OFFSET; + + for (gx, gy, c) in grid.iter_mut() { + // 0 to 1. + let nx = (gx as f32 + 0.5) / RESOLUTION as f32; + let ny = (gy as f32 + 0.5) / RESOLUTION as f32; + + // -4 to 4. + let x = nx * 4.0 - 2.0; + let y = ny * 4.0 - 2.0; + + *c = if mandelbrot(x, y) { + Color32::BLACK + } else { + Color32::TRANSPARENT + }; + } + + Self { code, grid } + } +} + +impl Slide for Fractals { + fn show(&mut self, ui: &mut Ui, ctx: &Context) { + Frame::none().margin(Margin::same(20.0)).show(ui, |ui| { + ui.vertical_centered(|ui| { + ui.heading("Fractals"); + }); + }); + + self.code.show(ctx); + self.grid.show(ctx); + } +} + +fn mandelbrot(x0: f32, y0: f32) -> bool { + const MAX_ITERATIONS: usize = 512; + + let mut x = 0f32; + let mut y = 0f32; + let mut i = 0usize; + while x.powi(2) + y.powi(2) <= 2f32.powi(2) && i < MAX_ITERATIONS { + let x_tmp = x.powi(2) - y.powi(2) + x0; + y = 2.0 * x * y + y0; + x = x_tmp; + i += 1; + } + + i == MAX_ITERATIONS +}