diff --git a/src/cartoon.rs b/src/cartoon.rs new file mode 100644 index 0000000000000000000000000000000000000000..1e414ea4a3b06f93ee50f875d7165601ba44c1c0 --- /dev/null +++ b/src/cartoon.rs @@ -0,0 +1,160 @@ +use crate::fade_in::fade_in; +use crate::slide::s1_title::Title; +use crate::slide::s2_introduction::Introduction; +use crate::slide::s3_complexity::Complexity; +use crate::slide::s4_automata::Automata; +use crate::slide::s5_fractals::Fractals; +use crate::slide::s6_computation::Computation; +use crate::slide::s7_mosaic::Mosaic; +use crate::slide::s8_conclusion::Conclusion; +use crate::slide::Slide; +use eframe::egui::style::Margin; +use eframe::egui::{Key, Style, TextStyle, Visuals}; +use eframe::epi::{Frame, Storage}; +use eframe::{egui, epi}; + +/// Top-level state. +pub struct Cartoon { + /// All slides (including their state). + slides: Vec<Box<dyn Slide>>, + /// Current index into [`slides`]. + slide_index: usize, + /// When we started fading in the current slide, in seconds (with respect to `ctx.input().time`). + transition_time: f64, +} + +// Default is used instead of a zero-argument constructor function, to be more idiomatic. +impl Default for Cartoon { + fn default() -> Self { + Self { + slides: create_slides(), + slide_index: 0, + transition_time: 0.0, + } + } +} + +/// Creates all the slides from default values. This will reset any and all animations and +/// transitions built into the slides. +/// +/// This is also how the chronology of the slideshow is determined. +fn create_slides() -> Vec<Box<dyn Slide>> { + vec![ + Box::new(Title::default()) as Box<dyn Slide>, + Box::new(Introduction::default()), + Box::new(Complexity::default()), + Box::new(Automata::default()), + Box::new(Fractals::default()), + Box::new(Computation::default()), + Box::new(Mosaic::default()), + Box::new(Conclusion::default()), + ] +} + +impl epi::App for Cartoon { + /// The title of the window, which is mostly irrelevant. + fn name(&self) -> &str { + "Generative Art Cartoon" + } + + /// Called once at window initialization. + fn setup(&mut self, ctx: &egui::Context, _frame: &Frame, _storage: Option<&dyn Storage>) { + // Top level style overrides. + let mut style = Style::default(); + // This doesn't actually affect as many things as [`FadeIn`]'s fade duration. + style.animation_time = 0.5; + // Light theme. + style.visuals = Visuals::light(); + // Larger margin for windows (which are used for more control when positing stuff). + style.spacing.window_margin = Margin::same(24.0); + + // Increase font sizes a lot. + { + let header_style = style.text_styles.get_mut(&TextStyle::Heading).unwrap(); + header_style.size = 48.0; + } + { + let body_style = style.text_styles.get_mut(&TextStyle::Body).unwrap(); + body_style.size = 30.0; + } + { + let small_style = style.text_styles.get_mut(&TextStyle::Small).unwrap(); + small_style.size = 22.0; + } + // TODO(finnb): This doesn't work. May have to fork syntax coloring code to fix it. + /* + { + let monospaced_style = style.text_styles.get_mut(&TextStyle::Monospace).unwrap(); + monospaced_style.size = 22.0; + } + */ + ctx.set_style(style); + + // Start fading in title slide now. + self.transition_time = ctx.input().time; + } + + /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`. + fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { + // For inspiration and more examples, go to https://emilk.github.io/egui + + /* + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { + egui::menu::bar(ui, |ui| { + ui.menu_button("File", |ui| { + if ui.button("Quit").clicked() { + frame.quit(); + } + }); + }); + }); + */ + + // Go forward one slide (don't wait for intra-slide transitions). + // + // This will wrap around to the beginning. + let force_advance = + ctx.input().key_pressed(Key::ArrowRight) || ctx.input().key_pressed(Key::D); + + // Play the next intra-slide transition or, if there isn't one, then go forward one slide. + // + // This will wrap around to the beginning. + let advance = force_advance || ctx.input().key_pressed(Key::Space); + + // Go backward one slide (don't wait for intra-slide transitions). + // + // This will wrap around to the end. + let retreat = ctx.input().key_pressed(Key::ArrowLeft) || ctx.input().key_pressed(Key::A); + + if advance || retreat { + self.slide_index = if advance { + if force_advance || self.slides[self.slide_index].transition(ctx) { + let new = (self.slide_index + 1) % self.slides.len(); + if new == 0 { + // We wrapped around to the beginning, so create slides to reset transitions. + self.slides = create_slides(); + } + self.transition_time = ctx.input().time; + ctx.request_repaint(); + new + } else { + self.slide_index + } + } else { + ctx.request_repaint(); + self.transition_time = ctx.input().time; + self.slide_index + .checked_sub(1) + .unwrap_or(self.slides.len() - 1) + }; + } + + egui::CentralPanel::default().show(ctx, |ui| { + // The current slide may be fading in. + fade_in(ui, self.transition_time, |ui| { + // Render the current slide. + self.slides[self.slide_index].show(ui); + }); + }); + } +} diff --git a/src/color.rs b/src/color.rs index 6e037eef4d555bd844b8c6a8ef07070afeeae38e..248cc49906dfbf36268dcff49f8d0b412dc76e1d 100644 --- a/src/color.rs +++ b/src/color.rs @@ -52,8 +52,6 @@ pub fn set_style_alpha_in_place(style: &mut Style, factor: f32) { set_alpha_in_place(&mut style.visuals.extreme_bg_color, factor); set_alpha_in_place(&mut style.visuals.code_bg_color, factor); set_alpha_in_place(&mut style.visuals.window_shadow.color, factor); - - //style.visuals.widgets.noninteractive.bg_fill = Color32::TRANSPARENT; } /// Render some children with the specified alpha from 0 to 1. diff --git a/src/component.rs b/src/component.rs index cf13fbc60f9b148b4095e55287a5fbcd2518db68..6acd3eca01d62ea6201084b3b4cb7588aacf863c 100644 --- a/src/component.rs +++ b/src/component.rs @@ -1,3 +1,4 @@ -/// See component directory. +/// See component directory. This file exists to make the individual components accessible to the +/// rest of the code. pub mod code; pub mod grid; diff --git a/src/component/code.rs b/src/component/code.rs index 056e4362268265495776b13ca2718aca64c05066..4c6aebe1c4a8828ab36d2490427bdc61c4b36042 100644 --- a/src/component/code.rs +++ b/src/component/code.rs @@ -2,9 +2,10 @@ use crate::color::{set_alpha_in_place, set_style_alpha}; use eframe::egui; use eframe::egui::{Align2, Color32, Context, Frame, Vec2, Widget, Window}; +/// A reusable (pseudo)code-display component. #[derive(Clone)] pub struct Code { - /// Must be unique. + /// Must be unique for `egui` reasons. pub name: &'static str, /// Horizontal offset from middle. pub offset_x: f32, diff --git a/src/component/grid.rs b/src/component/grid.rs index 499e410c4f0de09ec0bef45755bcefeb45931b58..a87c0a9ede2d61b1d785e55d80ab14ecdbad2601 100644 --- a/src/component/grid.rs +++ b/src/component/grid.rs @@ -4,9 +4,10 @@ use eframe::egui::{Align2, Color32, Context, Pos2, Rect, Rounding, Shape, Stroke use eframe::emath; use eframe::epaint::RectShape; +/// A reusable grid component. #[derive(Clone)] pub struct Grid { - /// Must be unique. + /// Must be unique for `egui` reasons. pub name: &'static str, /// Horizontal offset from middle. pub offset_x: f32, diff --git a/src/fade_in.rs b/src/fade_in.rs index f028cab1ed8fe5e56edca3e8e1cd6fdc4037cd7d..045c19f441391fdbd49a5c4d841c6ad69a5edbc0 100644 --- a/src/fade_in.rs +++ b/src/fade_in.rs @@ -12,6 +12,7 @@ pub fn fade_in<R>( let since_transition = ui.ctx().input().time - fade_start; let alpha = (since_transition * (1.0 / FADE_DURATION)).min(1.0) as f32; if alpha < 1.0 { + // The fade isn't done yet, so schedule another render. ui.ctx().request_repaint(); } with_alpha(ui, alpha, |ui| add_contents(ui)) diff --git a/src/governor.rs b/src/governor.rs index d8078f695d4999e7f51041eb8c09260bd03e24c2..6d30419408cfd81b3100be6a266a4ffe380069fa 100644 --- a/src/governor.rs +++ b/src/governor.rs @@ -8,9 +8,11 @@ impl Governor { /// Is the rate limit ready to allow another action? pub fn ready(&mut self, time: f64, limit: f64) -> bool { if time > self.last + limit { + // Enough time passed. self.last = time; true } else { + // Keep waiting. false } } diff --git a/src/image.rs b/src/image.rs index 35c4819922698c79ca424e21a35ccdc007c8fe96..671258e5396b7234ba5d99cb00ce586f4e6a1576 100644 --- a/src/image.rs +++ b/src/image.rs @@ -19,7 +19,8 @@ macro_rules! ctx_img { }}; } -/// Decodes image data. +/// Decodes image data. Formats other than PNG may require adding feature flags to the `image` crate +/// in Cargo.toml. pub fn load_image_from_memory( image_data: &[u8], ) -> Result<eframe::egui::ColorImage, image::ImageError> { diff --git a/src/main.rs b/src/main.rs index 13ed9d8612680a69e3590f59996c6885aa2bc5ff..b74843f7adc3109005aa1a8313b8c1b08db16936 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![feature(derive_default_enum)] #![feature(mixed_integer_ops)] +pub mod cartoon; pub mod color; pub mod component; pub mod fade_in; @@ -8,23 +9,13 @@ pub mod governor; pub mod image; pub mod slide; -use crate::fade_in::fade_in; -use crate::slide::s1_title::Title; -use crate::slide::s2_introduction::Introduction; -use crate::slide::s3_complexity::Complexity; -use crate::slide::s4_automata::Automata; -use crate::slide::s5_fractals::Fractals; -use crate::slide::s6_computation::Computation; -use crate::slide::s7_mosaic::Mosaic; -use crate::slide::s8_conclusion::Conclusion; -use crate::slide::Slide; +use crate::cartoon::Cartoon; +use eframe::egui; use eframe::egui::style::Margin; -use eframe::egui::{Key, Style, TextStyle, Visuals}; -use eframe::epi::{Frame, Storage}; -use eframe::{egui, epi}; // Entry point. fn main() { + // cartoon.rs is a good place to look next. let app = Cartoon::default(); // 16:9 aspect ratio. @@ -46,145 +37,3 @@ fn main() { eframe::run_native(Box::new(app), native_options); } - -/// Top-level state. -pub struct Cartoon { - /// All slides (including their state). - slides: Vec<Box<dyn Slide>>, - /// Current index into [`slides`]. - slide_index: usize, - /// When we started fading in the current slide, in seconds (with respect to `ctx.input().time`). - transition_time: f64, -} - -impl Default for Cartoon { - fn default() -> Self { - Self { - slides: create_slides(), - slide_index: 0, - transition_time: 0.0, - } - } -} - -/// Creates all the slides from default values. This will reset any and all animations and -/// transitions built into the slides. -/// -/// This is also how the chronology of the slideshow is determined. -fn create_slides() -> Vec<Box<dyn Slide>> { - vec![ - Box::new(Title::default()) as Box<dyn Slide>, - Box::new(Introduction::default()), - Box::new(Complexity::default()), - Box::new(Automata::default()), - Box::new(Fractals::default()), - Box::new(Computation::default()), - Box::new(Mosaic::default()), - Box::new(Conclusion::default()), - ] -} - -impl epi::App for Cartoon { - /// The title of the window, which is mostly irrelevant. - fn name(&self) -> &str { - "Generative Art Cartoon" - } - - /// Called once at window initialization. - fn setup(&mut self, ctx: &egui::Context, _frame: &Frame, _storage: Option<&dyn Storage>) { - // Top level style overrides. - let mut style = Style::default(); - style.animation_time = 0.5; - style.visuals = Visuals::light(); - style.spacing.window_margin = Margin::same(24.0); - - // Increase font sizes a lot. - { - let header_style = style.text_styles.get_mut(&TextStyle::Heading).unwrap(); - header_style.size = 48.0; - } - { - let body_style = style.text_styles.get_mut(&TextStyle::Body).unwrap(); - body_style.size = 30.0; - } - { - let small_style = style.text_styles.get_mut(&TextStyle::Small).unwrap(); - small_style.size = 22.0; - } - // TODO(finnb): This doesn't work. May have to fork syntax coloring code to fix it. - /* - { - let monospaced_style = style.text_styles.get_mut(&TextStyle::Monospace).unwrap(); - monospaced_style.size = 22.0; - } - */ - ctx.set_style(style); - - // Start fading in title slide now. - self.transition_time = ctx.input().time; - } - - /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`. - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { - // For inspiration and more examples, go to https://emilk.github.io/egui - - /* - egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { - egui::menu::bar(ui, |ui| { - ui.menu_button("File", |ui| { - if ui.button("Quit").clicked() { - frame.quit(); - } - }); - }); - }); - */ - - // Go forward one slide (don't wait for intra-slide transitions). - // - // This will wrap around to the beginning. - let force_advance = - ctx.input().key_pressed(Key::ArrowRight) || ctx.input().key_pressed(Key::D); - - // Play the next intra-slide transition or, if there isn't one, then go forward one slide. - // - // This will wrap around to the beginning. - let advance = force_advance || ctx.input().key_pressed(Key::Space); - - // Go backward one slide (don't wait for intra-slide transitions). - // - // This will wrap around to the end. - let retreat = ctx.input().key_pressed(Key::ArrowLeft) || ctx.input().key_pressed(Key::A); - - if advance || retreat { - self.slide_index = if advance { - if force_advance || self.slides[self.slide_index].transition(ctx) { - let new = (self.slide_index + 1) % self.slides.len(); - if new == 0 { - // We wrapped around to the beginning, so create slides to reset transitions. - self.slides = create_slides(); - } - self.transition_time = ctx.input().time; - ctx.request_repaint(); - new - } else { - self.slide_index - } - } else { - ctx.request_repaint(); - self.transition_time = ctx.input().time; - self.slide_index - .checked_sub(1) - .unwrap_or(self.slides.len() - 1) - }; - } - - egui::CentralPanel::default().show(ctx, |ui| { - // The current slide may be fading in. - fade_in(ui, self.transition_time, |ui| { - // Render the current slide. - self.slides[self.slide_index].show(ui); - }); - }); - } -} diff --git a/src/slide.rs b/src/slide.rs index bc6977495e0441f598e718624145413aa144c109..43cfc7528d79e6d78cf379758145c9269827f4e3 100644 --- a/src/slide.rs +++ b/src/slide.rs @@ -1,5 +1,7 @@ use eframe::egui::{Context, Ui}; +/// See slide directory. This file exists to make the individual slides accessible to the +/// rest of the code. pub mod s1_title; pub mod s2_introduction; pub mod s3_complexity; @@ -11,7 +13,7 @@ pub mod s8_conclusion; /// An interface for all slides. pub trait Slide { - /// Returns whether "done." + /// Returns whether "done" i.e. whether should switch to next slide. /// Repaint automatically requested. fn transition(&mut self, _ctx: &Context) -> bool { true diff --git a/src/slide/s1_title.rs b/src/slide/s1_title.rs index 8a983baafb570917aef3dab5819452fe1bf77514..ee88a527c9156e0279ea86d369d3bedc6d782d9a 100644 --- a/src/slide/s1_title.rs +++ b/src/slide/s1_title.rs @@ -1,5 +1,6 @@ use crate::egui::Align2; -use crate::{ctx_img, img, Margin, Slide}; +use crate::slide::Slide; +use crate::{ctx_img, img, Margin}; use eframe::egui; use eframe::egui::{Frame, Ui, Vec2, Window}; diff --git a/src/slide/s2_introduction.rs b/src/slide/s2_introduction.rs index 6c476ea692cc2868c7f05b61f653cff670a29863..c24d1a02b00374eab4fa540f9f243a980ea64d2b 100644 --- a/src/slide/s2_introduction.rs +++ b/src/slide/s2_introduction.rs @@ -1,4 +1,6 @@ -use crate::{fade_in, Margin, Slide}; +use crate::fade_in::fade_in; +use crate::slide::Slide; +use eframe::egui::style::Margin; use eframe::egui::{Context, Frame, Ui}; /// A slide that sets some expectations about generative art. diff --git a/src/slide/s3_complexity.rs b/src/slide/s3_complexity.rs index 3dd1dfa0e4b637b72c0c37fb93f5cdabbb98e915..794b0ab5f88f551d7106a2d3bd0b16dcd8ce5eb0 100644 --- a/src/slide/s3_complexity.rs +++ b/src/slide/s3_complexity.rs @@ -1,5 +1,5 @@ use crate::egui::{Context, Ui, Window}; -use crate::Slide; +use crate::slide::Slide; use eframe::egui::text_edit::{CCursorRange, TextEditState}; use eframe::egui::{Align2, Frame, TextEdit, Vec2, Widget}; use eframe::epaint::text::cursor::CCursor; diff --git a/src/slide/s4_automata.rs b/src/slide/s4_automata.rs index 4239c4bd015d5f9137e7fb26cce9a426099254de..6e83b65b78e0231f46b6625891abfa1f24f5bc57 100644 --- a/src/slide/s4_automata.rs +++ b/src/slide/s4_automata.rs @@ -1,6 +1,6 @@ use crate::component::grid::Grid; use crate::governor::Governor; -use crate::Slide; +use crate::slide::Slide; use eframe::egui::style::Margin; use eframe::egui::{Color32, Frame, Ui}; use rand::Rng; diff --git a/src/slide/s5_fractals.rs b/src/slide/s5_fractals.rs index adafb70dab72fdd5d6854c3787578edb7569f6d6..0d895ebba3916114bcf69d8bbc006382a6ce6ee7 100644 --- a/src/slide/s5_fractals.rs +++ b/src/slide/s5_fractals.rs @@ -8,7 +8,7 @@ use crate::egui::{Color32, Context, Frame, Ui}; use crate::slide::s5_fractals::circle::circle; use crate::slide::s5_fractals::mandelbrot::mandelbrot; use crate::slide::s5_fractals::rectangle::rectangle; -use crate::Slide; +use crate::slide::Slide; use eframe::egui::style::Margin; /// Mandelbrot, etc. diff --git a/src/slide/s5_fractals/README.md b/src/slide/s5_fractals/README.md new file mode 100644 index 0000000000000000000000000000000000000000..23ea144fa6649747bae8854d8f1116b449914614 --- /dev/null +++ b/src/slide/s5_fractals/README.md @@ -0,0 +1,4 @@ +These are linked into the program as both code (`use`) and data (`include_str!`). They should not necessarily obey Rust +idioms, since they are adapted into pseudocode. + +In particular, use `return x;` syntax instead of `x`. \ No newline at end of file diff --git a/src/slide/s6_computation.rs b/src/slide/s6_computation.rs index 68a4a05ae06eb683f1085966eeb9a8a9d5240cec..660de167366910b29afa928c0b52c9261e964041 100644 --- a/src/slide/s6_computation.rs +++ b/src/slide/s6_computation.rs @@ -1,5 +1,5 @@ use crate::egui::{Frame, Ui}; -use crate::Slide; +use crate::slide::Slide; use eframe::egui::style::Margin; #[derive(Default)] diff --git a/src/slide/s7_mosaic.rs b/src/slide/s7_mosaic.rs index e04e52257776f377626fa973d3be2cf8a02cf8a9..df7ff6871f6adc8cb8f2ade590e1189037f8c796 100644 --- a/src/slide/s7_mosaic.rs +++ b/src/slide/s7_mosaic.rs @@ -1,5 +1,5 @@ use crate::egui::Ui; -use crate::Slide; +use crate::slide::Slide; use eframe::egui::style::Margin; use eframe::egui::Frame; diff --git a/src/slide/s8_conclusion.rs b/src/slide/s8_conclusion.rs index 169ff0a992b7153344e41820fa77a31cb3f6bb4c..6bb55e86c22fd850f85e4210fa2e0ff7a9294aee 100644 --- a/src/slide/s8_conclusion.rs +++ b/src/slide/s8_conclusion.rs @@ -1,5 +1,5 @@ use crate::egui::Ui; -use crate::Slide; +use crate::slide::Slide; use eframe::egui::style::Margin; use eframe::egui::Frame;