figments_render/
smart_leds.rs

1use core::marker::Copy;
2use core::convert::AsRef;
3use core::result::Result;
4use core::iter::Iterator;
5
6use smart_leds_trait::{SmartLedsWrite, SmartLedsWriteAsync};
7
8use figments::{mappings::linear::LinearSpace, prelude::*};
9
10use crate::{gamma::{GammaCurve, WithGamma}, output::{Brightness, GammaCorrected, Output, OutputAsync}, power::*};
11
12#[derive(Debug)]
13pub struct PowerControls {
14    max_mw: u32,
15    brightness: u8,
16    is_on: bool,
17    gamma_curve: GammaCurve,
18    cur_mw: u32
19}
20
21impl PowerControls {
22    pub fn new(max_mw: u32) -> Self {
23        Self {
24            max_mw,
25            brightness: 255,
26            is_on: true,
27            gamma_curve: GammaCurve::default(),
28            cur_mw: 0
29        }
30    }
31
32    pub fn iter_brightness<'a, Color, P: AsRef<[Color]> + ?Sized>(&'a mut self, pixbuf: &'a P) -> impl Iterator<Item = Color> + use<'a, Color, P> where Color: 'a + Copy + WithGamma + AsMilliwatts + Fract8Ops {
33        self.cur_mw = pixbuf.as_ref().iter().map(|x| { x.with_gamma(&self.gamma_curve).as_milliwatts() }).sum();
34        let b = brightness_for_mw(self.cur_mw, self.brightness, self.max_mw);
35        pixbuf.as_ref().iter().map(move |x| { x.with_gamma(&self.gamma_curve).scale8(b) })
36    }
37}
38
39impl Brightness for PowerControls {
40    fn set_brightness(&mut self, brightness: u8) {
41        self.brightness = brightness;
42    }
43
44    fn set_on(&mut self, is_on: bool) {
45        self.is_on = is_on;
46    }
47}
48
49impl GammaCorrected for PowerControls {
50    fn set_gamma(&mut self, gamma: GammaCurve) {
51        self.gamma_curve = gamma
52    }
53}
54
55#[derive(Debug)]
56pub struct PowerManagedWriter<T> {
57    target: T,
58    controls: PowerControls
59}
60
61impl<T> PowerManagedWriter<T> {
62    pub fn new(target: T, max_mw: u32) -> Self {
63        Self {
64            target,
65            controls: PowerControls::new(max_mw)
66        }
67    }
68
69    pub fn write<P: AsRef<[T::Color]> + ?Sized>(&mut self, pixbuf: &P) -> Result<(), T::Error> where T: SmartLedsWrite, T::Color: Fract8Ops + Copy + WithGamma + AsMilliwatts + core::fmt::Debug {
70        if self.controls.is_on {
71            self.target.write(self.controls.iter_brightness(pixbuf))
72        } else {
73            self.target.write(pixbuf.as_ref().iter().map(|x| { x.scale8(0) }))
74        }
75    }
76
77
78    pub async fn write_async<P: AsRef<[T::Color]> + ?Sized>(&mut self, pixbuf: &P) -> Result<(), T::Error> where T: SmartLedsWriteAsync, T::Color: Fract8Ops + Copy + WithGamma + AsMilliwatts + core::fmt::Debug {
79        if self.controls.is_on {
80            self.target.write(self.controls.iter_brightness(pixbuf)).await
81        } else {
82            self.target.write(pixbuf.as_ref().iter().map(|x| { x.scale8(0) })).await
83        }
84    }
85
86    pub fn controls(&mut self) -> &mut PowerControls {
87        &mut self.controls
88    }
89
90    /// Returns the total power required to display the previous write at full brightness. This is /not/ the actual power consumption, only a theoretical maximum useful for designing power supplies.
91    pub const fn max_mw(&self) -> u32 {
92        self.controls.cur_mw
93    }
94}
95
96pub struct SmartLedsOutput<'a, T, Pixbuf> {
97    writer: PowerManagedWriter<T>,
98    pixbuf: &'a mut Pixbuf,
99    buf_idx: usize,
100    clip: Rectangle<LinearSpace>
101}
102
103impl<'a, T, Pixel, const PIXEL_COUNT: usize> SmartLedsOutput<'a, T, [Pixel; PIXEL_COUNT]> {
104    pub fn new(target: T, pixbuf: &'a mut [Pixel; PIXEL_COUNT], max_mw: u32) -> Self {
105        Self {
106            writer: PowerManagedWriter::new(target, max_mw),
107            pixbuf,
108            buf_idx: 0,
109            clip: Rectangle::everything()
110        }
111    }
112
113    pub const fn pixbuf(&mut self) -> &mut [Pixel; PIXEL_COUNT] {
114        self.pixbuf
115    }
116
117    pub fn set_clip(&mut self, clip: Rectangle<LinearSpace>) {
118        self.clip = clip;
119    }
120
121    // TODO: We could just put this into a DoubleBufferedPixbuf, then there isn't a need to call this ever with SmartLedsOutput, as you could do output.pixbuf().swap(&mut next) with that.
122    pub fn swap_buffer(&mut self, pixbuf: &'a mut [Pixel; PIXEL_COUNT]) -> &'a mut [Pixel; PIXEL_COUNT] {
123        self.buf_idx = (self.buf_idx + 1) % self.pixbuf.as_ref().len();
124        core::mem::replace(&mut self.pixbuf, pixbuf)
125    }
126}
127
128impl<'a, T: SmartLedsWrite + 'a, Pixbuf: AsRef<[T::Color]>> Output<'a, LinearSpace> for SmartLedsOutput<'a, T, Pixbuf> where Self: Sample<'a, LinearSpace>, T::Color: core::fmt::Debug + AsMilliwatts + Fract8Ops + Copy + WithGamma {
129    type Error = T::Error;
130
131    type Controls = PowerControls;
132
133    fn commit(&mut self)  -> Result<(), Self::Error> {
134        self.writer.write(&self.pixbuf)
135    }
136
137    fn controls(&mut self) -> Option<&mut Self::Controls> {
138        Some(self.writer.controls())
139    }
140}
141
142impl<'a, T: SmartLedsWriteAsync + 'a, Pixbuf: AsRef<[T::Color]>> OutputAsync<'a, LinearSpace> for SmartLedsOutput<'a, T, Pixbuf> where Self: Sample<'a, LinearSpace>, T::Color: core::fmt::Debug + AsMilliwatts + Fract8Ops + Copy + WithGamma {
143    type Error = T::Error;
144
145    type Controls = PowerControls;
146
147    async fn commit_async(&mut self)  -> Result<(), Self::Error> {
148        self.writer.write_async(&self.pixbuf).await
149    }
150
151    fn controls(&mut self) -> Option<&mut Self::Controls> {
152        Some(self.writer.controls())
153    }
154}
155
156impl<'a, T, Color, const PIXEL_COUNT: usize> Sample<'a, LinearSpace> for SmartLedsOutput<'a, T, [Color; PIXEL_COUNT]> where Color: 'a {
157    type Output = Color;
158
159    fn sample(&mut self, rect: &figments::prelude::Rectangle<LinearSpace>) -> impl Iterator<Item = (figments::prelude::Coordinates<LinearSpace>, &'a mut Self::Output)> {
160        let start = self.clip.top_left.x.clamp(0, self.pixbuf.len() - 1);
161        let end = self.clip.bottom_right.x.clamp(0, self.pixbuf.len() - 1);
162        self.pixbuf[start..=end].sample(rect)
163    }
164}