From 8f4a8b77bcf013faa3eefc90077a6e9fa1a73bf6 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Mon, 10 Apr 2023 01:18:26 -0400 Subject: [PATCH] Extract state management and add play and pause --- cyberpunk-splash/src/main.rs | 190 +++++++++++++++++++++++------------ 1 file changed, 123 insertions(+), 67 deletions(-) diff --git a/cyberpunk-splash/src/main.rs b/cyberpunk-splash/src/main.rs index e740699..211655c 100644 --- a/cyberpunk-splash/src/main.rs +++ b/cyberpunk-splash/src/main.rs @@ -7,6 +7,7 @@ use gtk::{gdk::Key, prelude::*, subclass::prelude::*, EventControllerKey}; use std::{ cell::RefCell, rc::Rc, + sync::{Arc, RwLock}, time::{Duration, Instant}, }; @@ -19,7 +20,85 @@ enum Event { Time(Duration), } -struct TimeoutAnimation { +#[derive(Clone, Copy, Debug)] +pub enum State { + Running { + last_update: Instant, + deadline: Instant, + timeout: Option, + }, + Paused { + time_remaining: Duration, + timeout: Option, + }, +} + +impl State { + fn new(countdown: Duration) -> Self { + Self::Paused { + time_remaining: countdown, + timeout: None, + } + } + + fn start(&mut self) { + if let Self::Paused { + time_remaining, + timeout, + } = self + { + *self = Self::Running { + last_update: Instant::now(), + deadline: Instant::now() + *time_remaining, + timeout: timeout.clone(), + }; + } + } + + fn pause(&mut self) { + if let Self::Running { + deadline, timeout, .. + } = self + { + *self = Self::Paused { + time_remaining: *deadline - Instant::now(), + timeout: timeout.clone(), + } + } + } + + fn start_pause(&mut self) { + match self { + Self::Running { .. } => self.pause(), + Self::Paused { .. } => self.start(), + } + } + + fn run(&mut self, now: Instant) { + if let Self::Running { + last_update, + deadline, + timeout, + } = self + { + *last_update = now; + if let Some(ref mut timeout) = timeout { + // TODO: figure out the actual number of frames + timeout.tick(1); + } + if *last_update > *deadline && timeout.is_none() { + *timeout = Some(TimeoutAnimation { + intensity: 1., + duration: 1., + ascending: false, + }); + } + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct TimeoutAnimation { intensity: f64, duration: f64, ascending: bool, @@ -46,13 +125,12 @@ impl TimeoutAnimation { pub struct SplashPrivate { text: Rc>, - time: Rc>, background: Rc>, time_extents: Rc>>, width: Rc>, height: Rc>, - timeout_animation: Rc>>, + state: Rc>, } impl SplashPrivate { @@ -60,8 +138,8 @@ impl SplashPrivate { *self.text.borrow_mut() = text; } - fn set_time(&self, time: Duration) { - *self.time.borrow_mut() = time; + fn set_state(&self, state: State) { + *self.state.borrow_mut() = state; } fn redraw_background(&self) { @@ -174,12 +252,12 @@ impl ObjectSubclass for SplashPrivate { SplashPrivate { text: Rc::new(RefCell::new(String::from(""))), - time: Rc::new(RefCell::new(Duration::ZERO)), background: Rc::new(RefCell::new(background)), time_extents: Rc::new(RefCell::new(None)), width: Rc::new(RefCell::new(WIDTH)), height: Rc::new(RefCell::new(HEIGHT)), - timeout_animation: Rc::new(RefCell::new(None)), + + state: Rc::new(RefCell::new(State::new(Duration::ZERO))), } } } @@ -192,13 +270,13 @@ glib::wrapper! { } impl Splash { - pub fn new(text: String, time: Duration) -> Self { + pub fn new(text: String, state: State) -> Self { let s: Self = Object::builder().build(); s.set_width_request(WIDTH); s.set_height_request(HEIGHT); s.imp().set_text(text); - s.imp().set_time(time); + s.imp().set_state(state); s.imp().redraw_background(); s.set_draw_func({ @@ -208,15 +286,18 @@ impl Splash { let _ = context.set_source(&*background); let _ = context.paint(); - let time = s.imp().time.borrow().clone(); + let state = s.imp().state.borrow().clone(); + + let time = match state { + State::Running { deadline, .. } => deadline - Instant::now(), + State::Paused { time_remaining, .. } => time_remaining, + }; let minutes = time.as_secs() / 60; let seconds = time.as_secs() % 60; let center_x = width as f64 / 2.; let center_y = height as f64 / 2.; - {} - { context.select_font_face( "Alegreya Sans SC", @@ -237,39 +318,30 @@ impl Splash { let time_baseline_x = center_x - time_extents.width() / 2.; let time_baseline_y = center_y + 100.; - /* - match *s.imp().timeout_animation.borrow() { - Some(ref animation) => { - context.set_source_rgb(animation.intensity / 2., 0., 0.); - RoundedRectangle { - x: time_baseline_x - 5., - y: time_baseline_y + 10., - width: time_extents.width() + 15., - height: time_extents.height() + 15., - } - .draw(&context); - // let _ = context.fill(); - } - None => {} - } - */ - let gradient = LinearGradient::new( time_baseline_x, time_baseline_y - time_extents.height(), time_baseline_x, time_baseline_y, ); - match *s.imp().timeout_animation.borrow() { + let (running, timeout_animation) = match state { + State::Running { timeout, .. } => (true, timeout.clone()), + State::Paused { timeout, .. } => (false, timeout.clone()), + }; + match timeout_animation { Some(ref animation) => { gradient.add_color_stop_rgba(0.2, 0.2, 0.0, 1.0, animation.intensity); gradient.add_color_stop_rgba(0.8, 0.7, 0.0, 1.0, animation.intensity); let _ = context.set_source(gradient); } None => { - gradient.add_color_stop_rgb(0.2, 0.2, 0.0, 1.0); - gradient.add_color_stop_rgb(0.8, 0.7, 0.0, 1.0); - let _ = context.set_source(gradient); + if running { + gradient.add_color_stop_rgb(0.2, 0.2, 0.0, 1.0); + gradient.add_color_stop_rgb(0.8, 0.7, 0.0, 1.0); + let _ = context.set_source(gradient); + } else { + context.set_source_rgb(0.3, 0.3, 0.3); + } } } context.move_to(time_baseline_x, time_baseline_y); @@ -304,28 +376,10 @@ impl Splash { s } - pub fn set_time(&self, time: Duration) { - self.imp().set_time(time); - if time == Duration::ZERO { - *self.imp().timeout_animation.borrow_mut() = Some(TimeoutAnimation { - intensity: 1., - duration: 1., - ascending: false, - }); - } + pub fn set_state(&self, state: State) { + self.imp().set_state(state); self.queue_draw(); } - - pub fn tick(&self, frames_elapsed: u8) { - let mut animation = self.imp().timeout_animation.borrow_mut(); - match *animation { - Some(ref mut animation) => { - animation.tick(frames_elapsed); - self.queue_draw(); - } - None => {} - } - } } struct AsymLineCutout { @@ -504,14 +558,13 @@ fn main() { app.connect_activate(move |app| { let (gtk_tx, gtk_rx) = - gtk::glib::MainContext::channel::(gtk::glib::PRIORITY_DEFAULT); + gtk::glib::MainContext::channel::(gtk::glib::PRIORITY_DEFAULT); let window = gtk::ApplicationWindow::new(app); window.present(); - let mut countdown = Duration::from_secs(5); - let mut next_tick = Instant::now() + Duration::from_secs(1); - - let splash = Splash::new("GTK Kifu".to_owned(), countdown.clone()); + let state = State::new(Duration::from_secs(5 * 60)); + let splash = Splash::new("GTK Kifu".to_owned(), state.clone()); + let state = Arc::new(RwLock::new(state)); window.set_child(Some(&splash)); @@ -522,6 +575,7 @@ fn main() { let keyboard_events = EventControllerKey::new(); keyboard_events.connect_key_released({ let window = window.clone(); + let state = state.clone(); move |_, key, _, _| { let name = key .name() @@ -529,28 +583,30 @@ fn main() { .unwrap_or("".to_owned()); match name.as_ref() { "Escape" => window.unfullscreen(), - "space" => println!("space pressed"), + "space" => state.write().unwrap().start_pause(), _ => {} } } }); window.add_controller(keyboard_events); - gtk_rx.attach(None, move |event| { + gtk_rx.attach(None, move |state| { + /* match event { Event::Frames(frames) => splash.tick(frames), Event::Time(time) => splash.set_time(time), }; + */ + splash.set_state(state); Continue(true) }); - std::thread::spawn(move || loop { - std::thread::sleep(Duration::from_millis(1000 / 60)); - let _ = gtk_tx.send(Event::Frames(1)); - if Instant::now() >= next_tick && countdown > Duration::from_secs(0) { - countdown = countdown - Duration::from_secs(1); - let _ = gtk_tx.send(Event::Time(countdown)); - next_tick = next_tick + Duration::from_secs(1); + std::thread::spawn(move || { + state.write().unwrap().start(); + loop { + std::thread::sleep(Duration::from_millis(1000 / 60)); + state.write().unwrap().run(Instant::now()); + let _ = gtk_tx.send(state.read().unwrap().clone()); } }); });