zmk/app/src/animation/animation_ripple.c

209 lines
8.8 KiB
C

/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_animation_ripple
#include <stdlib.h>
#include <math.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/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_hsl;
size_t *pixel_map;
size_t pixel_map_size;
size_t event_buffer_size;
uint8_t blending_mode;
uint8_t distance_per_frame;
uint8_t ripple_width;
uint8_t event_frames;
};
struct animation_ripple_data {
struct zmk_color_rgb color_rgb;
struct animation_ripple_event *event_buffer;
size_t events_start;
size_t events_end;
size_t num_events;
bool is_active;
};
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 (!data->is_active) {
return 0;
}
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;
zmk_animation_request_frames(config->event_frames);
return 0;
}
static void animation_ripple_render_frame(const struct device *dev, struct animation_pixel *pixels,
size_t num_pixels) {
const struct animation_ripple_config *config = dev->config;
struct animation_ripple_data *data = dev->data;
size_t *pixel_map = config->pixel_map;
size_t i = data->events_start;
while (i != data->events_end) {
struct animation_ripple_event *event = &data->event_buffer[i];
// Render all pixels for each event
for (int j = 0; j < config->pixel_map_size; ++j) {
uint8_t pixel_distance =
zmk_animation_get_pixel_distance(event->pixel_id, pixel_map[j]);
if (config->ripple_width > abs(pixel_distance - event->distance)) {
float intensity = 1.0f - (float)abs(pixel_distance - event->distance) /
(float)config->ripple_width;
struct zmk_color_rgb color = {
.r = intensity * data->color_rgb.r,
.g = intensity * data->color_rgb.g,
.b = intensity * data->color_rgb.b,
};
pixels[pixel_map[j]].value = zmk_apply_blending_mode(pixels[pixel_map[j]].value,
color, config->blending_mode);
}
}
// Update event counter
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_start(const struct device *dev) {
struct animation_ripple_data *data = dev->data;
data->is_active = true;
}
static void animation_ripple_stop(const struct device *dev) {
struct animation_ripple_data *data = dev->data;
data->is_active = false;
// Cancel processing of any ongoing events.
data->num_events = 0;
data->events_start = 0;
data->events_end = 0;
}
static int animation_ripple_init(const struct device *dev) {
const struct animation_ripple_config *config = dev->config;
struct animation_ripple_data *data = dev->data;
zmk_hsl_to_rgb(config->color_hsl, &data->color_rgb);
return 0;
}
static const struct animation_api animation_ripple_api = {
.on_start = animation_ripple_start,
.on_stop = animation_ripple_stop,
.render_frame = animation_ripple_render_frame,
};
#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 size_t animation_ripple_##idx##_pixel_map[] = DT_INST_PROP(idx, pixels); \
\
static uint32_t animation_ripple_##idx##_color = DT_INST_PROP(idx, color); \
\
static struct animation_ripple_config animation_ripple_##idx##_config = { \
.color_hsl = (struct zmk_color_hsl *)&animation_ripple_##idx##_color, \
.pixel_map = &animation_ripple_##idx##_pixel_map[0], \
.pixel_map_size = DT_INST_PROP_LEN(idx, pixels), \
.event_buffer_size = DT_INST_PROP(idx, buffer_size), \
.blending_mode = DT_INST_ENUM_IDX(idx, blending_mode), \
.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 = \
255 / ((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);