#![no_std] extern crate alloc; use alloc::boxed::Box; use az::*; use core::{ clone::Clone, cmp::PartialEq, default::Default, ops::{Add, Sub}, option::Option, }; use fixed::types::{I48F16, I8F8, U128F0, U16F0}; mod patterns; pub use patterns::*; mod types; pub use types::{BodyPattern, DashboardPattern, RGB}; fn calculate_frames(starting_time: U128F0, now: U128F0) -> U16F0 { let frames_128 = (now - starting_time) / U128F0::from(FPS); (frames_128 % U128F0::from(U16F0::MAX)).cast() } fn calculate_slope(start: I8F8, end: I8F8, frames: U16F0) -> I8F8 { let slope_i16f16 = (I48F16::from(end) - I48F16::from(start)) / I48F16::from(frames); slope_i16f16.saturating_as() } fn linear_ease(value: I8F8, frames: U16F0, slope: I8F8) -> I8F8 { let value_i16f16 = I48F16::from(value) + I48F16::from(frames) * I48F16::from(slope); value_i16f16.saturating_as() } #[derive(Clone, Copy)] pub struct Instant(pub U128F0); impl Default for Instant { fn default() -> Self { Self(U128F0::from(0 as u8)) } } impl Add for Instant { type Output = Self; fn add(self, r: Self) -> Self::Output { Self(self.0 + r.0) } } 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, body_lights: BodyPattern); } pub trait Animation { fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern); } pub struct DefaultAnimation {} impl Animation for DefaultAnimation { fn tick(&mut self, _: Instant) -> (DashboardPattern, BodyPattern) { (PRIDE_DASHBOARD, PRIDE_BODY) } } pub struct Fade { starting_dashboard: DashboardPattern, starting_lights: BodyPattern, start_time: Instant, dashboard_slope: [RGB; 3], body_slope: [RGB; 60], frames: U16F0, } impl Fade { fn new( dashboard: DashboardPattern, lights: BodyPattern, ending_dashboard: DashboardPattern, ending_lights: BodyPattern, frames: U16F0, 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: calculate_slope(dashboard[i].r, ending_dashboard[i].r, frames), g: calculate_slope(dashboard[i].g, ending_dashboard[i].g, frames), b: calculate_slope(dashboard[i].b, ending_dashboard[i].b, frames), }; dashboard_slope[i] = slope; } for i in 0..60 { let slope = RGB { r: calculate_slope(lights[i].r, ending_lights[i].r, frames), g: calculate_slope(lights[i].g, ending_lights[i].g, frames), b: calculate_slope(lights[i].b, ending_lights[i].b, frames), }; 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, BodyPattern) { let mut frames = calculate_frames(self.start_time.0, time.0); if frames > self.frames { frames = self.frames } let mut dashboard_pattern: DashboardPattern = OFF_DASHBOARD; let mut body_pattern: BodyPattern = OFF_BODY; for i in 0..3 { dashboard_pattern[i].r = linear_ease( self.starting_dashboard[i].r, frames, self.dashboard_slope[i].r, ); dashboard_pattern[i].g = linear_ease( self.starting_dashboard[i].g, frames, self.dashboard_slope[i].g, ); dashboard_pattern[i].b = linear_ease( self.starting_dashboard[i].b, frames, self.dashboard_slope[i].b, ); } for i in 0..60 { body_pattern[i].r = linear_ease(self.starting_lights[i].r, frames, self.body_slope[i].r); body_pattern[i].g = linear_ease(self.starting_lights[i].g, frames, self.body_slope[i].g); body_pattern[i].b = linear_ease(self.starting_lights[i].b, frames, self.body_slope[i].b); } (dashboard_pattern, body_pattern) } } #[derive(Debug)] pub enum FadeDirection { Transition, FadeIn, FadeOut, } pub enum BlinkerDirection { Left, Right, } pub struct Blinker { transition: Fade, fade_in: Fade, fade_out: Fade, direction: FadeDirection, start_time: Instant, frames: U16F0, } impl Blinker { fn new( starting_dashboard: DashboardPattern, starting_body: BodyPattern, home_dashboard: DashboardPattern, home_body: BodyPattern, direction: BlinkerDirection, time: Instant, ) -> Self { let mut ending_dashboard = home_dashboard.clone(); match direction { BlinkerDirection::Left => { ending_dashboard[0].r = LEFT_BLINKER_DASHBOARD[0].r; ending_dashboard[0].g = LEFT_BLINKER_DASHBOARD[0].g; ending_dashboard[0].b = LEFT_BLINKER_DASHBOARD[0].b; } BlinkerDirection::Right => { ending_dashboard[2].r = RIGHT_BLINKER_DASHBOARD[2].r; ending_dashboard[2].g = RIGHT_BLINKER_DASHBOARD[2].g; ending_dashboard[2].b = RIGHT_BLINKER_DASHBOARD[2].b; } } let mut ending_body = home_body.clone(); match direction { BlinkerDirection::Left => { for i in 0..30 { ending_body[i].r = LEFT_BLINKER_BODY[i].r; ending_body[i].g = LEFT_BLINKER_BODY[i].g; ending_body[i].b = LEFT_BLINKER_BODY[i].b; } } BlinkerDirection::Right => { for i in 30..60 { ending_body[i].r = RIGHT_BLINKER_BODY[i].r; ending_body[i].g = RIGHT_BLINKER_BODY[i].g; ending_body[i].b = RIGHT_BLINKER_BODY[i].b; } } } Blinker { transition: Fade::new( starting_dashboard.clone(), starting_body.clone(), ending_dashboard.clone(), ending_body.clone(), BLINKER_FRAMES, time, ), fade_in: Fade::new( home_dashboard.clone(), home_body.clone(), ending_dashboard.clone(), ending_body.clone(), BLINKER_FRAMES, time, ), fade_out: Fade::new( ending_dashboard.clone(), ending_body.clone(), home_dashboard.clone(), home_body.clone(), BLINKER_FRAMES, time, ), direction: FadeDirection::Transition, start_time: time, frames: BLINKER_FRAMES, } } } impl Animation for Blinker { fn tick(&mut self, time: Instant) -> (DashboardPattern, BodyPattern) { let frames = calculate_frames(self.start_time.0, time.0); if frames > self.frames { match self.direction { FadeDirection::Transition => { self.direction = FadeDirection::FadeOut; self.fade_out.start_time = time; } 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::Transition => self.transition.tick(time), 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, Copy, PartialEq)] pub enum Pattern { GayPride, TransPride, } impl Pattern { fn previous(&self) -> Pattern { match self { Pattern::GayPride => Pattern::TransPride, Pattern::TransPride => Pattern::GayPride, } } fn next(&self) -> Pattern { match self { Pattern::GayPride => Pattern::TransPride, Pattern::TransPride => Pattern::GayPride, } } fn dashboard(&self) -> DashboardPattern { match self { Pattern::GayPride => PRIDE_DASHBOARD, Pattern::TransPride => TRANS_PRIDE_DASHBOARD, } } fn body(&self) -> BodyPattern { match self { Pattern::GayPride => PRIDE_BODY, Pattern::TransPride => TRANS_PRIDE_BODY, } } } #[derive(Clone, PartialEq)] pub enum State { Pattern(Pattern), Brake, LeftBlinker, RightBlinker, BrakeLeftBlinker, BrakeRightBlinker, } pub struct App { ui: Box, state: State, home_pattern: Pattern, current_animation: Box, dashboard_lights: DashboardPattern, lights: BodyPattern, } impl App { pub fn new(ui: Box) -> Self { Self { ui, state: State::Pattern(Pattern::GayPride), home_pattern: Pattern::GayPride, current_animation: Box::new(DefaultAnimation {}), dashboard_lights: OFF_DASHBOARD, lights: OFF_BODY, } } fn update_animation(&mut self, time: Instant) { match self.state { State::Pattern(ref pattern) => { self.current_animation = Box::new(Fade::new( self.dashboard_lights.clone(), self.lights.clone(), pattern.dashboard(), pattern.body(), DEFAULT_FRAMES, time, )) } State::Brake => { self.current_animation = Box::new(Fade::new( self.dashboard_lights.clone(), self.lights.clone(), BRAKES_DASHBOARD, BRAKES_BODY, BRAKES_FRAMES, time, )); } State::LeftBlinker => { self.current_animation = Box::new(Blinker::new( self.dashboard_lights.clone(), self.lights.clone(), self.home_pattern.dashboard(), self.home_pattern.body(), BlinkerDirection::Left, time, )); } State::RightBlinker => { self.current_animation = Box::new(Blinker::new( self.dashboard_lights.clone(), self.lights.clone(), self.home_pattern.dashboard(), self.home_pattern.body(), 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 = State::Pattern(self.home_pattern); } else { self.state = State::Brake; } } Event::BrakeRelease => self.state = State::Pattern(self.home_pattern), Event::LeftBlinker => match self.state { State::Brake => self.state = State::BrakeLeftBlinker, State::BrakeLeftBlinker => self.state = State::Brake, State::LeftBlinker => self.state = State::Pattern(self.home_pattern), _ => self.state = State::LeftBlinker, }, Event::NextPattern => match self.state { State::Pattern(ref pattern) => { self.home_pattern = pattern.next(); self.state = State::Pattern(self.home_pattern); } _ => (), }, Event::PreviousPattern => match self.state { State::Pattern(ref pattern) => { self.home_pattern = pattern.previous(); self.state = State::Pattern(self.home_pattern); } _ => (), }, Event::RightBlinker => match self.state { State::Brake => self.state = State::BrakeRightBlinker, State::BrakeRightBlinker => self.state = State::Brake, State::RightBlinker => self.state = State::Pattern(self.home_pattern), _ => 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); } }