From 7d6413c91d026bf801fcdee3d978818076edb81d Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 26 Nov 2023 23:30:45 -0500 Subject: [PATCH] Set up a bunch of animations and some state transitions! --- bike-lights/core/src/lib.rs | 286 +++++++++++++++++++++++++++++- bike-lights/core/src/patterns.rs | 284 ++++++++++++++++++++++++++++- bike-lights/core/src/types.rs | 14 +- bike-lights/simulator/src/main.rs | 22 ++- 4 files changed, 580 insertions(+), 26 deletions(-) diff --git a/bike-lights/core/src/lib.rs b/bike-lights/core/src/lib.rs index d9b3c06..d7ef41b 100644 --- a/bike-lights/core/src/lib.rs +++ b/bike-lights/core/src/lib.rs @@ -1,28 +1,213 @@ -use std::time::Duration; - mod patterns; pub use patterns::*; mod types; pub use types::{DashboardPattern, Pattern, RGB}; +pub const FPS: u8 = 30; + pub trait UI { fn check_event(&self) -> Option; fn update_lights(&self, dashboard_lights: DashboardPattern, lights: Pattern); } pub trait Animation { - fn tick(&self, time: std::time::Instant) -> (DashboardPattern, Pattern); + fn tick(&mut self, time: std::time::Instant) -> (DashboardPattern, Pattern); } pub struct DefaultAnimation {} impl Animation for DefaultAnimation { - fn tick(&self, time: std::time::Instant) -> (DashboardPattern, Pattern) { + fn tick(&mut self, _: std::time::Instant) -> (DashboardPattern, Pattern) { (PRIDE_DASHBOARD, PRIDE) } } +pub struct Fade { + starting_dashboard: DashboardPattern, + starting_lights: Pattern, + + start_time: std::time::Instant, + dashboard_slope: Vec>, + body_slope: Vec>, + frames: u8, +} + +impl Fade { + fn new( + dashboard: DashboardPattern, + lights: Pattern, + ending_dashboard: DashboardPattern, + ending_lights: Pattern, + frames: u8, + time: std::time::Instant, + ) -> Self { + let mut dashboard_slope = Vec::new(); + let mut body_slope = Vec::new(); + for i in 0..3 { + let slope = RGB { + r: (ending_dashboard[i].r as f64 - dashboard[i].r as f64) / frames as f64, + g: (ending_dashboard[i].g as f64 - dashboard[i].g as f64) / frames as f64, + b: (ending_dashboard[i].b as f64 - dashboard[i].b as f64) / frames as f64, + }; + dashboard_slope.push(slope); + } + + for i in 0..60 { + let slope = RGB { + r: (ending_lights[i].r as f64 - lights[i].r as f64) / frames as f64, + g: (ending_lights[i].g as f64 - lights[i].g as f64) / frames as f64, + b: (ending_lights[i].b as f64 - lights[i].b as f64) / frames as f64, + }; + body_slope.push(slope); + } + + println!("dashboard slope: {:?}", dashboard_slope); + + Self { + starting_dashboard: dashboard, + starting_lights: lights, + start_time: time, + dashboard_slope, + body_slope, + frames, + } + } +} + +impl Animation for Fade { + fn tick(&mut self, time: std::time::Instant) -> (DashboardPattern, Pattern) { + let mut frames: u8 = ((time - self.start_time).as_millis() as f64 / FPS as f64) as u8; + if frames > self.frames { + frames = self.frames + } + let mut dashboard_pattern: DashboardPattern = OFF_DASHBOARD; + let mut body_pattern: Pattern = OFF; + + let apply = |value: f64, frames: f64, slope: f64| -> f64 { value + frames * slope }; + + for i in 0..3 { + dashboard_pattern[i].r = apply( + self.starting_dashboard[i].r as f64, + frames as f64, + self.dashboard_slope[i].r as f64, + ) as u8; + dashboard_pattern[i].g = apply( + self.starting_dashboard[i].g as f64, + frames as f64, + self.dashboard_slope[i].g as f64, + ) as u8; + dashboard_pattern[i].b = apply( + self.starting_dashboard[i].b as f64, + frames as f64, + self.dashboard_slope[i].b as f64, + ) as u8; + } + + for i in 0..60 { + body_pattern[i].r = apply( + self.starting_lights[i].r as f64, + frames as f64, + self.body_slope[i].r as f64, + ) as u8; + body_pattern[i].g = apply( + self.starting_lights[i].g as f64, + frames as f64, + self.body_slope[i].g as f64, + ) as u8; + body_pattern[i].b = apply( + self.starting_lights[i].b as f64, + frames as f64, + self.body_slope[i].b as f64, + ) as u8; + } + + (dashboard_pattern, body_pattern) + } +} + +#[derive(Debug)] +pub enum FadeDirection { + FadeIn, + FadeOut, +} + +pub struct LeftBlinker { + fade_in: Fade, + fade_out: Fade, + direction: FadeDirection, + + start_time: std::time::Instant, + frames: u8, +} + +impl LeftBlinker { + fn new( + starting_dashboard: DashboardPattern, + starting_body: Pattern, + time: std::time::Instant, + ) -> Self { + let mut ending_dashboard = starting_dashboard.clone(); + ending_dashboard[0].r = LEFT_DASHBOARD[0].r; + ending_dashboard[0].g = LEFT_DASHBOARD[0].g; + ending_dashboard[0].b = LEFT_DASHBOARD[0].b; + + let mut ending_body = starting_body.clone(); + for i in 0..30 { + ending_body[i].r = LEFT[i].r; + ending_body[i].g = LEFT[i].g; + ending_body[i].b = LEFT[i].b; + } + + LeftBlinker { + fade_in: Fade::new( + starting_dashboard.clone(), + starting_body.clone(), + ending_dashboard.clone(), + ending_body.clone(), + LEFT_FRAMES, + time, + ), + fade_out: Fade::new( + ending_dashboard.clone(), + ending_body.clone(), + starting_dashboard.clone(), + starting_body.clone(), + LEFT_FRAMES, + time, + ), + direction: FadeDirection::FadeIn, + start_time: time, + frames: LEFT_FRAMES, + } + } +} + +impl Animation for LeftBlinker { + fn tick(&mut self, time: std::time::Instant) -> (DashboardPattern, Pattern) { + let frames: u8 = ((time - self.start_time).as_millis() as f64 / FPS as f64) as u8; + if frames > self.frames { + match self.direction { + FadeDirection::FadeIn => { + self.direction = FadeDirection::FadeOut; + self.fade_out.start_time = time; + } + FadeDirection::FadeOut => { + self.direction = FadeDirection::FadeIn; + self.fade_in.start_time = time; + } + } + self.start_time = time; + } + println!("anim: {:?} {}", self.direction, frames); + + match self.direction { + FadeDirection::FadeIn => self.fade_in.tick(time), + FadeDirection::FadeOut => self.fade_out.tick(time), + } + } +} + #[derive(Clone, Debug)] pub enum Event { Brake, @@ -33,7 +218,7 @@ pub enum Event { RightBlinker, } -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub enum State { Pattern(u8), Brake, @@ -46,6 +231,7 @@ pub enum State { pub struct App { ui: Box, state: State, + home_state: State, current_animation: Box, dashboard_lights: DashboardPattern, lights: Pattern, @@ -56,17 +242,103 @@ impl App { Self { ui, state: State::Pattern(0), + home_state: State::Pattern(0), current_animation: Box::new(DefaultAnimation {}), dashboard_lights: OFF_DASHBOARD, lights: OFF, } } + fn update_state(&mut self, time: std::time::Instant) { + match self.state { + State::Pattern(0) => { + self.current_animation = Box::new(Fade::new( + self.dashboard_lights.clone(), + self.lights.clone(), + PRIDE_DASHBOARD, + PRIDE, + DEFAULT_FRAMES, + time, + )) + } + State::Pattern(1) => { + self.current_animation = Box::new(Fade::new( + self.dashboard_lights.clone(), + self.lights.clone(), + TRANS_PRIDE_DASHBOARD, + TRANS_PRIDE, + DEFAULT_FRAMES, + time, + )) + } + State::Pattern(_) => {} + State::Brake => { + self.current_animation = Box::new(Fade::new( + self.dashboard_lights.clone(), + self.lights.clone(), + BRAKE_DASHBOARD, + BRAKES, + BRAKE_FRAMES, + time, + )); + } + State::LeftBlinker => { + self.current_animation = Box::new(LeftBlinker::new( + self.dashboard_lights.clone(), + self.lights.clone(), + time, + )); + } + State::RightBlinker => (), + State::BrakeLeftBlinker => (), + State::BrakeRightBlinker => (), + } + } + pub fn tick(&mut self, time: std::time::Instant) { match self.ui.check_event() { - Some(event) => println!("event received: {:?}", event), - None => (), + Some(event) => { + match event { + Event::Brake => { + if self.state == State::Brake { + self.state = self.home_state.clone(); + } else { + self.state = State::Brake; + } + } + Event::BrakeRelease => self.state = State::Pattern(0), + Event::LeftBlinker => match self.state { + State::Brake => self.state = State::BrakeLeftBlinker, + State::BrakeLeftBlinker => self.state = State::Brake, + State::LeftBlinker => self.state = self.home_state.clone(), + _ => self.state = State::LeftBlinker, + }, + Event::NextPattern => match self.state { + State::Pattern(i) => { + let next = i + 1; + self.state = State::Pattern(if next > 1 { 0 } else { next }); + self.home_state = self.state.clone(); + } + _ => (), + }, + Event::PreviousPattern => match self.state { + State::Pattern(i) => { + if i == 0 { + self.state = State::Pattern(1); + } else { + self.state = State::Pattern(i - 1); + } + self.home_state = self.state.clone(); + } + _ => (), + }, + Event::RightBlinker => {} + } + self.update_state(time); + } + None => {} }; + let (dashboard, lights) = self.current_animation.tick(time); self.dashboard_lights = dashboard.clone(); self.lights = lights.clone(); diff --git a/bike-lights/core/src/patterns.rs b/bike-lights/core/src/patterns.rs index 063b4df..697e087 100644 --- a/bike-lights/core/src/patterns.rs +++ b/bike-lights/core/src/patterns.rs @@ -1,7 +1,7 @@ use crate::RGB; -pub type DashboardPattern = [RGB; 3]; -pub type Pattern = [RGB; 60]; +pub type DashboardPattern = [RGB; 3]; +pub type Pattern = [RGB; 60]; pub const OFF_DASHBOARD: DashboardPattern = [ RGB { r: 0, g: 0, b: 0 }, @@ -72,17 +72,19 @@ pub const OFF: Pattern = [ RGB { r: 0, g: 0, b: 0 }, ]; +pub const DEFAULT_FRAMES: u8 = 30; + pub const PRIDE_DASHBOARD: DashboardPattern = [ RGB { r: 228, g: 3, b: 3 }, RGB { - r: 255, - g: 237, - b: 0, + r: 0, + g: 128, + b: 38, }, RGB { - r: 115, - g: 41, - b: 130, + r: 36, + g: 64, + b: 142, }, ]; @@ -669,3 +671,269 @@ pub const TRANS_PRIDE: Pattern = [ b: 250, }, ]; + +pub const BRAKE_FRAMES: u8 = 15; + +pub const BRAKE_DASHBOARD: DashboardPattern = [ + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, +]; + +pub const BRAKES: Pattern = [ + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, + RGB { r: 128, g: 0, b: 0 }, +]; + +pub const LEFT_FRAMES: u8 = 15; + +pub const LEFT_DASHBOARD: DashboardPattern = [ + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, +]; + +pub const LEFT: Pattern = [ + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { + r: 255, + g: 191, + b: 0, + }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, + RGB { r: 0, g: 0, b: 0 }, +]; diff --git a/bike-lights/core/src/types.rs b/bike-lights/core/src/types.rs index 07b1b8d..b3a8f4b 100644 --- a/bike-lights/core/src/types.rs +++ b/bike-lights/core/src/types.rs @@ -1,9 +1,9 @@ -#[derive(Clone)] -pub struct RGB { - pub r: u8, - pub g: u8, - pub b: u8, +#[derive(Clone, Default, Debug)] +pub struct RGB { + pub r: T, + pub g: T, + pub b: T, } -pub type DashboardPattern = [RGB; 3]; -pub type Pattern = [RGB; 60]; +pub type DashboardPattern = [RGB; 3]; +pub type Pattern = [RGB; 60]; diff --git a/bike-lights/simulator/src/main.rs b/bike-lights/simulator/src/main.rs index be422e2..edba09a 100644 --- a/bike-lights/simulator/src/main.rs +++ b/bike-lights/simulator/src/main.rs @@ -1,7 +1,7 @@ use adw::prelude::*; use glib::{Object, Sender}; use gtk::subclass::prelude::*; -use lights_core::{App, DashboardPattern, Event, Pattern, RGB, UI}; +use lights_core::{App, DashboardPattern, Event, Pattern, FPS, RGB, UI}; use std::{ cell::RefCell, env, @@ -73,7 +73,7 @@ impl DashboardLights { s } - pub fn set_lights(&self, lights: [RGB; 3]) { + pub fn set_lights(&self, lights: [RGB; 3]) { *self.imp().lights.borrow_mut() = lights; self.queue_draw(); } @@ -200,7 +200,7 @@ impl BikeLights { s } - pub fn set_lights(&self, lights: [RGB; 60]) { + pub fn set_lights(&self, lights: [RGB; 60]) { *self.imp().lights.borrow_mut() = lights; self.queue_draw(); } @@ -247,7 +247,7 @@ fn main() { })); loop { bike_app.tick(std::time::Instant::now()); - std::thread::sleep(std::time::Duration::from_millis(100)); + std::thread::sleep(std::time::Duration::from_millis(1000 / (FPS as u64))); } }); @@ -303,6 +303,20 @@ fn main() { let previous_pattern = gtk::Button::builder().label("Previous").build(); let next_pattern = gtk::Button::builder().label("Next").build(); + previous_pattern.connect_clicked({ + let event_tx = event_tx.clone(); + move |_| { + let _ = event_tx.send(Event::PreviousPattern); + } + }); + + next_pattern.connect_clicked({ + let event_tx = event_tx.clone(); + move |_| { + let _ = event_tx.send(Event::NextPattern); + } + }); + pattern_controls.append(&previous_pattern); pattern_controls.append(&next_pattern); layout.append(&pattern_controls);