diff --git a/Cargo.lock b/Cargo.lock index 88939f2..6b620f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -921,6 +921,7 @@ dependencies = [ name = "cyberpunk-slideshow" version = "0.1.0" dependencies = [ + "async-std", "cairo-rs", "cyberpunk", "gio 0.18.4", diff --git a/cyberpunk-slideshow/Cargo.toml b/cyberpunk-slideshow/Cargo.toml index e64faab..eccb993 100644 --- a/cyberpunk-slideshow/Cargo.toml +++ b/cyberpunk-slideshow/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-std = "1.13.0" cairo-rs = "0.18" cyberpunk = { path = "../cyberpunk" } gio = "0.18" diff --git a/cyberpunk-slideshow/script.yml b/cyberpunk-slideshow/script.yml index 7fe3360..36fd3c5 100644 --- a/cyberpunk-slideshow/script.yml +++ b/cyberpunk-slideshow/script.yml @@ -1,11 +1,18 @@ - text: The distinguishing thing about magic is that it includes some kind of personal element. The person who is performing the magic is relevant to the magic. -- Ted Chang, Marie Brennan position: top - transition: !Fade + transition: + secs: 2 + nanos: 0 + - text: Any sufficiently advanced technology is indistinguishable from magic. -- Arthur C. Clark. position: middle - transition: !Fade + transition: + secs: 2 + nanos: 0 - text: Electricity is the closest we get to Magic in this world. position: middle - transition: !Fade + transition: + secs: 2 + nanos: 0 diff --git a/cyberpunk-slideshow/src/main.rs b/cyberpunk-slideshow/src/main.rs index c83cae4..ea2d3c2 100644 --- a/cyberpunk-slideshow/src/main.rs +++ b/cyberpunk-slideshow/src/main.rs @@ -1,5 +1,14 @@ -use std::{cell::RefCell, fs::File, io::Read, ops::Index, path::Path, rc::Rc, time::Duration}; +use std::{ + cell::RefCell, + fs::File, + io::Read, + ops::Index, + path::Path, + rc::Rc, + time::{Duration, Instant}, +}; +use cairo::Context; use cyberpunk::{AsymLine, AsymLineCutout, GlowPen, Pen, Text}; use glib::{GString, Object}; use gtk::{ @@ -10,6 +19,7 @@ use gtk::{ }; use serde::{Deserialize, Serialize}; +const FPS: u64 = 60; const PURPLE: (f64, f64, f64) = (0.7, 0., 1.); #[derive(Serialize, Deserialize, Debug, Clone)] @@ -20,24 +30,11 @@ enum Position { Bottom, } -#[derive(Serialize, Deserialize, Debug, Clone)] -enum TransitionName { - Fade, - CrossFade, -} - -struct Fade(Step); - -enum Transition { - Fade(Fade), - CrossFade(Step, Step), -} - #[derive(Serialize, Deserialize, Debug, Clone)] struct Step { text: String, position: Position, - transition: TransitionName, + transition: Duration, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -75,6 +72,59 @@ impl Index for Script { } } +struct Fade { + text: String, + position: Position, + duration: Duration, + + start_time: Instant, +} + +/* +impl Fade { + fn render(&self, context: &Context) { + let start = Instant::now(); + } +} +*/ + +/* +impl Transition { + fn render(&self, context: &Context) { + match self { + Transition::Fade(fade) => fade.render(context), + Transition::CrossFade(start, end) => {} + } + } +} +*/ + +trait Animation { + fn position(&self) -> Position; + + fn tick(&self, now: Instant, context: &Context) -> bool; +} + +impl Animation for Fade { + fn position(&self) -> Position { + self.position.clone() + } + + fn tick(&self, now: Instant, context: &Context) -> bool { + let total_frames = self.duration.as_secs() * FPS; + let alpha_rate: f64 = 1. / total_frames as f64; + + let frames = (now - self.start_time).as_secs_f64() * FPS as f64; + let alpha = alpha_rate * frames as f64; + + let _ = context.set_source_rgba(PURPLE.0, PURPLE.1, PURPLE.2, alpha); + let text_display = Text::new(self.text.clone(), context, 32.); + text_display.draw(); + + false + } +} + #[derive(Debug)] pub struct CyberScreenState { script: Script, @@ -103,13 +153,15 @@ impl CyberScreenState { s } - fn next_page(&mut self) -> Transition { + fn next_page(&mut self) -> impl Animation { let idx = match self.idx { None => 0, - Some(idx) => if idx < self.script.len() { - idx + 1 - } else { - idx + Some(idx) => { + if idx < self.script.len() { + idx + 1 + } else { + idx + } } }; self.idx = Some(idx); @@ -117,40 +169,28 @@ impl CyberScreenState { match step.position { Position::Top => { - let transition = if let Some(old_step) = self.top.take() { - Transition::CrossFade(old_step, step.clone()) - } else { - Transition::Fade(Fade(step.clone())) - }; - self.top = Some(step); - transition + self.top = Some(step.clone()); } Position::Middle => { - let transition = if let Some(old_step) = self.middle.take() { - Transition::CrossFade(old_step, step.clone()) - } else { - Transition::Fade(Fade(step.clone())) - }; - self.middle = Some(step); - transition + self.middle = Some(step.clone()); } Position::Bottom => { - let transition = if let Some(old_step) = self.bottom.take() { - Transition::CrossFade(old_step, step.clone()) - } else { - Transition::Fade(Fade(step.clone())) - }; - self.bottom = Some(step); - transition + self.bottom = Some(step.clone()); } } + Fade { + text: step.text.clone(), + position: step.position, + duration: step.transition, + start_time: Instant::now(), + } } } #[derive(Default)] pub struct CyberScreenPrivate { state: Rc>, - transition_queue: Rc>>, + animations: Rc>>>, } #[glib::object_subclass] @@ -171,7 +211,7 @@ impl CyberScreenPrivate { fn next_page(&self) { let transition = self.state.borrow_mut().next_page(); - self.transition_queue.borrow_mut().push(transition); + self.animations.borrow_mut().push(Box::new(transition)); } } @@ -187,6 +227,7 @@ impl CyberScreen { s.set_draw_func({ let s = s.clone(); move |_, context, width, height| { + let now = Instant::now(); let _ = context.set_source_rgb(0., 0., 0.); let _ = context.paint(); @@ -198,40 +239,27 @@ impl CyberScreen { } pen.stroke(); */ - - - let tracery = pen.finish(); let _ = context.set_source(tracery); let _ = context.paint(); - for transition in s.imp().transition_queue.borrow().iter() { - if let Some(ref text) = s.imp().state.borrow().top { - let text_display = Text::new(text.text.clone(), context, 32.); - let y = height as f64 * 1. / 5. + text_display.extents().height(); + let mut animations = s.imp().animations.borrow_mut(); + let mut to_remove = vec![]; + for (idx, animation) in animations.iter().enumerate() { + let y = match animation.position() { + Position::Top => height as f64 * 1. / 5., + Position::Middle => height as f64 * 2. / 5., + Position::Bottom => height as f64 * 3. / 5., + }; + context.move_to(20., y); + let done = animation.tick(now, context); + if done { + to_remove.push(idx) + }; + } - context.move_to(20., y); - let _ = context.set_source_rgb(PURPLE.0, PURPLE.1, PURPLE.2); - text_display.draw(); - } - - if let Some(ref text) = s.imp().state.borrow().middle { - let text_display = Text::new(text.text.clone(), context, 32.); - let y = height as f64 * 2. / 5. + text_display.extents().height(); - - context.move_to(20., y); - let _ = context.set_source_rgb(PURPLE.0, PURPLE.1, PURPLE.2); - text_display.draw(); - } - - if let Some(ref text) = s.imp().state.borrow().bottom { - let text_display = Text::new(text.text.clone(), context, 32.); - let y = height as f64 * 3. / 5. + text_display.extents().height(); - - context.move_to(20., y); - let _ = context.set_source_rgb(PURPLE.0, PURPLE.1, PURPLE.2); - text_display.draw(); - } + for idx in to_remove.into_iter() { + animations.remove(idx); } } }); @@ -247,6 +275,14 @@ impl CyberScreen { } fn main() { + let script = Script(vec![Step { + text: "The distinguishing thing".to_owned(), + position: Position::Top, + transition: Duration::from_secs(2), + }]); + + println!("{}", serde_yml::to_string(&script).unwrap()); + let script = Script::from_file(Path::new("./script.yml")).unwrap(); for element in script.iter() { println!("{:?}", element); @@ -277,6 +313,14 @@ fn main() { window.set_width_request(800); window.set_height_request(600); window.present(); + + let _ = glib::spawn_future_local({ + let screen = screen.clone(); + async move { loop { + screen.queue_draw(); + async_std::task::sleep(Duration::from_millis(1000 / FPS)).await; + }} + }); }); app.run();