From 873f543177ee5b2f39cab3778cca6c3f14560f43 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Mon, 16 Feb 2026 12:21:12 +0100 Subject: [PATCH] feat(keycolors): add fade-in at startup and fade-out when not used --- keycolors/keycolors.c | 148 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 141 insertions(+), 7 deletions(-) diff --git a/keycolors/keycolors.c b/keycolors/keycolors.c index fb93a7e..5cda6ad 100644 --- a/keycolors/keycolors.c +++ b/keycolors/keycolors.c @@ -4,14 +4,14 @@ #include QMK_KEYBOARD_H #if COMMUNITY_MODULE_ORYX_ENABLE == TRUE -#include +# include #endif ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 1, 0); extern const uint8_t PROGMEM ledmap[][RGB_MATRIX_LED_COUNT][3]; -RGB keycolors_hsv_to_rgb_with_value(HSV hsv) { +static RGB keycolors_hsv_to_rgb_with_value(HSV hsv) { RGB rgb = hsv_to_rgb(hsv); float f = (float)rgb_matrix_config.hsv.v / UINT8_MAX; return (RGB){f * rgb.r, f * rgb.g, f * rgb.b}; @@ -33,13 +33,123 @@ static void keycolors_set_layer_color(int layer) { } } -bool rgb_matrix_indicators_user(void) { +extern const uint32_t startup_fade_in_duration_ms; +extern const uint32_t inactivity_dim_duration_ms; +extern const uint32_t active_again_duration_ms; + +extern const uint8_t inactivity_brightness; +extern const uint8_t normal_brightness; + +extern const uint32_t inactivity_threshold_ms; + +typedef enum { + // Keyboard is starting up. Play fade-in animation. + STARTUP_FADE_IN, + // Normal keyboard operation, brightness is set to normal. + NORMAL, + // Keyboard has been left untouched for a while, slowly reduce brightness to save power. + INACTIVE_DIMMING, + // Keyboard is still inactive, brightness has been reduced to its target value. + INACTIVE_DIMMED, + // Keyboard was inactive but was just touched, increase the brightness again to normal. + ACTIVE_AGAIN, +} animation_state_t; + +static uint8_t target_brightness; +static uint32_t current_animation_end_time; +int8_t current_animation_slope; // Unit: ms / brightness +static animation_state_t current_state; + +static bool keycolors_is_animation_playing(void) { + return current_state == STARTUP_FADE_IN || current_state == INACTIVE_DIMMING || current_state == ACTIVE_AGAIN; +} + +static uint32_t last_keypress_time; +static bool key_currently_pressed; + +static bool keycolors_keyboard_is_inactive(const uint32_t now) { + if (key_currently_pressed) { + return false; + } + + return (now - last_keypress_time > inactivity_threshold_ms); +} + +static void keycolors_trigger_animation(const uint32_t now, const uint32_t animation_duration_ms, const uint8_t initial_brightness, const uint8_t final_brightness, animation_state_t new_state) { + const int32_t expected_brightness_delta = (int32_t)final_brightness - (int32_t)initial_brightness; + const int32_t actual_brightness_delta = (int32_t)final_brightness - (int32_t)rgb_matrix_config.hsv.v; + + current_animation_slope = (int32_t)animation_duration_ms / expected_brightness_delta; + current_animation_end_time = now + actual_brightness_delta * current_animation_slope; + current_state = new_state; + target_brightness = final_brightness; +} + +static void keycolors_trigger_startup_fade_in(const uint32_t now) { + keycolors_trigger_animation(now, startup_fade_in_duration_ms, 0, normal_brightness, STARTUP_FADE_IN); +} + +static void keycolors_trigger_inactivity_dimming(const uint32_t now) { + keycolors_trigger_animation(now, inactivity_dim_duration_ms, normal_brightness, inactivity_brightness, INACTIVE_DIMMING); +} + +static void keycolors_trigger_active_again(const uint32_t now) { + keycolors_trigger_animation(now, active_again_duration_ms, inactivity_brightness, normal_brightness, ACTIVE_AGAIN); +} + +static void keycolors_mark_animation_finished(const uint32_t now) { + switch (current_state) { + case STARTUP_FADE_IN: + last_keypress_time = now; + current_state = NORMAL; + break; + case NORMAL: + break; + case INACTIVE_DIMMING: + current_state = INACTIVE_DIMMED; + break; + case INACTIVE_DIMMED: + break; + case ACTIVE_AGAIN: + current_state = NORMAL; + break; + } +} + +static void keycolors_adjust_led_brightness(void) { + const uint32_t now = timer_read32(); + + if (current_state == NORMAL && keycolors_keyboard_is_inactive(now)) { + keycolors_trigger_inactivity_dimming(now); + } + + bool currently_playing_animation = keycolors_is_animation_playing(); + + if (now > current_animation_end_time) { + if (!currently_playing_animation) { + return; + } + + keycolors_mark_animation_finished(now); + } + + int32_t computed_brightness = currently_playing_animation ? target_brightness - ((int32_t)(current_animation_end_time - now) / (int32_t)current_animation_slope) : target_brightness; + uint8_t brightness = (computed_brightness > RGB_MATRIX_MAXIMUM_BRIGHTNESS) ? RGB_MATRIX_MAXIMUM_BRIGHTNESS : (computed_brightness < 0) ? 0 : computed_brightness; + + hsv_t color = rgb_matrix_get_hsv(); + color.v = brightness; + rgb_matrix_sethsv_noeeprom(color.h, color.s, color.v); +} + +bool rgb_matrix_indicators_keycolors_kb(void) { #if COMMUNITY_MODULE_ORYX_ENABLE == TRUE - if (rawhid_state.rgb_control) { - return false; - } + if (rawhid_state.rgb_control) { + return false; + } #endif + keycolors_adjust_led_brightness(); + if (!keyboard_config.disable_layer_led) { switch (biton32(layer_state)) { case 1: @@ -59,5 +169,29 @@ bool rgb_matrix_indicators_user(void) { } } - return true; + return rgb_matrix_indicators_keycolors_user(); +} + +void keyboard_post_init_keycolors(void) { + // Technically not exactly 0 but close enough since this function is called during + // initialization. + uint32_t now = 0; + keycolors_trigger_startup_fade_in(now); + + hsv_t color = rgb_matrix_get_hsv(); + color.v = 0; + rgb_matrix_sethsv_noeeprom(color.h, color.s, color.v); +} + +bool pre_process_record_keycolors_kb(uint16_t keycode, keyrecord_t *record) { + const uint32_t now = timer_read32(); + + if (keycolors_keyboard_is_inactive(now)) { + keycolors_trigger_active_again(now); + } + + last_keypress_time = now; + key_currently_pressed = record->event.pressed; + + return pre_process_record_keycolors_user(keycode, record); }