Implement a 'ripple' per-key effect animation

This commit is contained in:
Kuba Birecki 2021-12-12 08:44:18 +01:00
parent df35aa5547
commit 30e0903d8e
3 changed files with 221 additions and 0 deletions

View file

@ -81,6 +81,7 @@ target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/battery.c)
target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/color.c)
target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation_ripple.c)
target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation_solid.c)
target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation.c)

View file

@ -0,0 +1,46 @@
# Copyright (c) 2020, The ZMK Contributors
# SPDX-License-Identifier: MIT
description: |
Animated 'ripples' trigered by key presses.
compatible: "zmk,animation-ripple"
include: animation_base.yaml
properties:
label:
type: string
required: true
duration:
type: int
required: false
default: 1000
description: |
Approximate ripple travel time in milliseconds.
color:
type: int
required: true
description: |
Ripple color.
buffer-size:
type: int
default: 10
description: |
This will limit how many keystroke events the animation is able to track
at the same time. Depending on how fast you type and the animation duration
you might need to increase this number.
ripple-width:
type: int
default: 25
description: |
This setting determines the thickness of the ripple 'ring'.
Think of it as antialiasing. The further apart the pixels are, or if they're
spaced irregularly, the larger this number should be. Otherwise the animation
will look uneven or LEDs might not light up at all.
The effect is especially pronounced when lowering the effect duration or running
low FPS.

View file

@ -0,0 +1,174 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_animation_ripple
#include <zephyr.h>
#include <device.h>
#include <stdlib.h>
#include <math.h>
#include <logging/log.h>
#include <drivers/animation.h>
#include <zmk/animation.h>
#include <zmk/event_manager.h>
#include <zmk/events/position_state_changed.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct animation_ripple_event {
size_t pixel_id;
uint16_t distance;
uint8_t counter;
};
struct animation_ripple_config {
struct zmk_color_hsl *color;
size_t event_buffer_size;
uint8_t distance_per_frame;
uint8_t ripple_width;
uint8_t event_frames;
};
struct animation_ripple_data {
struct animation_ripple_event *event_buffer;
size_t events_start;
size_t events_end;
size_t num_events;
};
static int animation_ripple_on_key_press(const struct device *dev, const zmk_event_t *event) {
const struct animation_ripple_config *config = dev->config;
struct animation_ripple_data *data = dev->data;
const struct zmk_position_state_changed *pos_event;
if ((pos_event = as_zmk_position_state_changed(event)) == NULL) {
// Event not supported.
return -ENOTSUP;
}
if (!pos_event->state) {
// Don't track key releases.
return 0;
}
if (data->num_events == config->event_buffer_size) {
// Event buffer is full - new key press events are dropped.
return -ENOMEM;
}
data->event_buffer[data->events_end].pixel_id =
zmk_animation_get_pixel_by_key_position(pos_event->position);
data->event_buffer[data->events_end].distance = 0;
data->event_buffer[data->events_end].counter = 0;
data->events_end = (data->events_end + 1) % config->event_buffer_size;
data->num_events += 1;
return 0;
}
static void animation_ripple_on_after_frame(const struct device *dev) {
const struct animation_ripple_config *config = dev->config;
struct animation_ripple_data *data = dev->data;
size_t i = data->events_start;
while (i != data->events_end) {
struct animation_ripple_event *event = &data->event_buffer[i];
if (event->counter < config->event_frames) {
event->distance += config->distance_per_frame;
event->counter += 1;
} else {
data->events_start = (data->events_start + 1) % config->event_buffer_size;
data->num_events -= 1;
}
if (++i == config->event_buffer_size) {
i = 0;
}
}
}
static void animation_ripple_render_pixel(const struct device *dev,
const struct animation_pixel *pixel,
struct zmk_color_rgb *value) {
const struct animation_ripple_config *config = dev->config;
struct animation_ripple_data *data = dev->data;
size_t i = data->events_start;
while (i != data->events_end) {
const struct animation_ripple_event *event = &data->event_buffer[i];
uint16_t pixel_distance = zmk_animation_get_pixel_distance(event->pixel_id, pixel->id);
if (config->ripple_width > abs(pixel_distance - event->distance)) {
float intensity =
(float)abs(pixel_distance - event->distance) / (float)config->ripple_width;
value->r = value->r < intensity ? intensity : value->r;
value->g = value->g < intensity ? intensity : value->g;
value->b = value->b < intensity ? intensity : value->b;
}
if (++i == config->event_buffer_size) {
i = 0;
}
}
}
static int animation_ripple_init(const struct device *dev) { return 0; }
static const struct animation_api animation_ripple_api = {
.on_before_frame = NULL,
.on_after_frame = animation_ripple_on_after_frame,
.render_pixel = animation_ripple_render_pixel,
};
#define ANIMATION_RIPPLE_DEVICE(idx) \
\
static struct animation_ripple_event \
animation_ripple_##idx##_events[DT_INST_PROP(idx, buffer_size)]; \
\
static struct animation_ripple_data animation_ripple_##idx##_data = { \
.event_buffer = animation_ripple_##idx##_events, \
.events_start = 0, \
.events_end = 0, \
.num_events = 0, \
}; \
\
static uint32_t animation_ripple_##idx##_color = DT_INST_PROP(idx, color); \
\
static struct animation_ripple_config animation_ripple_##idx##_config = { \
.color = (struct zmk_color_hsl *)&animation_ripple_##idx##_color, \
.event_buffer_size = DT_INST_PROP(idx, buffer_size), \
.distance_per_frame = \
(255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS, \
.ripple_width = DT_INST_PROP(idx, ripple_width) / 2, \
.event_frames = \
360 / ((255 * 1000 / DT_INST_PROP(idx, duration)) / CONFIG_ZMK_ANIMATION_FPS), \
}; \
\
DEVICE_DT_INST_DEFINE(idx, &animation_ripple_init, NULL, &animation_ripple_##idx##_data, \
&animation_ripple_##idx##_config, POST_KERNEL, \
CONFIG_APPLICATION_INIT_PRIORITY, &animation_ripple_api); \
\
static int animation_ripple_##idx##_event_handler(const zmk_event_t *event) { \
const struct device *dev = DEVICE_DT_GET(DT_DRV_INST(idx)); \
\
return animation_ripple_on_key_press(dev, event); \
} \
\
ZMK_LISTENER(animation_ripple_##idx, animation_ripple_##idx##_event_handler); \
ZMK_SUBSCRIPTION(animation_ripple_##idx, zmk_position_state_changed);
DT_INST_FOREACH_STATUS_OKAY(ANIMATION_RIPPLE_DEVICE);