#![no_std] extern crate alloc; use alloc::boxed::Box; use core::{clone::Clone, cmp::PartialEq, ops::Sub, option::Option}; mod patterns; pub use patterns::*; mod types; pub use types::{DashboardPattern, Pattern, RGB}; #[derive(Clone, Copy)] pub struct Instant(pub u128); impl Sub for Instant { type Output = Self; fn sub(self, r: Self) -> Self::Output { Self(self.0 - r.0) } } 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(&mut self, time: Instant) -> (DashboardPattern, Pattern); } pub struct DefaultAnimation {} impl Animation for DefaultAnimation { fn tick(&mut self, _: Instant) -> (DashboardPattern, Pattern) { (PRIDE_DASHBOARD, PRIDE) } } pub struct Fade { starting_dashboard: DashboardPattern, starting_lights: Pattern, start_time: Instant, dashboard_slope: [RGB; 3], body_slope: [RGB; 60], frames: u8, } impl Fade { fn new( dashboard: DashboardPattern, lights: Pattern, ending_dashboard: DashboardPattern, ending_lights: Pattern, frames: u8, time: Instant, ) -> Self { let mut dashboard_slope = [Default::default(); 3]; let mut body_slope = [Default::default(); 60]; 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[i] = 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[i] = 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: Instant) -> (DashboardPattern, Pattern) { let mut frames: u8 = ((time - self.start_time).0 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 enum BlinkerDirection { Left, Right, } pub struct Blinker { fade_in: Fade, fade_out: Fade, direction: FadeDirection, start_time: Instant, frames: u8, } impl Blinker { fn new( starting_dashboard: DashboardPattern, starting_body: Pattern, direction: BlinkerDirection, time: Instant, ) -> Self { let mut ending_dashboard = starting_dashboard.clone(); match direction { BlinkerDirection::Left => { ending_dashboard[0].r = LEFT_DASHBOARD[0].r; ending_dashboard[0].g = LEFT_DASHBOARD[0].g; ending_dashboard[0].b = LEFT_DASHBOARD[0].b; } BlinkerDirection::Right => { ending_dashboard[2].r = RIGHT_DASHBOARD[2].r; ending_dashboard[2].g = RIGHT_DASHBOARD[2].g; ending_dashboard[2].b = RIGHT_DASHBOARD[2].b; } } let mut ending_body = starting_body.clone(); match direction { BlinkerDirection::Left => { 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; } } BlinkerDirection::Right => { for i in 30..60 { ending_body[i].r = RIGHT[i].r; ending_body[i].g = RIGHT[i].g; ending_body[i].b = RIGHT[i].b; } } } Blinker { fade_in: Fade::new( starting_dashboard.clone(), starting_body.clone(), ending_dashboard.clone(), ending_body.clone(), BLINKER_FRAMES, time, ), fade_out: Fade::new( ending_dashboard.clone(), ending_body.clone(), starting_dashboard.clone(), starting_body.clone(), BLINKER_FRAMES, time, ), direction: FadeDirection::FadeIn, start_time: time, frames: BLINKER_FRAMES, } } } impl Animation for Blinker { fn tick(&mut self, time: Instant) -> (DashboardPattern, Pattern) { let frames: u8 = ((time - self.start_time).0 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; } match self.direction { FadeDirection::FadeIn => self.fade_in.tick(time), FadeDirection::FadeOut => self.fade_out.tick(time), } } } #[derive(Clone, Debug)] pub enum Event { Brake, BrakeRelease, LeftBlinker, NextPattern, PreviousPattern, RightBlinker, } #[derive(Clone, PartialEq)] pub enum State { Pattern(u8), Brake, LeftBlinker, RightBlinker, BrakeLeftBlinker, BrakeRightBlinker, } pub struct App { ui: Box, state: State, home_state: State, current_animation: Box, dashboard_lights: DashboardPattern, lights: Pattern, } impl App { pub fn new(ui: Box) -> Self { Self { ui, state: State::Pattern(0), home_state: State::Pattern(0), current_animation: Box::new(DefaultAnimation {}), dashboard_lights: OFF_DASHBOARD, lights: OFF, } } fn update_animation(&mut self, 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(Blinker::new( self.dashboard_lights.clone(), self.lights.clone(), BlinkerDirection::Left, time, )); } State::RightBlinker => { self.current_animation = Box::new(Blinker::new( self.dashboard_lights.clone(), self.lights.clone(), BlinkerDirection::Right, time, )); } State::BrakeLeftBlinker => (), State::BrakeRightBlinker => (), } } fn update_state(&mut self, event: 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 = self.home_state.clone(), 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 => match self.state { State::Brake => self.state = State::BrakeRightBlinker, State::BrakeRightBlinker => self.state = State::Brake, State::RightBlinker => self.state = self.home_state.clone(), _ => self.state = State::RightBlinker, }, } } pub fn tick(&mut self, time: Instant) { match self.ui.check_event() { Some(event) => { self.update_state(event); self.update_animation(time); } None => {} }; let (dashboard, lights) = self.current_animation.tick(time); self.dashboard_lights = dashboard.clone(); self.lights = lights.clone(); self.ui.update_lights(dashboard, lights); } }