Port typing_heatmap animation to LED Matrix (#26300)

This commit is contained in:
Joel Challis
2026-08-02 20:10:35 +01:00
committed by GitHub
parent 5351ac6c66
commit ae0aaa50dc
4 changed files with 163 additions and 0 deletions
+45
View File
@@ -119,6 +119,7 @@ enum led_matrix_effects {
LED_MATRIX_SOLID_MULTISPLASH, // Value pulses away from multiple key hits then fades out
LED_MATRIX_WAVE_LEFT_RIGHT, // Sine wave scrolling from left to right
LED_MATRIX_WAVE_UP_DOWN, // Sine wave scrolling from up to down
LED_MATRIX_TYPING_HEATMAP, // How hot is your WPM!
LED_MATRIX_EFFECT_MAX
};
```
@@ -140,6 +141,14 @@ You can enable a single effect by defining `ENABLE_[EFFECT_NAME]` in your `confi
|`#define ENABLE_LED_MATRIX_WAVE_LEFT_RIGHT` |Enables `LED_MATRIX_WAVE_LEFT_RIGHT` |
|`#define ENABLE_LED_MATRIX_WAVE_UP_DOWN` |Enables `LED_MATRIX_WAVE_UP_DOWN` |
|Framebuffer Defines |Description |
|------------------------------------------------------|-----------------------------------------------|
|`#define ENABLE_LED_MATRIX_TYPING_HEATMAP` |Enables `LED_MATRIX_TYPING_HEATMAP` |
::: tip
These modes introduce additional logic that can increase firmware size.
:::
|Reactive Defines |Description |
|-------------------------------------------------------|----------------------------------------------|
|`#define ENABLE_LED_MATRIX_SOLID_REACTIVE_SIMPLE` |Enables `LED_MATRIX_SOLID_REACTIVE_SIMPLE` |
@@ -156,6 +165,42 @@ You can enable a single effect by defining `ENABLE_[EFFECT_NAME]` in your `confi
These modes introduce additional logic that can increase firmware size.
:::
### LED Matrix Effect Typing Heatmap {#led-matrix-effect-typing-heatmap}
This effect will scale the LED matrix brightness according to a heatmap of recently pressed keys. Whenever a key is pressed its "temperature" increases as well as that of its neighboring keys. The temperature of each key is then decreased automatically every 25 milliseconds by default.
In order to change the delay of temperature decrease define `LED_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS`:
```c
#define LED_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS 50
```
As heatmap uses the physical position of the leds set in the g_led_config, you may need to tweak the following options to get the best effect for your keyboard. Note the size of this grid is `224x64`.
Limit the distance the effect spreads to surrounding keys.
```c
#define LED_MATRIX_TYPING_HEATMAP_SPREAD 40
```
Limit how hot surrounding keys get from each press.
```c
#define LED_MATRIX_TYPING_HEATMAP_AREA_LIMIT 16
```
Remove the spread effect entirely.
```c
#define LED_MATRIX_TYPING_HEATMAP_SLIM
```
It's also possible to adjust the tempo of *heating up*. It's defined as the number of steps by which to increment the brightness. Decreasing this value increases the number of keystrokes needed to fully heat up the key.
```c
#define LED_MATRIX_TYPING_HEATMAP_INCREASE_STEP 32
```
## Custom LED Matrix Effects {#custom-led-matrix-effects}
By setting `LED_MATRIX_CUSTOM_USER = yes` in `rules.mk`, new effects can be defined directly from your keymap or userspace, without having to edit any QMK core files. To declare new effects, create a `led_matrix_user.inc` file in the user keymap directory or userspace folder.
@@ -16,3 +16,4 @@
#include "solid_splash_anim.h"
#include "wave_left_right_anim.h"
#include "wave_up_down_anim.h"
#include "typing_heatmap_anim.h"
@@ -0,0 +1,112 @@
#if defined(LED_MATRIX_FRAMEBUFFER_EFFECTS) && defined(ENABLE_LED_MATRIX_TYPING_HEATMAP)
LED_MATRIX_EFFECT(TYPING_HEATMAP)
# ifdef LED_MATRIX_CUSTOM_EFFECT_IMPLS
# ifndef LED_MATRIX_TYPING_HEATMAP_INCREASE_STEP
# define LED_MATRIX_TYPING_HEATMAP_INCREASE_STEP 32
# endif
# ifndef LED_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS
# define LED_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS 25
# endif
# ifndef LED_MATRIX_TYPING_HEATMAP_SPREAD
# define LED_MATRIX_TYPING_HEATMAP_SPREAD 40
# endif
# ifndef LED_MATRIX_TYPING_HEATMAP_AREA_LIMIT
# define LED_MATRIX_TYPING_HEATMAP_AREA_LIMIT 16
# endif
void process_led_matrix_typing_heatmap(uint8_t row, uint8_t col) {
# ifdef LED_MATRIX_TYPING_HEATMAP_SLIM
// Limit effect to pressed keys
g_led_frame_buffer[row][col] = qadd8(g_led_frame_buffer[row][col], LED_MATRIX_TYPING_HEATMAP_INCREASE_STEP);
# else
if (g_led_config.matrix_co[row][col] == NO_LED) { // skip as pressed key doesn't have an led position
return;
}
for (uint8_t i_row = 0; i_row < MATRIX_ROWS; i_row++) {
for (uint8_t i_col = 0; i_col < MATRIX_COLS; i_col++) {
if (g_led_config.matrix_co[i_row][i_col] == NO_LED) { // skip as target key doesn't have an led position
continue;
}
if (i_row == row && i_col == col) {
g_led_frame_buffer[row][col] = qadd8(g_led_frame_buffer[row][col], LED_MATRIX_TYPING_HEATMAP_INCREASE_STEP);
} else {
# define LED_DISTANCE(led_a, led_b) sqrt16(((int32_t)(led_a.x - led_b.x) * (int32_t)(led_a.x - led_b.x)) + ((int32_t)(led_a.y - led_b.y) * (int32_t)(led_a.y - led_b.y)))
uint8_t distance = LED_DISTANCE(g_led_config.point[g_led_config.matrix_co[row][col]], g_led_config.point[g_led_config.matrix_co[i_row][i_col]]);
# undef LED_DISTANCE
if (distance <= LED_MATRIX_TYPING_HEATMAP_SPREAD) {
uint8_t amount = qsub8(LED_MATRIX_TYPING_HEATMAP_SPREAD, distance);
if (amount > LED_MATRIX_TYPING_HEATMAP_AREA_LIMIT) {
amount = LED_MATRIX_TYPING_HEATMAP_AREA_LIMIT;
}
g_led_frame_buffer[i_row][i_col] = qadd8(g_led_frame_buffer[i_row][i_col], amount);
}
}
}
}
# endif
}
// A timer to track the last time we decremented all heatmap values.
static uint16_t heatmap_decrease_timer;
// Whether we should decrement the heatmap values during the next update.
static bool decrease_heatmap_values;
bool TYPING_HEATMAP(effect_params_t* params) {
LED_MATRIX_USE_LIMITS(led_min, led_max);
if (params->init) {
led_matrix_set_value_all(0);
memset(g_led_frame_buffer, 0, sizeof g_led_frame_buffer);
}
// The heatmap animation might run in several iterations depending on
// `LED_MATRIX_LED_PROCESS_LIMIT`, therefore we only want to update the
// timer when the animation starts.
if (params->iter == 0) {
decrease_heatmap_values = timer_elapsed(heatmap_decrease_timer) >= LED_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS;
// Restart the timer if we are going to decrease the heatmap this frame.
if (decrease_heatmap_values) {
heatmap_decrease_timer = timer_read();
}
}
// Render heatmap & decrease
uint8_t count = 0;
for (uint8_t row = 0; row < MATRIX_ROWS && count < LED_MATRIX_LED_PROCESS_LIMIT; row++) {
for (uint8_t col = 0; col < MATRIX_COLS && LED_MATRIX_LED_PROCESS_LIMIT; col++) {
uint8_t val = g_led_frame_buffer[row][col];
bool processed = false;
uint8_t led[LED_HITS_TO_REMEMBER];
uint8_t led_count = led_matrix_map_row_column_to_led(row, col, led);
for (uint8_t index = 0; index < led_count; index++) {
uint8_t led_index = led[index];
if (led_index >= led_min && led_index < led_max) {
count++;
if (!HAS_ANY_FLAGS(g_led_config.flags[led_index], params->flags)) {
continue;
}
uint8_t value = scale8(led_matrix_eeconfig.val, val);
led_matrix_set_value(led_index, value);
processed = true;
}
}
if (processed && decrease_heatmap_values) {
g_led_frame_buffer[row][col] = qsub8(val, 1);
}
}
}
return led_matrix_check_finished_leds(led_max);
}
# endif // LED_MATRIX_CUSTOM_EFFECT_IMPLS
#endif // defined(LED_MATRIX_FRAMEBUFFER_EFFECTS) && defined(ENABLE_LED_MATRIX_TYPING_HEATMAP)
+5
View File
@@ -5,6 +5,11 @@
// clang-format off
// framebuffer
#if defined(ENABLE_LED_MATRIX_TYPING_HEATMAP)
# define LED_MATRIX_FRAMEBUFFER_EFFECTS
#endif
// reactive
#if defined(ENABLE_LED_MATRIX_SOLID_REACTIVE_SIMPLE) || \
defined(ENABLE_LED_MATRIX_SOLID_REACTIVE_WIDE) || \