mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
Advanced Aura feature
Groundwork for 'advanced' aura modes Add single zone + Doom light flash Fix mocking for ROGCC Better prepare & change to mapping of keyboard layouts to models and functions Refactor and begin using new key layout stuff Enable first arg to rogcc to set layout in mocking feature mode Complete refactor of key layouts, and to RON serde
This commit is contained in:
33
rog-aura/src/effects/base.rs
Normal file
33
rog-aura/src/effects/base.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use super::{EffectState, InputForEffect};
|
||||
use crate::advanced::LedCode;
|
||||
use crate::Colour;
|
||||
|
||||
pub struct InputBased {
|
||||
address: LedCode,
|
||||
colour: Colour,
|
||||
/// - audio
|
||||
/// - cpu freq
|
||||
/// - temperature
|
||||
/// - fan speed
|
||||
/// - time
|
||||
input: Box<dyn InputForEffect>,
|
||||
}
|
||||
|
||||
impl EffectState for InputBased {
|
||||
fn next_colour_state(&mut self, _layout: &crate::layouts::KeyLayout) {
|
||||
self.input.next_colour_state();
|
||||
self.colour = self.input.get_colour();
|
||||
}
|
||||
|
||||
fn get_colour(&self) -> Colour {
|
||||
self.colour
|
||||
}
|
||||
|
||||
fn get_led(&self) -> LedCode {
|
||||
self.address
|
||||
}
|
||||
|
||||
fn set_led(&mut self, address: LedCode) {
|
||||
self.address = address
|
||||
}
|
||||
}
|
||||
82
rog-aura/src/effects/breathe.rs
Normal file
82
rog-aura/src/effects/breathe.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::EffectState;
|
||||
use crate::advanced::LedCode;
|
||||
use crate::layouts::KeyLayout;
|
||||
use crate::{effect_state_impl, Colour, Speed};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Breathe {
|
||||
address: LedCode,
|
||||
/// 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(address: LedCode, colour1: Colour, colour2: Colour, speed: Speed) -> Self {
|
||||
Self {
|
||||
address,
|
||||
start_colour1: colour1,
|
||||
start_colour2: colour2,
|
||||
speed,
|
||||
colour: colour1,
|
||||
count_flipped: false,
|
||||
use_colour1: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EffectState for Breathe {
|
||||
effect_state_impl!();
|
||||
|
||||
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);
|
||||
|
||||
if *colour_actual == Colour(0, 0, 0) {
|
||||
*use_colour1 = !*use_colour1;
|
||||
}
|
||||
|
||||
let colour = if !*use_colour1 { colour2 } else { 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
164
rog-aura/src/effects/doom.rs
Normal file
164
rog-aura/src/effects/doom.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::advanced::LedCode;
|
||||
use crate::effects::{p_random, EffectState};
|
||||
use crate::layouts::KeyLayout;
|
||||
use crate::{effect_state_impl, Colour};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct DoomFlicker {
|
||||
address: LedCode,
|
||||
start_colour: Colour,
|
||||
max_percentage: u8,
|
||||
min_percentage: u8,
|
||||
#[serde(skip)]
|
||||
count: u8,
|
||||
#[serde(skip)]
|
||||
colour: Colour,
|
||||
}
|
||||
|
||||
impl DoomFlicker {
|
||||
pub fn new(address: LedCode, colour: Colour, max_percentage: u8, min_percentage: u8) -> Self {
|
||||
Self {
|
||||
address,
|
||||
colour,
|
||||
count: 4,
|
||||
max_percentage,
|
||||
min_percentage,
|
||||
start_colour: colour,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EffectState for DoomFlicker {
|
||||
effect_state_impl!();
|
||||
|
||||
fn next_colour_state(&mut self, _layout: &KeyLayout) {
|
||||
let Self {
|
||||
max_percentage,
|
||||
min_percentage,
|
||||
colour,
|
||||
start_colour,
|
||||
..
|
||||
} = self;
|
||||
|
||||
if self.count == 0 {
|
||||
self.count = 4;
|
||||
}
|
||||
self.count -= 1;
|
||||
if self.count != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: make a "percentage" method on Colour.
|
||||
let max_light = Colour(
|
||||
(start_colour.0 as f32 / 100.0 * *max_percentage as f32) as u8,
|
||||
(start_colour.1 as f32 / 100.0 * *max_percentage as f32) as u8,
|
||||
(start_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(
|
||||
(start_colour.0 as f32 / 100.0 * *min_percentage as f32) as u8,
|
||||
(start_colour.1 as f32 / 100.0 * *min_percentage as f32) as u8,
|
||||
(start_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;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LightFlash {
|
||||
pub count: i32,
|
||||
pub max_light: i32,
|
||||
pub min_light: i32,
|
||||
pub max_time: i32,
|
||||
pub min_time: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct DoomLightFlash {
|
||||
address: LedCode,
|
||||
start_colour: Colour,
|
||||
max_percentage: u8,
|
||||
min_percentage: u8,
|
||||
#[serde(skip)]
|
||||
max_time: i32,
|
||||
#[serde(skip)]
|
||||
min_time: i32,
|
||||
#[serde(skip)]
|
||||
count: u8,
|
||||
#[serde(skip)]
|
||||
colour: Colour,
|
||||
}
|
||||
|
||||
impl DoomLightFlash {
|
||||
pub fn new(address: LedCode, colour: Colour, max_percentage: u8, min_percentage: u8) -> Self {
|
||||
Self {
|
||||
address,
|
||||
colour,
|
||||
count: 4,
|
||||
max_percentage,
|
||||
min_percentage,
|
||||
start_colour: colour,
|
||||
max_time: 32,
|
||||
min_time: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EffectState for DoomLightFlash {
|
||||
effect_state_impl!();
|
||||
|
||||
fn next_colour_state(&mut self, _layout: &KeyLayout) {
|
||||
let Self {
|
||||
max_percentage,
|
||||
min_percentage,
|
||||
colour,
|
||||
start_colour,
|
||||
..
|
||||
} = self;
|
||||
|
||||
self.count -= 1;
|
||||
if self.count != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: make a "percentage" method on Colour.
|
||||
let max_light = Colour(
|
||||
(start_colour.0 as f32 / 100.0 * *max_percentage as f32) as u8,
|
||||
(start_colour.1 as f32 / 100.0 * *max_percentage as f32) as u8,
|
||||
(start_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(
|
||||
(start_colour.0 as f32 / 100.0 * *min_percentage as f32) as u8,
|
||||
(start_colour.1 as f32 / 100.0 * *min_percentage as f32) as u8,
|
||||
(start_colour.2 as f32 / 100.0 * *min_percentage as f32) as u8,
|
||||
);
|
||||
|
||||
if *colour == max_light {
|
||||
*colour = min_light;
|
||||
self.count = ((p_random() & self.min_time) + 1) as u8;
|
||||
} else {
|
||||
*colour = max_light;
|
||||
self.count = ((p_random() & self.max_time) + 1) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
291
rog-aura/src/effects/mod.rs
Normal file
291
rog-aura/src/effects/mod.rs
Normal file
@@ -0,0 +1,291 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
mod doom;
|
||||
pub use doom::*;
|
||||
|
||||
mod base;
|
||||
pub use base::*;
|
||||
|
||||
mod breathe;
|
||||
pub use breathe::*;
|
||||
|
||||
mod static_;
|
||||
pub use static_::*;
|
||||
|
||||
use crate::advanced::{LedCode, LedUsbPackets, UsbPackets};
|
||||
use crate::layouts::KeyLayout;
|
||||
use crate::Colour;
|
||||
|
||||
// static mut RNDINDEX: usize = 0;
|
||||
static mut PRNDINDEX: usize = 0;
|
||||
|
||||
/// Pseudo random table ripped straight out of room4doom
|
||||
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 trait InputForEffect {
|
||||
/// Calculate the next colour state
|
||||
fn next_colour_state(&mut self);
|
||||
|
||||
/// Return the resulting colour. Implementers should store the colour to
|
||||
/// return it.
|
||||
fn get_colour(&self) -> Colour;
|
||||
}
|
||||
|
||||
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(&self) -> LedCode;
|
||||
|
||||
fn set_led(&mut self, address: LedCode);
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default)]
|
||||
pub struct AdvancedEffects {
|
||||
effects: Vec<Effect>,
|
||||
zoned: bool,
|
||||
}
|
||||
|
||||
impl AdvancedEffects {
|
||||
#[inline]
|
||||
pub fn new(zoned: bool) -> Self {
|
||||
Self {
|
||||
effects: Default::default(),
|
||||
zoned,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push(&mut self, action: Effect) {
|
||||
self.effects.push(action);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn insert(&mut self, index: usize, action: Effect) {
|
||||
self.effects.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.effects.len() {
|
||||
return Some(self.effects.remove(index));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn next_state(&mut self, layout: &KeyLayout) {
|
||||
for effect in &mut self.effects {
|
||||
effect.next_state(layout);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_packets(&self) -> UsbPackets {
|
||||
let mut usb_packets = if self.zoned {
|
||||
// TODO: figure out if that single byte difference for multizone actually
|
||||
// matters
|
||||
LedUsbPackets::new_zoned(true)
|
||||
} else {
|
||||
LedUsbPackets::new_per_key()
|
||||
};
|
||||
|
||||
for effect in &self.effects {
|
||||
let c = effect.colour();
|
||||
usb_packets.set(effect.led(), c.0, c.1, c.2);
|
||||
}
|
||||
usb_packets.into()
|
||||
}
|
||||
}
|
||||
|
||||
// how to be lazy
|
||||
#[macro_export]
|
||||
macro_rules! effect_state_impl {
|
||||
() => {
|
||||
fn get_colour(&self) -> $crate::Colour {
|
||||
self.colour
|
||||
}
|
||||
|
||||
fn get_led(&self) -> $crate::advanced::LedCode {
|
||||
self.address.clone()
|
||||
}
|
||||
|
||||
/// Change the led type
|
||||
fn set_led(&mut self, address: $crate::advanced::LedCode) {
|
||||
self.address = address;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A helper macro to quickly add new effects to the matching on `Effect`
|
||||
macro_rules! effect_impl {
|
||||
($($effect:ident),*) => {
|
||||
impl Effect {
|
||||
/// Get the type of LED set
|
||||
pub fn led(&self) -> $crate::advanced::LedCode {
|
||||
match self {
|
||||
$(Effect::$effect(c) => c.get_led(),)*
|
||||
}
|
||||
}
|
||||
|
||||
/// Change the led type (can be used to change location of the effect)
|
||||
pub fn set_led(&mut self, address: $crate::advanced::LedCode) {
|
||||
match self {
|
||||
$(Effect::$effect(c) => c.set_led(address),)*
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 colour(&self) -> $crate::Colour {
|
||||
match self {
|
||||
$(Effect::$effect(c) => c.get_colour(),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// The main effect container, a sequencer will call various methods on this to
|
||||
/// update states and get colours
|
||||
///
|
||||
/// Every effect is added here to quickly and easily match within `Effect`
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub enum Effect {
|
||||
Static(Static),
|
||||
Breathe(Breathe),
|
||||
DoomFlicker(DoomFlicker),
|
||||
DoomLightFlash(DoomLightFlash),
|
||||
}
|
||||
|
||||
impl Default for Effect {
|
||||
fn default() -> Self {
|
||||
Self::Static(Static::new(LedCode::default(), Colour::default()))
|
||||
}
|
||||
}
|
||||
|
||||
effect_impl!(Static, Breathe, DoomFlicker, DoomLightFlash);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::advanced::LedCode;
|
||||
use crate::effects::{AdvancedEffects, Breathe, DoomFlicker, Effect, Static};
|
||||
use crate::layouts::KeyLayout;
|
||||
use crate::{Colour, Speed};
|
||||
|
||||
#[test]
|
||||
fn single_key_next_state_then_create() {
|
||||
let layout = KeyLayout::default_layout();
|
||||
let mut seq = AdvancedEffects::new(false);
|
||||
seq.effects
|
||||
.push(Effect::Static(Static::new(LedCode::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::default_layout();
|
||||
let mut seq = AdvancedEffects::new(false);
|
||||
seq.effects.push(Effect::Breathe(Breathe::new(
|
||||
LedCode::F,
|
||||
Colour(255, 127, 0),
|
||||
Colour(127, 0, 255),
|
||||
Speed::Med,
|
||||
)));
|
||||
|
||||
let s =
|
||||
ron::ser::to_string_pretty(&seq, ron::ser::PrettyConfig::new().depth_limit(4)).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::default_layout();
|
||||
let mut seq = AdvancedEffects::new(false);
|
||||
seq.effects.push(Effect::DoomFlicker(DoomFlicker::new(
|
||||
LedCode::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);
|
||||
}
|
||||
}
|
||||
25
rog-aura/src/effects/static_.rs
Normal file
25
rog-aura/src/effects/static_.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::EffectState;
|
||||
use crate::advanced::LedCode;
|
||||
use crate::layouts::KeyLayout;
|
||||
use crate::{effect_state_impl, Colour};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Static {
|
||||
address: LedCode,
|
||||
/// The starting colour
|
||||
colour: Colour,
|
||||
}
|
||||
|
||||
impl Static {
|
||||
pub fn new(address: LedCode, colour: Colour) -> Self {
|
||||
Self { address, colour }
|
||||
}
|
||||
}
|
||||
|
||||
impl EffectState for Static {
|
||||
effect_state_impl!();
|
||||
|
||||
fn next_colour_state(&mut self, _layout: &KeyLayout) {}
|
||||
}
|
||||
Reference in New Issue
Block a user