324 lines
9.0 KiB
Rust
324 lines
9.0 KiB
Rust
|
use crate::color::{self, RAINBOW, RGB};
|
||
|
pub trait Pattern: std::fmt::Debug {
|
||
|
fn init(&mut self, lights_buf: &mut Vec<RGB>, num_lights: u16) -> Result<(), ()>;
|
||
|
fn step(&mut self, lights_buf: &mut Vec<RGB>) -> bool;
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug)]
|
||
|
pub struct MovingPixel {
|
||
|
color: RGB,
|
||
|
num_lights: u16,
|
||
|
step: u16,
|
||
|
}
|
||
|
impl MovingPixel {
|
||
|
pub const fn new(color: RGB) -> Self {
|
||
|
Self {
|
||
|
color,
|
||
|
step: 0,
|
||
|
num_lights: 1,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
impl Pattern for MovingPixel {
|
||
|
fn step(&mut self, lights_buf: &mut Vec<RGB>) -> bool {
|
||
|
let len = self.num_lights;
|
||
|
lights_buf.swap(
|
||
|
self.step.rem_euclid(len).into(),
|
||
|
self.step.saturating_add(1).saturating_mul(3).into(),
|
||
|
);
|
||
|
self.step = self.step.wrapping_add(1);
|
||
|
true
|
||
|
}
|
||
|
fn init(&mut self, lights_buf: &mut Vec<RGB>, num_lights: u16) -> Result<(), ()> {
|
||
|
if num_lights < 1 {
|
||
|
return Err(());
|
||
|
}
|
||
|
self.step = 0;
|
||
|
self.num_lights = num_lights;
|
||
|
// Set the strip to black except for one pixel
|
||
|
*lights_buf = vec![color::BLACK; num_lights.into()];
|
||
|
lights_buf[0] = self.color;
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug)]
|
||
|
pub struct Solid {
|
||
|
color: RGB,
|
||
|
has_run: bool,
|
||
|
}
|
||
|
impl Solid {
|
||
|
pub const fn new(color: RGB) -> Self {
|
||
|
Self {
|
||
|
color,
|
||
|
has_run: false,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
impl Pattern for Solid {
|
||
|
fn step(&mut self, _lights_buf: &mut Vec<RGB>) -> bool {
|
||
|
let ret = !self.has_run;
|
||
|
self.has_run = true;
|
||
|
ret
|
||
|
}
|
||
|
fn init(&mut self, lights_buf: &mut Vec<RGB>, num_lights: u16) -> Result<(), ()> {
|
||
|
if num_lights < 1 {
|
||
|
return Err(());
|
||
|
}
|
||
|
self.has_run = false;
|
||
|
*lights_buf = vec![self.color; num_lights.into()];
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug)]
|
||
|
pub struct MovingRainbow {}
|
||
|
impl MovingRainbow {
|
||
|
pub const fn new() -> Self {
|
||
|
Self {}
|
||
|
}
|
||
|
}
|
||
|
impl Pattern for MovingRainbow {
|
||
|
fn step(&mut self, lights_buf: &mut Vec<RGB>) -> bool {
|
||
|
lights_buf.rotate_right(1);
|
||
|
true
|
||
|
}
|
||
|
fn init(&mut self, lights_buf: &mut Vec<RGB>, num_lights: u16) -> Result<(), ()> {
|
||
|
if num_lights < 1 {
|
||
|
return Err(());
|
||
|
}
|
||
|
*lights_buf = RAINBOW
|
||
|
.iter()
|
||
|
.cycle()
|
||
|
.take(
|
||
|
num_lights
|
||
|
.saturating_sub(1)
|
||
|
.div_euclid(7)
|
||
|
.saturating_add(1)
|
||
|
.saturating_mul(7)
|
||
|
.into(),
|
||
|
)
|
||
|
.copied()
|
||
|
.collect::<Vec<RGB>>();
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug)]
|
||
|
pub struct Fade {
|
||
|
color: RGB,
|
||
|
step: u8,
|
||
|
direction: bool,
|
||
|
num_lights: u16,
|
||
|
}
|
||
|
impl Fade {
|
||
|
pub const fn new(color: RGB) -> Self {
|
||
|
Self {
|
||
|
color,
|
||
|
step: 0,
|
||
|
direction: true,
|
||
|
num_lights: 1,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
impl Pattern for Fade {
|
||
|
fn step(&mut self, lights_buf: &mut Vec<RGB>) -> bool {
|
||
|
if self.direction {
|
||
|
if self.step == 254 {
|
||
|
self.direction = !self.direction;
|
||
|
}
|
||
|
self.step = self.step.saturating_add(1);
|
||
|
} else {
|
||
|
if self.step == 1 {
|
||
|
self.direction = !self.direction;
|
||
|
}
|
||
|
self.step = self.step.saturating_sub(1);
|
||
|
}
|
||
|
*lights_buf = vec![RGB(self.step, self.step, self.step); self.num_lights.into()];
|
||
|
true
|
||
|
}
|
||
|
fn init(&mut self, lights_buf: &mut Vec<RGB>, num_lights: u16) -> Result<(), ()> {
|
||
|
if num_lights < 1 {
|
||
|
return Err(());
|
||
|
}
|
||
|
self.step = 0;
|
||
|
self.direction = true;
|
||
|
self.num_lights = num_lights;
|
||
|
*lights_buf = vec![color::BLACK; self.num_lights.into()];
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug)]
|
||
|
pub struct Collide {
|
||
|
num_lights: u16,
|
||
|
left_color: RGB,
|
||
|
right_color: RGB,
|
||
|
conjoined_color: RGB,
|
||
|
step: u16,
|
||
|
step_max: u16,
|
||
|
conjoined_bounds: (u16, u16),
|
||
|
offset_max: u16,
|
||
|
offset: u16,
|
||
|
previous_offset: u16,
|
||
|
increase_offset: bool,
|
||
|
}
|
||
|
impl Collide {
|
||
|
pub const fn new(left_color: RGB, right_color: RGB, conjoined_color: RGB) -> Self {
|
||
|
Self {
|
||
|
num_lights: 0,
|
||
|
left_color,
|
||
|
right_color,
|
||
|
conjoined_color,
|
||
|
step: 0,
|
||
|
step_max: 0,
|
||
|
conjoined_bounds: (0, 0),
|
||
|
offset_max: 0,
|
||
|
previous_offset: 0,
|
||
|
offset: 0,
|
||
|
increase_offset: true,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
impl Pattern for Collide {
|
||
|
fn step(&mut self, lights_buf: &mut Vec<RGB>) -> bool {
|
||
|
// TODO: Better range storage
|
||
|
// Set the left and right color
|
||
|
let colors =
|
||
|
if ((self.conjoined_bounds.0)..=(self.conjoined_bounds.1)).contains(&(self.step)) {
|
||
|
(self.conjoined_color, self.conjoined_color)
|
||
|
} else {
|
||
|
(self.left_color, self.right_color)
|
||
|
};
|
||
|
|
||
|
// Turn off the previous LED
|
||
|
lights_buf[usize::from(self.previous_offset)] = color::BLACK;
|
||
|
if self.previous_offset
|
||
|
!= self
|
||
|
.num_lights
|
||
|
.saturating_sub(1)
|
||
|
.saturating_sub(self.previous_offset)
|
||
|
{
|
||
|
lights_buf[usize::from(
|
||
|
self.num_lights
|
||
|
.saturating_sub(1)
|
||
|
.saturating_sub(self.previous_offset),
|
||
|
)] = color::BLACK;
|
||
|
}
|
||
|
// Set the color of the current offset
|
||
|
lights_buf[usize::from(self.offset)] = colors.0;
|
||
|
if self.offset
|
||
|
!= self
|
||
|
.num_lights
|
||
|
.saturating_sub(1)
|
||
|
.saturating_sub(self.offset)
|
||
|
{
|
||
|
lights_buf[usize::from(
|
||
|
self.num_lights
|
||
|
.saturating_sub(1)
|
||
|
.saturating_sub(self.offset),
|
||
|
)] = colors.1;
|
||
|
}
|
||
|
|
||
|
self.previous_offset = self.offset;
|
||
|
if self.offset == 0 {
|
||
|
self.increase_offset = true;
|
||
|
self.offset = self.offset.saturating_add(1);
|
||
|
} else if self.offset == self.offset_max {
|
||
|
self.increase_offset = false;
|
||
|
self.offset = self.offset.saturating_sub(1);
|
||
|
} else if self.increase_offset {
|
||
|
self.offset = self.offset.saturating_add(1);
|
||
|
// The previous condition was false, so subtract
|
||
|
} else {
|
||
|
self.offset = self.offset.saturating_sub(1);
|
||
|
}
|
||
|
|
||
|
self.step = self.step.saturating_add(1).rem_euclid(self.step_max);
|
||
|
|
||
|
true
|
||
|
}
|
||
|
fn init(&mut self, lights_buf: &mut Vec<RGB>, num_lights: u16) -> Result<(), ()> {
|
||
|
// Reset changing parameters
|
||
|
self.step = 0;
|
||
|
self.offset = 0;
|
||
|
self.previous_offset = 0;
|
||
|
self.num_lights = num_lights;
|
||
|
self.increase_offset = true;
|
||
|
if self.num_lights < 3 {
|
||
|
return Err(());
|
||
|
}
|
||
|
*lights_buf = vec![color::BLACK; self.num_lights.into()];
|
||
|
if self.num_lights.rem_euclid(2) == 0 {
|
||
|
self.conjoined_bounds = (
|
||
|
self.num_lights
|
||
|
.div_euclid(2)
|
||
|
.saturating_add(1_u16.saturating_sub(1)),
|
||
|
self.num_lights
|
||
|
.saturating_add(1)
|
||
|
.saturating_mul(3)
|
||
|
.div_euclid(2)
|
||
|
.saturating_sub(4),
|
||
|
);
|
||
|
} else {
|
||
|
self.conjoined_bounds = (
|
||
|
self.num_lights
|
||
|
.div_euclid(2)
|
||
|
.saturating_add(1_u16.saturating_sub(1)),
|
||
|
self.num_lights
|
||
|
.saturating_add(1)
|
||
|
.saturating_mul(3)
|
||
|
.div_euclid(2)
|
||
|
.saturating_sub(3),
|
||
|
);
|
||
|
}
|
||
|
self.offset_max = self.num_lights.saturating_sub(1).div_euclid(2);
|
||
|
self.step_max = self
|
||
|
.num_lights
|
||
|
.saturating_sub(1)
|
||
|
.div_euclid(2)
|
||
|
.saturating_mul(4);
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use super::*;
|
||
|
const NUM_LIGHTS: u16 = 10;
|
||
|
fn test_strip() -> Vec<RGB> {
|
||
|
vec![color::BLACK; NUM_LIGHTS.into()]
|
||
|
}
|
||
|
#[test]
|
||
|
fn moving_pixel() {
|
||
|
let color = RGB(123, 152, 89);
|
||
|
let mut pat = MovingPixel::new(color.clone());
|
||
|
let mut strip = test_strip();
|
||
|
|
||
|
assert!(pat.init(&mut strip, NUM_LIGHTS).is_ok());
|
||
|
// One is my color
|
||
|
assert_eq!(strip.iter().filter(|c| **c == color).count(), 1);
|
||
|
// The rest are off
|
||
|
assert_eq!(
|
||
|
strip.iter().filter(|c| **c == color::BLACK).count(),
|
||
|
(NUM_LIGHTS - 1).into()
|
||
|
);
|
||
|
pat.step(&mut strip);
|
||
|
// One is my color
|
||
|
assert_eq!(strip.iter().filter(|c| **c == color).count(), 1);
|
||
|
// The rest are off
|
||
|
assert_eq!(
|
||
|
strip.iter().filter(|c| **c == color::BLACK).count(),
|
||
|
(NUM_LIGHTS - 1).into()
|
||
|
);
|
||
|
}
|
||
|
#[test]
|
||
|
fn solid() {}
|
||
|
#[test]
|
||
|
fn moving_rainbow() {}
|
||
|
#[test]
|
||
|
fn fade() {}
|
||
|
#[test]
|
||
|
fn collide() {}
|
||
|
}
|