spatial_led/driver/
mod.rs

1use core::time::Duration;
2
3use alloc::boxed::Box;
4
5use crate::{color::ColorType, time::Instant, Led, Sled, SledError, Vec2};
6
7/// A driver representing instants with `std::time::Instant`
8#[cfg(feature = "std")]
9pub type Driver<COLOR> = CustomDriver<std::time::Instant, COLOR>;
10
11mod data;
12pub use data::Data;
13
14#[derive(Clone, Debug)]
15pub struct Time {
16    pub elapsed: Duration,
17    pub delta: Duration,
18}
19
20type SledResult = Result<(), SledError>;
21
22/// Drivers are useful for encapsulating everything you need to drive a complicated lighting effect all in one place.
23pub struct CustomDriver<INSTANT, COLOR>
24where
25    INSTANT: Instant,
26    COLOR: ColorType,
27{
28    sled: Option<Sled<COLOR>>,
29    startup_commands: Box<dyn Fn(&mut Sled<COLOR>, &mut Data) -> SledResult>,
30    compute_commands: Box<dyn Fn(&Sled<COLOR>, &mut Data, &Time) -> SledResult>,
31    draw_commands: Box<dyn Fn(&mut Sled<COLOR>, &Data, &Time) -> SledResult>,
32    startup: INSTANT,
33    last_update: INSTANT,
34
35    data: Data,
36}
37
38impl<INSTANT, COLOR> CustomDriver<INSTANT, COLOR>
39where
40    INSTANT: Instant,
41    COLOR: ColorType,
42{
43    pub fn new() -> Self {
44        CustomDriver {
45            sled: None,
46            startup_commands: Box::new(|_, _| Ok(())),
47            compute_commands: Box::new(|_, _, _| Ok(())),
48            draw_commands: Box::new(|_, _, _| Ok(())),
49            startup: INSTANT::now(),
50            last_update: INSTANT::now(),
51            data: Data::new(),
52        }
53    }
54
55    /// Returns `Some(&Sled)` if the Driver has been mounted, `None` if it hasn't.
56    pub fn sled(&self) -> Option<&Sled<COLOR>> {
57        self.sled.as_ref()
58    }
59
60    /// Returns a duration representing how long it has been since the Driver was initially [mounted](Driver::mount).
61    pub fn elapsed(&self) -> Duration {
62        self.startup.elapsed()
63    }
64
65    /// Define commands to be called as soon as a Sled is [mounted](CustomDriver::mount) to the driver. This is a good place to initialize important data.
66    /// ```rust
67    /// # use spatial_led::{Vec2, Sled, SledResult, driver::{Driver, Data}};
68    /// # use palette::rgb::Rgb;
69    ///
70    /// fn startup(_: &mut Sled<Rgb>, data: &mut Data) -> SledResult {
71    ///     let streak_positions = vec![
72    ///         Vec2::new(-1.2, 0.3),
73    ///         Vec2::new(0.9, 1.6),
74    ///         Vec2::new(0.4, -2.3),
75    ///     ];
76    ///     
77    ///     data.set("positions", streak_positions);
78    ///     Ok(())
79    /// }
80    ///
81    /// pub fn main() {
82    ///     let mut driver = Driver::<Rgb>::new();
83    ///     driver.set_startup_commands(startup);
84    /// }
85    /// ```
86    pub fn set_startup_commands<F: Fn(&mut Sled<COLOR>, &mut Data) -> SledResult + 'static>(
87        &mut self,
88        startup_commands: F,
89    ) {
90        self.startup_commands = Box::new(startup_commands);
91    }
92
93    /// Define commands to be called each time [CustomDriver::step()] is called, right before we run [draw commands](CustomDriver::set_draw_commands).
94    /// ```rust
95    ///# use spatial_led::{Vec2, Sled, SledResult, driver::{Driver, Data, Time}};
96    ///# use palette::rgb::Rgb;
97    /// const WIND: Vec2 = Vec2::new(0.25, 1.5);
98    ///
99    /// fn compute(_: &Sled<Rgb>, data: &mut Data, time: &Time) -> SledResult {
100    ///     let streak_positions: &mut Vec<Vec2> = data.get_mut("positions")?;
101    ///     let elapsed = time.elapsed.as_secs_f32();
102    ///     for p in streak_positions {
103    ///         *p += WIND * elapsed
104    ///     }
105    ///    Ok(())
106    /// }
107    ///
108    /// pub fn main() {
109    ///     let mut driver = Driver::<Rgb>::new();
110    ///     driver.set_compute_commands(compute);
111    /// }
112    ///
113    /// ```
114    pub fn set_compute_commands<F: Fn(&Sled<COLOR>, &mut Data, &Time) -> SledResult + 'static>(
115        &mut self,
116        compute_commands: F,
117    ) {
118        self.compute_commands = Box::new(compute_commands);
119    }
120
121    /// Define commands to be called each time [CustomDriver::step()] is called, right after we run [compute commands](CustomDriver::set_compute_commands).
122    /// ```rust
123    /// # use spatial_led::{Sled, Vec2, SledResult, driver::{Driver, Time, Data}};
124    /// # use palette::rgb::Rgb;
125    ///
126    /// fn draw(sled: &mut Sled<Rgb>, data: &Data, _:&Time) -> SledResult {
127    ///     // gradually fade all LEDs to black
128    ///     sled.map(|led| led.color * 0.95);
129    ///
130    ///     // For each position in our vector, draw white in the direction to it.
131    ///     let streak_positions = data.get::<Vec<Vec2>>("positions")?;
132    ///     let center = sled.center_point();
133    ///     for pos in streak_positions {
134    ///         let dir = (pos - center).normalize();
135    ///         sled.set_at_dir(dir, Rgb::new(1.0, 1.0, 1.0));
136    ///     }
137    ///    Ok(())
138    /// }
139    ///
140    /// pub fn main() {
141    ///     let mut driver = Driver::new();
142    ///     driver.set_draw_commands(draw);
143    /// }
144    ///
145    /// ```
146    pub fn set_draw_commands<F: Fn(&mut Sled<COLOR>, &Data, &Time) -> SledResult + 'static>(
147        &mut self,
148        draw_commands: F,
149    ) {
150        self.draw_commands = Box::new(draw_commands);
151    }
152
153    /// Takes ownership of the given Sled and runs the Driver's [startup commands](Driver::set_startup_commands).
154    pub fn mount(&mut self, mut sled: Sled<COLOR>) {
155        (self.startup_commands)(&mut sled, &mut self.data).unwrap();
156        self.startup = INSTANT::now();
157        self.last_update = self.startup;
158        self.sled = Some(sled);
159    }
160
161    /// Runs the Driver's [compute commands](CustomDriver::set_compute_commands) first, and then runs its [draw commands](CustomDriver::set_draw_commands).
162    pub fn step(&mut self) {
163        if let Some(sled) = &mut self.sled {
164            let time = Time {
165                elapsed: self.startup.elapsed(),
166                delta: self.last_update.elapsed(),
167            };
168
169            self.last_update = INSTANT::now();
170            (self.compute_commands)(sled, &mut self.data, &time).unwrap();
171            (self.draw_commands)(sled, &self.data, &time).unwrap();
172        }
173    }
174
175    pub fn step_by(&mut self, delta: Duration) {
176        self.startup -= delta;
177        self.step();
178    }
179
180    /// Returns full ownership over the Driver's assigned Sled. Panics if [Driver::mount()] was never called.
181    pub fn dismount(&mut self) -> Sled<COLOR> {
182        self.sled.take().unwrap()
183    }
184
185    /// See [Sled::leds()].
186    pub fn leds(&self) -> impl Iterator<Item = &Led<COLOR>> {
187        if let Some(sled) = &self.sled {
188            sled.leds()
189        } else {
190            panic!("Driver has no Sled assigned!")
191        }
192    }
193
194    /// See [Sled::colors()].
195    pub fn colors(&self) -> impl Iterator<Item = &COLOR> + '_ {
196        if let Some(sled) = &self.sled {
197            sled.colors()
198        } else {
199            panic!("Driver has no Sled assigned!")
200        }
201    }
202
203    /// See [Sled::positions()].
204    pub fn positions(&self) -> impl Iterator<Item = Vec2> + '_ {
205        if let Some(sled) = &self.sled {
206            sled.positions()
207        } else {
208            panic!("Driver has no Sled assigned!")
209        }
210    }
211
212    pub fn colors_and_positions(&self) -> impl Iterator<Item = (COLOR, Vec2)> + '_ {
213        if let Some(sled) = &self.sled {
214            sled.colors_and_positions()
215        } else {
216            panic!("Driver has no Sled assigned!")
217        }
218    }
219
220    /// Returns a reference to the Driver's Data. Helpful for displaying data values to the program user.
221    pub fn data(&self) -> &Data {
222        &self.data
223    }
224
225    /// Returns a mutable reference to the Driver's Data. Helpful for changing data values as the user provides input to the program.
226    pub fn data_mut(&mut self) -> &mut Data {
227        &mut self.data
228    }
229}
230
231impl<INSTANT, COLOR> Default for CustomDriver<INSTANT, COLOR>
232where
233    INSTANT: Instant,
234    COLOR: ColorType,
235{
236    fn default() -> Self {
237        Self::new()
238    }
239}