Implement a 'ripple' per-key effect animation
This commit is contained in:
parent
df35aa5547
commit
30e0903d8e
3 changed files with 221 additions and 0 deletions
|
@ -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_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/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_solid.c)
|
||||||
target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation.c)
|
target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation.c)
|
||||||
|
|
||||||
|
|
46
app/dts/bindings/animations/zmk,animation-ripple.yaml
Normal file
46
app/dts/bindings/animations/zmk,animation-ripple.yaml
Normal 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.
|
174
app/src/animation/animation_ripple.c
Normal file
174
app/src/animation/animation_ripple.c
Normal 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);
|
Loading…
Add table
Reference in a new issue