rog-aura: Add flicker effect

This commit is contained in:
Luke D. Jones
2022-08-27 20:52:43 +12:00
parent 414d610bd2
commit ebbfa58a76
8 changed files with 537 additions and 312 deletions

View File

@@ -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)

View File

@@ -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": <one of
- `"LightbarLeftCorner"`
- `"LightbarLeft"`
At the moment there are only two effects available as shown in the example. More will come in the future
At the moment there are only three effects available as shown in the example. More will come in the future
but this may take me some time.
**Aura layouts**: `asusd-user` does its best to find a suitable layout to use based on `/sys/class/dmi/id/board_name`.

View File

@@ -1,6 +1,6 @@
//! Using a combination of key-colour array plus a key layout to generate outputs.
use rog_aura::{keys::Key, layouts::KeyLayout, ActionData, Colour, LedType, Sequences, Speed};
use rog_aura::{keys::Key, layouts::KeyLayout, Colour, LedType, Sequences, Speed, Effect, Breathe};
use rog_dbus::RogDbusClientBlocking;
fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -9,12 +9,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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));

View File

@@ -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<dyn std::error::Error>> {
@@ -10,28 +10,31 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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 {

View File

@@ -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<T> {
pub trait ConfigLoadSave<T: DeserializeOwned + serde::Serialize> {
fn name(&self) -> String;
fn default_with_name(name: String) -> T;
@@ -44,10 +44,7 @@ pub trait ConfigLoadSave<T> {
Ok(())
}
fn load(name: String) -> Result<T, Error>
where
T: DeserializeOwned + serde::Serialize,
{
fn load(name: String) -> Result<T, Error> {
let mut path = if let Some(dir) = dirs::config_dir() {
dir
} else {
@@ -197,12 +194,12 @@ impl ConfigLoadSave<UserAuraConfig> 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,

View File

@@ -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 - <u8>::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<ActionData>);
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<ActionData> {
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);
}
}

View File

@@ -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 - <u8>::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!();
}

View File

@@ -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<Effect>);
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<Effect> {
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);
}
}