mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-01-22 17:33:19 +01:00
rog-aura: Add flicker effect
This commit is contained in:
@@ -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)
|
||||
|
||||
61
MANUAL.md
61
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": <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`.
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
249
rog-aura/src/sequencer/effects.rs
Normal file
249
rog-aura/src/sequencer/effects.rs
Normal 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!();
|
||||
}
|
||||
218
rog-aura/src/sequencer/mod.rs
Normal file
218
rog-aura/src/sequencer/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user