199 lines
6.9 KiB
Rust
199 lines
6.9 KiB
Rust
use embassy_time::{Duration, Instant, Timer};
|
|
use futures::{select_biased, FutureExt};
|
|
use tgnt::button::Button;
|
|
|
|
use crate::{rgb::Rgb, util::wheel};
|
|
|
|
use super::{Event, EventKind, KbEvents, State, SWITCH_COUNT};
|
|
|
|
/// Duration until the keyboard starts the idle animation
|
|
const UNTIL_IDLE: Duration = Duration::from_secs(30);
|
|
|
|
///// Duration from idle until the keyboard goes to sleep
|
|
//const UNTIL_SLEEP: Duration = Duration::from_secs(10);
|
|
|
|
const IDLE_ANIMATION_SPEED: u64 = 3;
|
|
const IDLE_ANIMATION_KEY_OFFSET: u64 = 10;
|
|
const IDLE_BRIGHTNESS_RAMPUP: Duration = Duration::from_secs(120);
|
|
const MAX_IDLE_BRIGHTESS: f32 = 0.2;
|
|
const MIN_IDLE_BRIGHTESS: f32 = 0.05;
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum LightState {
|
|
Solid(Rgb),
|
|
#[allow(dead_code)]
|
|
SolidThenFade {
|
|
color: Rgb,
|
|
solid_until: Instant,
|
|
fade_by: f32,
|
|
},
|
|
FadeBy(f32),
|
|
None,
|
|
}
|
|
|
|
#[embassy_executor::task]
|
|
pub(super) async fn task(mut events: KbEvents, state: &'static State) {
|
|
let mut lights: [LightState; SWITCH_COUNT] = [LightState::None; SWITCH_COUNT];
|
|
let mut next_frame = Instant::now();
|
|
let mut idle_at = Instant::now() + UNTIL_IDLE;
|
|
loop {
|
|
select_biased! {
|
|
event = events.recv().fuse() => {
|
|
handle_event(event, state, &mut lights).await;
|
|
idle_at = Instant::now() + UNTIL_IDLE;
|
|
}
|
|
_ = Timer::at(next_frame).fuse() => {
|
|
tick(state, &mut lights).await;
|
|
next_frame = Instant::now() + Duration::from_millis(16);
|
|
}
|
|
_ = Timer::at(idle_at).fuse() => {
|
|
select_biased! {
|
|
event = events.recv().fuse() => {
|
|
state.lights.update(|lights| {
|
|
lights.iter_mut().for_each(|rgb| *rgb = Rgb::new(0, 0, 0));
|
|
}).await;
|
|
handle_event(event, state, &mut lights).await;
|
|
idle_at = Instant::now() + UNTIL_IDLE;
|
|
}
|
|
_ = idle_animation(state).fuse() => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn tick(state: &'static State, lights: &mut [LightState; SWITCH_COUNT]) {
|
|
let now = Instant::now();
|
|
|
|
state
|
|
.lights
|
|
.update(|rgbs| {
|
|
for (button, light) in lights.iter_mut().enumerate() {
|
|
let Some(&led_id) = state.led_map.get(button) else {
|
|
continue;
|
|
};
|
|
let Some(rgb) = rgbs.get_mut(led_id) else {
|
|
continue;
|
|
};
|
|
|
|
match &*light {
|
|
LightState::None => {}
|
|
LightState::FadeBy(fade) => {
|
|
let [r, g, b] = rgb
|
|
.components()
|
|
.map(|c| ((c as f32) * fade.clamp(0.0, 1.0)) as u8);
|
|
*rgb = Rgb::new(r, g, b);
|
|
if *rgb == Rgb::new(0, 0, 0) {
|
|
*light = LightState::None;
|
|
}
|
|
}
|
|
&LightState::Solid(color) => *rgb = color,
|
|
&LightState::SolidThenFade {
|
|
color,
|
|
solid_until,
|
|
fade_by,
|
|
} => {
|
|
*rgb = color;
|
|
if now >= solid_until {
|
|
*light = LightState::FadeBy(fade_by);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.await;
|
|
}
|
|
|
|
async fn idle_animation(state: &'static State) {
|
|
for tick in 0.. {
|
|
const FRAMETIME: Duration = Duration::from_millis(16);
|
|
|
|
state
|
|
.lights
|
|
.update(|lights| {
|
|
const N_MAX: u64 = IDLE_BRIGHTNESS_RAMPUP.as_millis() / FRAMETIME.as_millis();
|
|
|
|
let brightness = if tick >= N_MAX {
|
|
MAX_IDLE_BRIGHTESS
|
|
} else {
|
|
((tick as f32) / N_MAX as f32).clamp(MIN_IDLE_BRIGHTESS, MAX_IDLE_BRIGHTESS)
|
|
};
|
|
|
|
for (n, &i) in state.led_map.iter().enumerate() {
|
|
let Some(light) = lights.get_mut(i) else {
|
|
continue;
|
|
};
|
|
let rgb = wheel(
|
|
(n as u64 * IDLE_ANIMATION_KEY_OFFSET + tick * IDLE_ANIMATION_SPEED) as u8,
|
|
);
|
|
*light = rgb * brightness;
|
|
}
|
|
})
|
|
.await;
|
|
|
|
Timer::after(FRAMETIME).await;
|
|
}
|
|
}
|
|
|
|
async fn handle_event(
|
|
event: Event,
|
|
state: &'static State,
|
|
lights: &mut [LightState; SWITCH_COUNT],
|
|
) {
|
|
let rgb = match event.kind {
|
|
EventKind::Press { button } => match button {
|
|
Button::Key(..) => LightState::Solid(Rgb::new(0, 150, 0)),
|
|
Button::Mod(..) => LightState::Solid(Rgb::new(0, 0, 150)),
|
|
Button::ModTap(..) => LightState::Solid(Rgb::new(0, 0, 150)),
|
|
Button::Compose2(..) | Button::Compose3(..) => LightState::Solid(Rgb::new(0, 100, 100)),
|
|
Button::Layer(..) => LightState::Solid(Rgb::new(120, 0, 120)),
|
|
/*
|
|
Button::NextLayer | Button::PrevLayer => {
|
|
yield_now().await; // dirty hack to make sure layer_switch_task gets to run first
|
|
let layer = state.current_layer.load(Ordering::Relaxed);
|
|
let layer = min(layer, state.layers.len().saturating_sub(1) as u16);
|
|
let buttons_to_light_up = if state.layers.len() <= 3 {
|
|
match layer {
|
|
0 => [0, 1, 2, 3, 4].as_ref(),
|
|
1 => &[5, 6, 7, 8, 9],
|
|
2 => &[10, 11, 12, 13, 14],
|
|
_ => &[],
|
|
}
|
|
} else {
|
|
match layer {
|
|
0 => [0, 5, 10].as_ref(),
|
|
1 => &[1, 6, 11],
|
|
2 => &[2, 7, 12],
|
|
3 => &[3, 8, 13],
|
|
4 => &[4, 9, 14],
|
|
_ => &[],
|
|
}
|
|
};
|
|
|
|
let solid_until = Instant::now() + Duration::from_millis(200);
|
|
for &button in buttons_to_light_up {
|
|
let Some(light) = lights.get_mut(button) else { continue; };
|
|
*light = LightState::SolidThenFade {
|
|
color: Rgb::new(120, 0, 120),
|
|
solid_until,
|
|
fade_by: 0.85,
|
|
}
|
|
}
|
|
LightState::Solid(Rgb::new(100, 0, 100))
|
|
}
|
|
*/
|
|
_ => LightState::Solid(Rgb::new(150, 0, 0)),
|
|
},
|
|
EventKind::Release { .. } => LightState::FadeBy(0.85),
|
|
};
|
|
|
|
if event.source != state.half {
|
|
return;
|
|
}
|
|
|
|
let Some(light) = lights.get_mut(event.source_button) else {
|
|
return;
|
|
};
|
|
*light = rgb;
|
|
}
|