diff --git a/CHANGELOG.md b/CHANGELOG.md index e4aefb45..393e1216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for per-key config has been added to `asusd-user`. At the moment it is basic with only two effects done. Please see the manual for more information. - Support for per-zone effects on some laptops. As above. +- Added three effects to use with Zoned or Per-Key: + + Static, Breathe, Flicker. More to come. ### Changed - Create new rog-platform crate to manage all i/o in a universal way + kbd-led handling (requires kernel patches, TUF specific) diff --git a/MANUAL.md b/MANUAL.md index b740fd9d..0d11ea72 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -139,38 +139,51 @@ An Aura config itself is a file with contents: ```json { - [ + "name": "aura-default", + "aura": [ { - "led_type": { - "Key": "W" - }, - "action": { - "Breathe": { - "colour1": [ - 255, - 127, - 0 - ], - "colour2": [ - 127, - 0, - 255 - ], - "speed": "Med" - } + "Breathe": { + "led_type": { + "Key": "W" + }, + "start_colour1": [ + 255, + 0, + 20 + ], + "start_colour2": [ + 20, + 255, + 0 + ], + "speed": "Low" } }, { - "led_type": { - "Key": "Esc" - }, - "action": { - "Static": [ + "Static": { + "led_type": { + "Key": "Esc" + }, + "colour": [ 0, 0, 255 ] } + }, + { + "Flicker": { + "led_type": { + "Key": "N9" + }, + "colour": [ + 0, + 0, + 255 + ], + "max_percentage": 80, + "min_percentage": 40 + } } ] } @@ -189,7 +202,7 @@ If your laptop supports multizone, `"led_type"` can also be `"PerZone": Result<(), Box> { @@ -9,12 +9,12 @@ fn main() -> Result<(), Box> { let (client, _) = RogDbusClientBlocking::new().unwrap(); let mut seq = Sequences::new(); - let mut key = ActionData::new_breathe( - LedType::Key(Key::W), + let mut key = Effect::Breathe( + Breathe::new(LedType::Key(Key::W), Colour(255, 127, 0), Colour(127, 0, 255), Speed::Med, - ); + )); seq.push(key.clone()); key.set_led_type(LedType::Key(Key::A)); @@ -24,22 +24,24 @@ fn main() -> Result<(), Box> { key.set_led_type(LedType::Key(Key::D)); seq.push(key.clone()); - let mut key = ActionData::new_breathe( + let mut key = Effect::Breathe( + Breathe::new( LedType::Key(Key::Q), Colour(127, 127, 127), Colour(127, 255, 255), Speed::Low, - ); + )); seq.push(key.clone()); key.set_led_type(LedType::Key(Key::E)); seq.push(key.clone()); - let mut key = ActionData::new_breathe( + let mut key = Effect::Breathe( + Breathe::new( LedType::Key(Key::N1), Colour(166, 127, 166), Colour(127, 155, 20), Speed::High, - ); + )); key.set_led_type(LedType::Key(Key::Tilde)); seq.push(key.clone()); key.set_led_type(LedType::Key(Key::N2)); diff --git a/asusctl/examples/aura-zoned-breathe.rs b/asusctl/examples/aura-zoned-breathe.rs index 7b4f4c77..a1c875a5 100644 --- a/asusctl/examples/aura-zoned-breathe.rs +++ b/asusctl/examples/aura-zoned-breathe.rs @@ -1,6 +1,6 @@ //! Using a combination of key-colour array plus a key layout to generate outputs. -use rog_aura::{layouts::KeyLayout, ActionData, Colour, LedType, PerZone, Sequences, Speed}; +use rog_aura::{layouts::KeyLayout, Colour, LedType, PerZone, Sequences, Speed, Effect, Breathe}; use rog_dbus::RogDbusClientBlocking; fn main() -> Result<(), Box> { @@ -10,28 +10,31 @@ fn main() -> Result<(), Box> { let mut seq = Sequences::new(); - let zone = ActionData::new_breathe( + let zone = Effect::Breathe( + Breathe::new( LedType::Zone(PerZone::KeyboardLeft), Colour(166, 127, 166), Colour(127, 155, 20), Speed::High, - ); + )); seq.push(zone); - let zone = ActionData::new_breathe( + let zone = Effect::Breathe( + Breathe::new( LedType::Zone(PerZone::KeyboardCenterLeft), Colour(16, 127, 255), Colour(127, 15, 20), Speed::Low, - ); + )); seq.push(zone); - let zone = ActionData::new_breathe( + let zone = Effect::Breathe( + Breathe::new( LedType::Zone(PerZone::LightbarRightCorner), Colour(0, 255, 255), Colour(255, 0, 255), Speed::Med, - ); + )); seq.push(zone); loop { diff --git a/daemon-user/src/user_config.rs b/daemon-user/src/user_config.rs index a1216950..0fca55aa 100644 --- a/daemon-user/src/user_config.rs +++ b/daemon-user/src/user_config.rs @@ -5,13 +5,13 @@ use std::{ }; use rog_anime::{ActionLoader, AnimTime, AnimeType, Fade, Sequences, Vec2}; -use rog_aura::{keys::Key, Colour, LedType, Speed}; +use rog_aura::{keys::Key, Breathe, Colour, Effect, LedType, Speed, Static, Flicker}; use serde::de::DeserializeOwned; use serde_derive::{Deserialize, Serialize}; use crate::error::Error; -pub trait ConfigLoadSave { +pub trait ConfigLoadSave { fn name(&self) -> String; fn default_with_name(name: String) -> T; @@ -44,10 +44,7 @@ pub trait ConfigLoadSave { Ok(()) } - fn load(name: String) -> Result - where - T: DeserializeOwned + serde::Serialize, - { + fn load(name: String) -> Result { let mut path = if let Some(dir) = dirs::config_dir() { dir } else { @@ -197,12 +194,12 @@ impl ConfigLoadSave for UserAuraConfig { impl Default for UserAuraConfig { fn default() -> Self { let mut seq = rog_aura::Sequences::new(); - let mut key = rog_aura::ActionData::new_breathe( + let mut key = Effect::Breathe(Breathe::new( LedType::Key(Key::W), Colour(255, 0, 20), Colour(20, 255, 0), Speed::Low, - ); + )); seq.push(key.clone()); key.set_led_type(LedType::Key(Key::A)); @@ -212,21 +209,24 @@ impl Default for UserAuraConfig { key.set_led_type(LedType::Key(Key::D)); seq.push(key); - let key = rog_aura::ActionData::new_breathe( + let key = Effect::Breathe(Breathe::new( LedType::Key(Key::F), Colour(255, 0, 0), Colour(255, 0, 0), Speed::High, - ); + )); seq.push(key); - let mut key = rog_aura::ActionData::new_static(LedType::Key(Key::RCtrl), Colour(0, 0, 255)); + let mut key = Effect::Static(Static::new(LedType::Key(Key::RCtrl), Colour(0, 0, 255))); seq.push(key.clone()); key.set_led_type(LedType::Key(Key::LCtrl)); seq.push(key.clone()); key.set_led_type(LedType::Key(Key::Esc)); seq.push(key); + let key = Effect::Flicker(Flicker::new(LedType::Key(Key::N9), Colour(0, 0, 255), 80, 40)); + seq.push(key.clone()); + Self { name: "default".to_string(), aura: seq, diff --git a/rog-aura/src/sequencer.rs b/rog-aura/src/sequencer.rs deleted file mode 100644 index c203df98..00000000 --- a/rog-aura/src/sequencer.rs +++ /dev/null @@ -1,262 +0,0 @@ -use serde_derive::{Deserialize, Serialize}; - -use crate::{ - keys::Key, layouts::KeyLayout, Colour, KeyColourArray, PerKeyRaw, PerZone, Speed, - ZonedColourArray, -}; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub enum LedType { - Key(Key), - Zone(PerZone), -} - -impl Default for LedType { - fn default() -> Self { - Self::Zone(PerZone::None) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub(super) enum Action { - Static(Colour), - Breathe { - /// The starting colour - colour1: Colour, - /// The secondary starting colour - colour2: Colour, - /// The speed at which to cycle between the colours - speed: Speed, - /// Temporary data to help keep state - #[serde(skip)] - colour_actual: Colour, - #[serde(skip)] - count_flipped: bool, - #[serde(skip)] - use_colour1: bool, - }, -} - -impl Default for Action { - fn default() -> Self { - Self::Static(Colour::default()) - } -} - -#[derive(Debug, Default, Clone, Deserialize, Serialize)] -pub struct ActionData { - led_type: LedType, - action: Action, - // TODO: time - /// The end resulting colour after stepping through effect - #[serde(skip)] - colour: Colour, -} - -impl ActionData { - pub fn set_led_type(&mut self, led_type: LedType) { - self.led_type = led_type - } - - pub fn new_static(led_type: LedType, colour: Colour) -> Self { - Self { - led_type, - action: Action::Static(colour), - colour: Default::default(), - } - } - - pub fn new_breathe(led_type: LedType, colour1: Colour, colour2: Colour, speed: Speed) -> Self { - Self { - led_type, - action: Action::Breathe { - colour1, - colour2, - speed, - colour_actual: colour1, - count_flipped: false, - use_colour1: true, - }, - colour: Default::default(), - } - } - - pub fn next_state(&mut self, _layout: &KeyLayout) { - match &mut self.action { - Action::Static(c) => self.colour = *c, - Action::Breathe { - colour1, - colour2, - speed, - colour_actual, - count_flipped: flipped, - use_colour1, - } => { - let speed = 4 - ::from(*speed); - - let colour: &mut Colour; - if *colour_actual == Colour(0, 0, 0) { - *use_colour1 = !*use_colour1; - } - - if !*use_colour1 { - colour = colour2; - } else { - colour = colour1; - } - - let r1_scale = colour.0 / speed / 2; - let g1_scale = colour.1 / speed / 2; - let b1_scale = colour.2 / speed / 2; - - if *colour_actual == Colour(0, 0, 0) { - *flipped = true; - } else if colour_actual >= colour { - *flipped = false; - } - - if !*flipped { - colour_actual.0 = colour_actual.0.saturating_sub(r1_scale); - colour_actual.1 = colour_actual.1.saturating_sub(g1_scale); - colour_actual.2 = colour_actual.2.saturating_sub(b1_scale); - } else { - colour_actual.0 = colour_actual.0.saturating_add(r1_scale); - colour_actual.1 = colour_actual.1.saturating_add(g1_scale); - colour_actual.2 = colour_actual.2.saturating_add(b1_scale); - } - self.colour = *colour_actual; - } - } - } -} - -#[derive(Debug, Deserialize, Serialize, Default)] -pub struct Sequences(Vec); - -impl Sequences { - #[inline] - pub fn new() -> Self { - Self(Vec::new()) - } - - #[inline] - pub fn push(&mut self, action: ActionData) { - self.0.push(action); - } - - #[inline] - pub fn insert(&mut self, index: usize, action: ActionData) { - self.0.insert(index, action); - } - - /// Remove an item at this position from the run buffer. If the `index` supplied - /// is not in range then `None` is returned, otherwise the `ActionData` at that location - /// is yeeted and returned. - #[inline] - pub fn remove_item(&mut self, index: usize) -> Option { - if index < self.0.len() { - return Some(self.0.remove(index)); - } - None - } - - pub fn next_state(&mut self, layout: &KeyLayout) { - for effect in self.0.iter_mut() { - effect.next_state(layout); - } - } - - pub fn create_packets(&self) -> PerKeyRaw { - let mut keys = KeyColourArray::new(); - let mut zones = ZonedColourArray::new(); - let mut is_per_key = false; - for effect in self.0.iter() { - match effect.led_type { - LedType::Key(key) => { - is_per_key = true; - if let Some(rgb) = keys.rgb_for_key(key) { - rgb[0] = effect.colour.0; - rgb[1] = effect.colour.1; - rgb[2] = effect.colour.2; - } - } - LedType::Zone(z) => { - let rgb = zones.rgb_for_zone(z); - rgb[0] = effect.colour.0; - rgb[1] = effect.colour.1; - rgb[2] = effect.colour.2; - } - } - } - if is_per_key { - keys.into() - } else { - vec![zones.into()] - } - } -} - -#[cfg(test)] -mod tests { - use crate::{ - keys::Key, layouts::KeyLayout, Action, ActionData, Colour, LedType, Sequences, Speed, - }; - - #[test] - fn single_key_next_state_then_create() { - let layout = KeyLayout::gx502_layout(); - let mut seq = Sequences::new(); - seq.0.push(ActionData { - led_type: LedType::Key(Key::F), - action: Action::Static(Colour(255, 127, 0)), - colour: Default::default(), - }); - - seq.next_state(&layout); - let packets = seq.create_packets(); - - assert_eq!(packets[0][0], 0x5d); - assert_eq!(packets[5][33], 255); - assert_eq!(packets[5][34], 127); - assert_eq!(packets[5][35], 0); - } - - #[test] - fn cycle_breathe() { - let layout = KeyLayout::gx502_layout(); - let mut seq = Sequences::new(); - seq.0.push(ActionData { - led_type: LedType::Key(Key::F), - action: Action::Breathe { - colour1: Colour(255, 127, 0), - colour2: Colour(127, 0, 255), - speed: Speed::Med, - colour_actual: Colour(255, 127, 0), - count_flipped: false, - use_colour1: true, - }, - colour: Default::default(), - }); - - let s = serde_json::to_string_pretty(&seq).unwrap(); - println!("{s}"); - - seq.next_state(&layout); - let packets = seq.create_packets(); - - assert_eq!(packets[0][0], 0x5d); - assert_eq!(packets[5][33], 213); - assert_eq!(packets[5][34], 106); - assert_eq!(packets[5][35], 0); - - // dbg!(&packets[5][33..=35]); - - seq.next_state(&layout); - let packets = seq.create_packets(); - - assert_eq!(packets[0][0], 0x5d); - assert_eq!(packets[5][33], 171); - assert_eq!(packets[5][34], 85); - assert_eq!(packets[5][35], 0); - } -} diff --git a/rog-aura/src/sequencer/effects.rs b/rog-aura/src/sequencer/effects.rs new file mode 100644 index 00000000..fe40b9bc --- /dev/null +++ b/rog-aura/src/sequencer/effects.rs @@ -0,0 +1,249 @@ +use crate::{layouts::KeyLayout, p_random, Colour, EffectState, LedType, Speed}; +use serde_derive::{Deserialize, Serialize}; + +macro_rules! effect_state_impl { + () => { + fn get_colour(&self) -> Colour { + self.colour + } + + fn get_led_type(&self) -> LedType { + self.led_type.clone() + } + + /// Change the led type + fn set_led_type(&mut self, led_type: LedType) { + self.led_type = led_type; + } + }; +} + +macro_rules! effect_impl { + ($($effect:ident),*) => { + impl Effect { + /// Get the type of LED set + pub fn get_led_type(&self) -> LedType { + match self { + $(Effect::$effect(c) => c.get_led_type(),)* + } + } + + /// Change the led type + pub fn set_led_type(&mut self, led_type: LedType) { + match self { + $(Effect::$effect(c) => c.set_led_type(led_type),)* + } + } + + /// Calculate the next state of the effect + pub fn next_state(&mut self, layout: &KeyLayout) { + match self { + $(Effect::$effect(c) => c.next_colour_state(layout),)* + } + } + + /// Get the calculated colour + pub fn get_colour(&self) -> Colour { + match self { + $(Effect::$effect(c) => c.get_colour(),)* + } + } + } + }; +} + +/**************************************************************************************************/ + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub enum Effect { + Static(Static), + Breathe(Breathe), + Flicker(Flicker), +} + +impl Default for Effect { + fn default() -> Self { + Self::Static(Static::new(LedType::default(), Colour::default())) + } +} + +effect_impl!(Static, Breathe, Flicker); + +/**************************************************************************************************/ + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Static { + led_type: LedType, + /// The starting colour + colour: Colour, +} + +impl Static { + pub fn new(led_type: LedType, colour: Colour) -> Self { + Self { led_type, colour } + } +} + +impl EffectState for Static { + fn next_colour_state(&mut self, _layout: &KeyLayout) {} + + effect_state_impl!(); +} + +/**************************************************************************************************/ + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Breathe { + led_type: LedType, + /// The starting colour + start_colour1: Colour, + /// The secondary starting colour + start_colour2: Colour, + /// The speed at which to cycle between the colours + speed: Speed, + /// Temporary data to help keep state + #[serde(skip)] + colour: Colour, + #[serde(skip)] + count_flipped: bool, + #[serde(skip)] + use_colour1: bool, +} + +impl Breathe { + pub fn new(led_type: LedType, colour1: Colour, colour2: Colour, speed: Speed) -> Self { + Self { + led_type, + start_colour1: colour1, + start_colour2: colour2, + speed, + colour: colour1, + count_flipped: false, + use_colour1: true, + } + } +} + +impl EffectState for Breathe { + fn next_colour_state(&mut self, _layout: &KeyLayout) { + let Self { + start_colour1: colour1, + start_colour2: colour2, + speed, + colour: colour_actual, + count_flipped: flipped, + use_colour1, + .. + } = self; + + let speed = 4 - ::from(*speed); + + let colour: &mut Colour; + if *colour_actual == Colour(0, 0, 0) { + *use_colour1 = !*use_colour1; + } + + if !*use_colour1 { + colour = colour2; + } else { + colour = colour1; + } + + let r1_scale = colour.0 / speed / 2; + let g1_scale = colour.1 / speed / 2; + let b1_scale = colour.2 / speed / 2; + + if *colour_actual == Colour(0, 0, 0) { + *flipped = true; + } else if colour_actual >= colour { + *flipped = false; + } + + if !*flipped { + colour_actual.0 = colour_actual.0.saturating_sub(r1_scale); + colour_actual.1 = colour_actual.1.saturating_sub(g1_scale); + colour_actual.2 = colour_actual.2.saturating_sub(b1_scale); + } else { + colour_actual.0 = colour_actual.0.saturating_add(r1_scale); + colour_actual.1 = colour_actual.1.saturating_add(g1_scale); + colour_actual.2 = colour_actual.2.saturating_add(b1_scale); + } + } + + effect_state_impl!(); +} + +/**************************************************************************************************/ + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Flicker { + led_type: LedType, + /// The starting colour + colour: Colour, + max_percentage: u8, + min_percentage: u8, + #[serde(skip)] + count: u8, +} + +impl Flicker { + pub fn new(led_type: LedType, colour: Colour, max_percentage: u8, min_percentage: u8) -> Self { + Self { + led_type, + colour, + count: 4, + max_percentage, + min_percentage, + } + } +} + +impl EffectState for Flicker { + fn next_colour_state(&mut self, _layout: &KeyLayout) { + let Self { + max_percentage, + min_percentage, + colour, + .. + } = self; + + self.count -= 1; + if self.count != 0 { + return; + } + + // TODO: make a "percentage" method on Colour. + let max_light = Colour( + (colour.0 as f32 / 100.0 * *max_percentage as f32) as u8, + (colour.1 as f32 / 100.0 * *max_percentage as f32) as u8, + (colour.2 as f32 / 100.0 * *max_percentage as f32) as u8, + ); + // min light is a percentage of the set colour + let min_light = Colour( + (colour.0 as f32 / 100.0 * *min_percentage as f32) as u8, + (colour.1 as f32 / 100.0 * *min_percentage as f32) as u8, + (colour.2 as f32 / 100.0 * *min_percentage as f32) as u8, + ); + + // Convert the 255 to percentage + let amount = (p_random() & 7) as f32 * 8.0; + + let set_colour = |colour: &mut u8, max: f32, min: f32| { + let pc = amount / max * 100.0; + let min_amount = pc * min / 100.0; // percentage of min colour + let max_amount = pc * max / 100.0; // percentage of max colour + if *colour as f32 - min_amount < min { + *colour = min as u8; + } else { + *colour = (max - max_amount) as u8; + } + }; + set_colour(&mut colour.0, max_light.0 as f32, min_light.0 as f32); + set_colour(&mut colour.1, max_light.1 as f32, min_light.1 as f32); + set_colour(&mut colour.2, max_light.2 as f32, min_light.2 as f32); + + self.count = 4; + } + + effect_state_impl!(); +} diff --git a/rog-aura/src/sequencer/mod.rs b/rog-aura/src/sequencer/mod.rs new file mode 100644 index 00000000..04d5ed3a --- /dev/null +++ b/rog-aura/src/sequencer/mod.rs @@ -0,0 +1,218 @@ +mod effects; +pub use effects::*; + +use crate::{ + keys::Key, layouts::KeyLayout, Colour, KeyColourArray, PerKeyRaw, PerZone, ZonedColourArray, +}; +use serde_derive::{Deserialize, Serialize}; + +// static mut RNDINDEX: usize = 0; +static mut PRNDINDEX: usize = 0; + +pub const RNDTABLE: [i32; 256] = [ + 0, 8, 109, 220, 222, 241, 149, 107, 75, 248, 254, 140, 16, 66, 74, 21, 211, 47, 80, 242, 154, + 27, 205, 128, 161, 89, 77, 36, 95, 110, 85, 48, 212, 140, 211, 249, 22, 79, 200, 50, 28, 188, + 52, 140, 202, 120, 68, 145, 62, 70, 184, 190, 91, 197, 152, 224, 149, 104, 25, 178, 252, 182, + 202, 182, 141, 197, 4, 81, 181, 242, 145, 42, 39, 227, 156, 198, 225, 193, 219, 93, 122, 175, + 249, 0, 175, 143, 70, 239, 46, 246, 163, 53, 163, 109, 168, 135, 2, 235, 25, 92, 20, 145, 138, + 77, 69, 166, 78, 176, 173, 212, 166, 113, 94, 161, 41, 50, 239, 49, 111, 164, 70, 60, 2, 37, + 171, 75, 136, 156, 11, 56, 42, 146, 138, 229, 73, 146, 77, 61, 98, 196, 135, 106, 63, 197, 195, + 86, 96, 203, 113, 101, 170, 247, 181, 113, 80, 250, 108, 7, 255, 237, 129, 226, 79, 107, 112, + 166, 103, 241, 24, 223, 239, 120, 198, 58, 60, 82, 128, 3, 184, 66, 143, 224, 145, 224, 81, + 206, 163, 45, 63, 90, 168, 114, 59, 33, 159, 95, 28, 139, 123, 98, 125, 196, 15, 70, 194, 253, + 54, 14, 109, 226, 71, 17, 161, 93, 186, 87, 244, 138, 20, 52, 123, 251, 26, 36, 17, 46, 52, + 231, 232, 76, 31, 221, 84, 37, 216, 165, 212, 106, 197, 242, 98, 43, 39, 175, 254, 145, 190, + 84, 118, 222, 187, 136, 120, 163, 236, 249, +]; + +pub fn p_random() -> i32 { + unsafe { + PRNDINDEX = (PRNDINDEX + 1) & 0xFF; + RNDTABLE[PRNDINDEX] + } +} + +pub(crate) trait EffectState { + /// Calculate the next colour state + fn next_colour_state(&mut self, _layout: &KeyLayout); + + /// Return the resulting colour. Implementers should store the colour to return it. + fn get_colour(&self) -> Colour; + + fn get_led_type(&self) -> LedType; + + fn set_led_type(&mut self, led_type: LedType); +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub enum LedType { + Key(Key), + Zone(PerZone), +} + +impl Default for LedType { + fn default() -> Self { + Self::Zone(PerZone::None) + } +} + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct Sequences(Vec); + +impl Sequences { + #[inline] + pub fn new() -> Self { + Self(Vec::new()) + } + + #[inline] + pub fn push(&mut self, action: Effect) { + self.0.push(action); + } + + #[inline] + pub fn insert(&mut self, index: usize, action: Effect) { + self.0.insert(index, action); + } + + /// Remove an item at this position from the run buffer. If the `index` supplied + /// is not in range then `None` is returned, otherwise the `ActionData` at that location + /// is yeeted and returned. + #[inline] + pub fn remove_item(&mut self, index: usize) -> Option { + if index < self.0.len() { + return Some(self.0.remove(index)); + } + None + } + + pub fn next_state(&mut self, layout: &KeyLayout) { + for effect in self.0.iter_mut() { + effect.next_state(layout); + } + } + + pub fn create_packets(&self) -> PerKeyRaw { + let mut keys = KeyColourArray::new(); + let mut zones = ZonedColourArray::new(); + let mut is_per_key = false; + for effect in self.0.iter() { + match effect.get_led_type() { + LedType::Key(key) => { + is_per_key = true; + if let Some(rgb) = keys.rgb_for_key(key) { + let c = effect.get_colour(); + rgb[0] = c.0; + rgb[1] = c.1; + rgb[2] = c.2; + } + } + LedType::Zone(z) => { + let rgb = zones.rgb_for_zone(z); + let c = effect.get_colour(); + rgb[0] = c.0; + rgb[1] = c.1; + rgb[2] = c.2; + } + } + } + if is_per_key { + keys.into() + } else { + vec![zones.into()] + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + keys::Key, layouts::KeyLayout, Breathe, Colour, Effect, Flicker, LedType, Sequences, Speed, + Static, + }; + + #[test] + fn single_key_next_state_then_create() { + let layout = KeyLayout::gx502_layout(); + let mut seq = Sequences::new(); + seq.0.push(Effect::Static(Static::new( + LedType::Key(Key::F), + Colour(255, 127, 0), + ))); + + seq.next_state(&layout); + let packets = seq.create_packets(); + + assert_eq!(packets[0][0], 0x5d); + assert_eq!(packets[5][33], 255); + assert_eq!(packets[5][34], 127); + assert_eq!(packets[5][35], 0); + } + + #[test] + fn cycle_breathe() { + let layout = KeyLayout::gx502_layout(); + let mut seq = Sequences::new(); + seq.0.push(Effect::Breathe(Breathe::new( + LedType::Key(Key::F), + Colour(255, 127, 0), + Colour(127, 0, 255), + Speed::Med, + ))); + + let s = serde_json::to_string_pretty(&seq).unwrap(); + println!("{s}"); + + seq.next_state(&layout); + let packets = seq.create_packets(); + + assert_eq!(packets[0][0], 0x5d); + assert_eq!(packets[5][33], 213); + assert_eq!(packets[5][34], 106); + assert_eq!(packets[5][35], 0); + + // dbg!(&packets[5][33..=35]); + + seq.next_state(&layout); + let packets = seq.create_packets(); + + assert_eq!(packets[0][0], 0x5d); + assert_eq!(packets[5][33], 171); + assert_eq!(packets[5][34], 85); + assert_eq!(packets[5][35], 0); + } + + #[test] + fn cycle_flicker() { + let layout = KeyLayout::gx502_layout(); + let mut seq = Sequences::new(); + seq.0.push(Effect::Flicker(Flicker::new( + LedType::Key(Key::F), + Colour(255, 127, 80), + 100, + 10, + ))); + + seq.next_state(&layout); + let packets = seq.create_packets(); + + assert_eq!(packets[0][0], 0x5d); + assert_eq!(packets[5][33], 255); + assert_eq!(packets[5][34], 127); + assert_eq!(packets[5][35], 80); + + // The random is deterministic + seq.next_state(&layout); + seq.next_state(&layout); + seq.next_state(&layout); + seq.next_state(&layout); + seq.next_state(&layout); + seq.next_state(&layout); + seq.next_state(&layout); + + let packets = seq.create_packets(); + assert_eq!(packets[5][33], 215); + assert_eq!(packets[5][34], 87); + assert_eq!(packets[5][35], 40); + } +}