Merge 180882cb89
into eaeea4bdfa
This commit is contained in:
commit
92d0c27c6e
29 changed files with 1890 additions and 1 deletions
|
@ -80,6 +80,14 @@ target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/behaviors/behavior_bac
|
||||||
target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/events/battery_state_changed.c)
|
target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/events/battery_state_changed.c)
|
||||||
target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/battery.c)
|
target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING 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_compose.c)
|
||||||
|
target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation_control.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)
|
||||||
|
target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/behaviors/behavior_animation.c)
|
||||||
|
|
||||||
target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c)
|
target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c)
|
||||||
add_subdirectory(src/split)
|
add_subdirectory(src/split)
|
||||||
|
|
||||||
|
|
18
app/Kconfig
18
app/Kconfig
|
@ -305,6 +305,24 @@ config ZMK_BACKLIGHT_AUTO_OFF_USB
|
||||||
#ZMK_BACKLIGHT
|
#ZMK_BACKLIGHT
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
config ZMK_ANIMATION
|
||||||
|
bool "RGB Animations"
|
||||||
|
select LED_STRIP
|
||||||
|
|
||||||
|
if ZMK_ANIMATION
|
||||||
|
|
||||||
|
config ZMK_ANIMATION_FPS
|
||||||
|
int "Animation FPS"
|
||||||
|
range 1 60
|
||||||
|
default 30
|
||||||
|
|
||||||
|
config ZMK_ANIMATION_PIXEL_DISTANCE
|
||||||
|
bool "Generate a lookup table for distances between pixels"
|
||||||
|
default y
|
||||||
|
|
||||||
|
#ZMK_ANIMATION
|
||||||
|
endif
|
||||||
|
|
||||||
#Display/LED Options
|
#Display/LED Options
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
add_subdirectory_ifdef(CONFIG_ZMK_DRIVERS_GPIO gpio)
|
add_subdirectory_ifdef(CONFIG_ZMK_DRIVERS_GPIO gpio)
|
||||||
|
add_subdirectory_ifdef(CONFIG_ZMK_ANIUMATION animation)
|
||||||
add_subdirectory(kscan)
|
add_subdirectory(kscan)
|
||||||
add_subdirectory(sensor)
|
add_subdirectory(sensor)
|
||||||
add_subdirectory(display)
|
add_subdirectory(display)
|
||||||
|
|
13
app/dts/animation.dtsi
Normal file
13
app/dts/animation.dtsi
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/ {
|
||||||
|
/omit-if-no-ref/ pixel: animation_pixel {
|
||||||
|
compatible = "zmk,animation-pixel";
|
||||||
|
label = "PIXEL";
|
||||||
|
#pixel-cells = <2>;
|
||||||
|
};
|
||||||
|
};
|
|
@ -18,4 +18,5 @@
|
||||||
#include <behaviors/caps_word.dtsi>
|
#include <behaviors/caps_word.dtsi>
|
||||||
#include <behaviors/key_repeat.dtsi>
|
#include <behaviors/key_repeat.dtsi>
|
||||||
#include <behaviors/backlight.dtsi>
|
#include <behaviors/backlight.dtsi>
|
||||||
#include <behaviors/macros.dtsi>
|
#include <behaviors/macros.dtsi>
|
||||||
|
#include <behaviors/animation.dtsi>
|
||||||
|
|
15
app/dts/behaviors/animation.dtsi
Normal file
15
app/dts/behaviors/animation.dtsi
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/ {
|
||||||
|
behaviors {
|
||||||
|
/omit-if-no-ref/ animation: behavior_animation {
|
||||||
|
compatible = "zmk,behavior-animation";
|
||||||
|
label = "ANIMATION";
|
||||||
|
#binding-cells = <1>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
22
app/dts/bindings/animations/animation_base.yaml
Normal file
22
app/dts/bindings/animations/animation_base.yaml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Copyright (c) 2020, The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
properties:
|
||||||
|
pixels:
|
||||||
|
type: array
|
||||||
|
required: true
|
||||||
|
description: |
|
||||||
|
Pixel positions to which the animation should apply.
|
||||||
|
|
||||||
|
blending-mode:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- "normal"
|
||||||
|
- "multiply"
|
||||||
|
- "lighten"
|
||||||
|
- "darken"
|
||||||
|
- "screen"
|
||||||
|
- "subtract"
|
||||||
|
default: "normal"
|
||||||
|
description: |
|
||||||
|
Blending mode for the animation to use during render.
|
15
app/dts/bindings/animations/zmk,animation-compose.yaml
Normal file
15
app/dts/bindings/animations/zmk,animation-compose.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Copyright (c) 2020, The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
description: |
|
||||||
|
Higher-order animation which allows for composing other animation
|
||||||
|
drivers and using different blending modes.
|
||||||
|
|
||||||
|
compatible: "zmk,animation-compose"
|
||||||
|
|
||||||
|
properties:
|
||||||
|
animations:
|
||||||
|
type: phandles
|
||||||
|
required: true
|
||||||
|
description: |
|
||||||
|
Animations to be combined.
|
26
app/dts/bindings/animations/zmk,animation-control.yaml
Normal file
26
app/dts/bindings/animations/zmk,animation-control.yaml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# Copyright (c) 2020, The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
description: |
|
||||||
|
Higher-order animation which allows for controlling animation drivers
|
||||||
|
placed underneath it by turning them on and off, cycling though them,
|
||||||
|
or changing the brightness.
|
||||||
|
|
||||||
|
compatible: "zmk,animation-control"
|
||||||
|
|
||||||
|
properties:
|
||||||
|
label:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
|
||||||
|
animations:
|
||||||
|
type: phandles
|
||||||
|
required: true
|
||||||
|
description: |
|
||||||
|
Animations to be combined.
|
||||||
|
|
||||||
|
brightness-steps:
|
||||||
|
type: int
|
||||||
|
default: 5
|
||||||
|
description: |
|
||||||
|
How many brightness steps should be supported.
|
41
app/dts/bindings/animations/zmk,animation-ripple.yaml
Normal file
41
app/dts/bindings/animations/zmk,animation-ripple.yaml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# 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:
|
||||||
|
duration:
|
||||||
|
type: int
|
||||||
|
default: 1000
|
||||||
|
description: |
|
||||||
|
Approximate ripple travel time in milliseconds.
|
||||||
|
|
||||||
|
color:
|
||||||
|
type: int
|
||||||
|
required: true
|
||||||
|
description: |
|
||||||
|
Ripple color in HSL format.
|
||||||
|
|
||||||
|
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.
|
24
app/dts/bindings/animations/zmk,animation-solid.yaml
Normal file
24
app/dts/bindings/animations/zmk,animation-solid.yaml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Copyright (c) 2020, The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
description: |
|
||||||
|
Animation for displaying solid colors across the entire area
|
||||||
|
|
||||||
|
compatible: "zmk,animation-solid"
|
||||||
|
|
||||||
|
include: animation_base.yaml
|
||||||
|
|
||||||
|
properties:
|
||||||
|
duration:
|
||||||
|
type: int
|
||||||
|
default: 5
|
||||||
|
description: |
|
||||||
|
Animation duration in seconds.
|
||||||
|
This is the time it takes for the animation to complete a full cycle and return to the original color.
|
||||||
|
Ignored if only a single color is given.
|
||||||
|
|
||||||
|
colors:
|
||||||
|
type: array
|
||||||
|
required: true
|
||||||
|
description: |
|
||||||
|
The colors to cycle through during the animation in HSL format.
|
8
app/dts/bindings/behaviors/zmk,behavior-animation.yaml
Normal file
8
app/dts/bindings/behaviors/zmk,behavior-animation.yaml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Copyright (c) 2020 The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
description: Animation Action
|
||||||
|
|
||||||
|
compatible: "zmk,behavior-animation"
|
||||||
|
|
||||||
|
include: one_param.yaml
|
10
app/dts/bindings/zmk,animation-pixel.yaml
Normal file
10
app/dts/bindings/zmk,animation-pixel.yaml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Copyright (c) 2020, The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
description: Pixel configuration
|
||||||
|
|
||||||
|
compatible: "zmk,animation-pixel"
|
||||||
|
|
||||||
|
pixel-cells:
|
||||||
|
- position_x
|
||||||
|
- position_y
|
36
app/dts/bindings/zmk,animation.yaml
Normal file
36
app/dts/bindings/zmk,animation.yaml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Copyright (c) 2020, The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
description: Global animation configuration
|
||||||
|
|
||||||
|
compatible: "zmk,animation"
|
||||||
|
|
||||||
|
properties:
|
||||||
|
drivers:
|
||||||
|
type: phandles
|
||||||
|
required: true
|
||||||
|
description: |
|
||||||
|
This array should contain all driver devices responsible for illuminating animated LEDs.
|
||||||
|
The devices must implement Zephyr's LED Strip Interface.
|
||||||
|
|
||||||
|
chain-lengths:
|
||||||
|
type: array
|
||||||
|
required: true
|
||||||
|
description: |
|
||||||
|
This field contains the number of LEDs controlled by each driver device.
|
||||||
|
|
||||||
|
pixels:
|
||||||
|
type: phandle-array
|
||||||
|
required: true
|
||||||
|
description: |
|
||||||
|
This field contains the pixel configuration for the entire board.
|
||||||
|
The order of this array determines in what order pixels are sent to the driver device API.
|
||||||
|
If multiple driving devices are used, their chain-length property determines the size of the buffer for each device.
|
||||||
|
|
||||||
|
key-pixels:
|
||||||
|
type: array
|
||||||
|
description: |
|
||||||
|
Use this field to specify the pixel index corresponding to each key
|
||||||
|
following the order used in your keymap.
|
||||||
|
When left unspecified, the driver assumes that for every key, the pixel has a matching id.
|
||||||
|
So for N keys, the first N pixels are exactly in the same order as keys in your keymap.
|
86
app/include/drivers/animation.h
Normal file
86
app/include/drivers/animation.h
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zephyr/types.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/led_strip.h>
|
||||||
|
|
||||||
|
#include <zmk/animation.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* #brief Public API for controlling animations.
|
||||||
|
*
|
||||||
|
* This library abstracts the implementation details
|
||||||
|
* for various types of 2D animations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct animation_pixel {
|
||||||
|
const uint8_t position_x;
|
||||||
|
const uint8_t position_y;
|
||||||
|
|
||||||
|
struct zmk_color_rgb value;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef animation_start
|
||||||
|
* @brief Callback API for starting an animation.
|
||||||
|
*
|
||||||
|
* @see animation_start() for argument descriptions.
|
||||||
|
*/
|
||||||
|
typedef void (*animation_api_start)(const struct device *dev);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef animation_stop
|
||||||
|
* @brief Callback API for stopping an animation.
|
||||||
|
*
|
||||||
|
* @see animation_stop() for argument descriptions.
|
||||||
|
*/
|
||||||
|
typedef void (*animation_api_stop)(const struct device *dev);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef animation_render_frame
|
||||||
|
* @brief Callback API for generating the next animation frame.
|
||||||
|
*
|
||||||
|
* @see animation_render_frame() for argument descriptions.
|
||||||
|
*/
|
||||||
|
typedef void (*animation_api_render_frame)(const struct device *dev, struct animation_pixel *pixels,
|
||||||
|
size_t num_pixels);
|
||||||
|
|
||||||
|
struct animation_api {
|
||||||
|
animation_api_start on_start;
|
||||||
|
animation_api_stop on_stop;
|
||||||
|
animation_api_render_frame render_frame;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void animation_start(const struct device *dev) {
|
||||||
|
const struct animation_api *api = (const struct animation_api *)dev->api;
|
||||||
|
|
||||||
|
return api->on_start(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void animation_stop(const struct device *dev) {
|
||||||
|
const struct animation_api *api = (const struct animation_api *)dev->api;
|
||||||
|
|
||||||
|
return api->on_stop(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void animation_render_frame(const struct device *dev, struct animation_pixel *pixels,
|
||||||
|
size_t num_pixels) {
|
||||||
|
const struct animation_api *api = (const struct animation_api *)dev->api;
|
||||||
|
|
||||||
|
return api->render_frame(dev, pixels, num_pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
56
app/include/dt-bindings/zmk/animation.h
Normal file
56
app/include/dt-bindings/zmk/animation.h
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps HSL color settings into a single uint32_t value
|
||||||
|
* that can be cast to zmk_color_hsl.
|
||||||
|
*/
|
||||||
|
#ifdef CONFIG_BIG_ENDIAN
|
||||||
|
#define HSL(h, s, l) ((h << 16) + (s << 8) + l)
|
||||||
|
#else
|
||||||
|
#define HSL(h, s, l) (h + (s << 16) + (l << 24))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animation blending modes
|
||||||
|
*/
|
||||||
|
#define BLENDING_MODE_NORMAL 0
|
||||||
|
#define BLENDING_MODE_MULTIPLY 1
|
||||||
|
#define BLENDING_MODE_LIGHTEN 2
|
||||||
|
#define BLENDING_MODE_DARKEN 3
|
||||||
|
#define BLENDING_MODE_SCREEN 4
|
||||||
|
#define BLENDING_MODE_SUBTRACT 5
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animation control commands
|
||||||
|
*/
|
||||||
|
#define ANIMATION_CMD_TOGGLE 0
|
||||||
|
#define ANIMATION_CMD_NEXT 1
|
||||||
|
#define ANIMATION_CMD_PREVIOUS 2
|
||||||
|
#define ANIMATION_CMD_SELECT 3
|
||||||
|
#define ANIMATION_CMD_BRIGHTEN 4
|
||||||
|
#define ANIMATION_CMD_DIM 5
|
||||||
|
#define ANIMATION_CMD_NEXT_CONTROL_ZONE 6
|
||||||
|
#define ANIMATION_CMD_PREVIOUS_CONTROL_ZONE 7
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic animation command macro
|
||||||
|
*/
|
||||||
|
#define ANIMATION_CONTROL_CMD(command, zone, param) ((zone << 24) | (command << 16) | (param))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animation behavior helpers
|
||||||
|
*/
|
||||||
|
#define ANIMATION_TOGGLE(zone) ANIMATION_CONTROL_CMD(ANIMATION_CMD_TOGGLE, zone, 0)
|
||||||
|
#define ANIMATION_NEXT(zone) ANIMATION_CONTROL_CMD(ANIMATION_CMD_NEXT, zone, 0)
|
||||||
|
#define ANIMATION_PREVIOUS(zone) ANIMATION_CONTROL_CMD(ANIMATION_CMD_PREVIOUS, zone, 0)
|
||||||
|
#define ANIMATION_SELECT(zone, target_animation) \
|
||||||
|
ANIMATION_CONTROL_CMD(ANIMATION_CMD_SELECT, zone, target_animation)
|
||||||
|
#define ANIMATION_BRIGHTEN(zone) ANIMATION_CONTROL_CMD(ANIMATION_CMD_BRIGHTEN, zone, 0)
|
||||||
|
#define ANIMATION_DIM(zone) ANIMATION_CONTROL_CMD(ANIMATION_CMD_DIM, zone, 0)
|
||||||
|
#define ANIMATION_NEXT_CONTROL_ZONE ANIMATION_CONTROL_CMD(ANIMATION_CMD_NEXT_CONTROL_ZONE, 0, 0)
|
||||||
|
#define ANIMATION_PREVIOUS_CONTROL_ZONE \
|
||||||
|
ANIMATION_CONTROL_CMD(ANIMATION_CMD_PREVIOUS_CONTROL_ZONE, 0, 0)
|
12
app/include/dt-bindings/zmk/animation_compose.h
Normal file
12
app/include/dt-bindings/zmk/animation_compose.h
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define BLENDING_MODE_NORMAL 0
|
||||||
|
#define BLENDING_MODE_MULTIPLY 1
|
||||||
|
#define BLENDING_MODE_LIGHTEN 2
|
||||||
|
#define BLENDING_MODE_DARKEN 3
|
||||||
|
#define BLENDING_MODE_SCREEN 4
|
||||||
|
#define BLENDING_MODE_SUBTRACT 5
|
94
app/include/zmk/animation.h
Normal file
94
app/include/zmk/animation.h
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/led_strip.h>
|
||||||
|
|
||||||
|
#define ZMK_ANIMATION_BLENDING_MODE_NORMAL 0
|
||||||
|
#define ZMK_ANIMATION_BLENDING_MODE_MULTIPLY 1
|
||||||
|
#define ZMK_ANIMATION_BLENDING_MODE_LIGHTEN 2
|
||||||
|
#define ZMK_ANIMATION_BLENDING_MODE_DARKEN 3
|
||||||
|
#define ZMK_ANIMATION_BLENDING_MODE_SCREEN 4
|
||||||
|
#define ZMK_ANIMATION_BLENDING_MODE_SUBTRACT 5
|
||||||
|
|
||||||
|
struct zmk_color_rgb {
|
||||||
|
float r;
|
||||||
|
float g;
|
||||||
|
float b;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct zmk_color_hsl {
|
||||||
|
uint16_t h;
|
||||||
|
uint8_t s;
|
||||||
|
uint8_t l;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if DT_NODE_HAS_PROP(DT_INST(0, animation), key_position)
|
||||||
|
size_t zmk_animation_get_pixel_by_key_position(size_t key_position);
|
||||||
|
#else
|
||||||
|
static inline size_t zmk_animation_get_pixel_by_key_position(size_t key_position) {
|
||||||
|
return key_position;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE) && (CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE == 1)
|
||||||
|
uint8_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts color from HSL to RGB.
|
||||||
|
*
|
||||||
|
* @param hsl Color to convert
|
||||||
|
* @param rgb Converted color
|
||||||
|
*/
|
||||||
|
void zmk_hsl_to_rgb(const struct zmk_color_hsl *hsl, struct zmk_color_rgb *rgb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the internal RGB representation into a led_rgb struct
|
||||||
|
* for use with led_strip drivers.
|
||||||
|
*
|
||||||
|
* @param rgb Color to convert
|
||||||
|
* @param led Converted color
|
||||||
|
*/
|
||||||
|
void zmk_rgb_to_led_rgb(const struct zmk_color_rgb *rgb, struct led_rgb *led);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if two HSL colors are the same.
|
||||||
|
*
|
||||||
|
* @param a HSL color to compare
|
||||||
|
* @param b HSL color to compare
|
||||||
|
* @return True when colors share the same values
|
||||||
|
*/
|
||||||
|
bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform linear interpolation between HSL values of two colors
|
||||||
|
* at a given distance (step) and store the resulting value in the given pointer.
|
||||||
|
*
|
||||||
|
* @param from HSL color to interpolate
|
||||||
|
* @param to HSL color to interpolate
|
||||||
|
* @param result Resulting HSL color
|
||||||
|
* @param step Interpolation step
|
||||||
|
*/
|
||||||
|
void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_color_hsl *to,
|
||||||
|
struct zmk_color_hsl *result, float step);
|
||||||
|
|
||||||
|
void zmk_animation_request_frames(uint32_t frames);
|
||||||
|
|
||||||
|
struct zmk_color_rgb __zmk_apply_blending_mode(struct zmk_color_rgb base_value,
|
||||||
|
struct zmk_color_rgb blend_value, uint8_t mode);
|
||||||
|
|
||||||
|
static inline struct zmk_color_rgb zmk_apply_blending_mode(struct zmk_color_rgb base_value,
|
||||||
|
struct zmk_color_rgb blend_value,
|
||||||
|
uint8_t mode) {
|
||||||
|
if (mode == ZMK_ANIMATION_BLENDING_MODE_NORMAL) {
|
||||||
|
return blend_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return __zmk_apply_blending_mode(base_value, blend_value, mode);
|
||||||
|
}
|
24
app/include/zmk/animation/animation_control.h
Normal file
24
app/include/zmk/animation/animation_control.h
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animation control commands
|
||||||
|
*/
|
||||||
|
#define ANIMATION_CMD_TOGGLE 0
|
||||||
|
#define ANIMATION_CMD_NEXT 1
|
||||||
|
#define ANIMATION_CMD_PREVIOUS 2
|
||||||
|
#define ANIMATION_CMD_SELECT 3
|
||||||
|
#define ANIMATION_CMD_BRIGHTEN 4
|
||||||
|
#define ANIMATION_CMD_DIM 5
|
||||||
|
#define ANIMATION_CMD_NEXT_CONTROL_ZONE 6
|
||||||
|
#define ANIMATION_CMD_PREVIOUS_CONTROL_ZONE 7
|
||||||
|
|
||||||
|
int animation_control_handle_command(const struct device *dev, uint8_t command, uint8_t param);
|
200
app/src/animation/animation.c
Normal file
200
app/src/animation/animation.c
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT zmk_animation
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/led_strip.h>
|
||||||
|
#include <zephyr/init.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/activity_state_changed.h>
|
||||||
|
|
||||||
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
|
#define PHANDLE_TO_DEVICE(node_id, prop, idx) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)),
|
||||||
|
|
||||||
|
#define PHANDLE_TO_PIXEL(node_id, prop, idx) \
|
||||||
|
{ \
|
||||||
|
.position_x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \
|
||||||
|
.position_y = DT_PHA_BY_IDX(node_id, prop, idx, position_y), \
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LED Driver device pointers.
|
||||||
|
*/
|
||||||
|
static const struct device *drivers[] = {DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_DEVICE)};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of the LED driver device pointers array.
|
||||||
|
*/
|
||||||
|
static const size_t drivers_size = DT_INST_PROP_LEN(0, drivers);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array containing the number of LEDs handled by each device.
|
||||||
|
*/
|
||||||
|
static const size_t pixels_per_driver[] = DT_INST_PROP(0, chain_lengths);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pointer to the root animation
|
||||||
|
*/
|
||||||
|
static const struct device *animation_root = DEVICE_DT_GET(DT_CHOSEN(zmk_animation));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pixel configuration.
|
||||||
|
*/
|
||||||
|
static struct animation_pixel pixels[] = {DT_INST_FOREACH_PROP_ELEM(0, pixels, PHANDLE_TO_PIXEL)};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of the pixels array.
|
||||||
|
*/
|
||||||
|
static const size_t pixels_size = DT_INST_PROP_LEN(0, pixels);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffer for RGB values ready to be sent to the drivers.
|
||||||
|
*/
|
||||||
|
static struct led_rgb px_buffer[DT_INST_PROP_LEN(0, pixels)];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counter for animation frames that have been requested but have yet to be executed.
|
||||||
|
*/
|
||||||
|
static uint32_t animation_timer_countdown = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditional implementation of zmk_animation_get_pixel_by_key_position
|
||||||
|
* if key-pixels is set.
|
||||||
|
*/
|
||||||
|
#if DT_INST_NODE_HAS_PROP(0, key_position)
|
||||||
|
static const uint8_t pixels_by_key_position[] = DT_INST_PROP(0, key_pixels);
|
||||||
|
|
||||||
|
size_t zmk_animation_get_pixel_by_key_position(size_t key_position) {
|
||||||
|
return pixels_by_key_position[key_position];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE) && (CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE == 1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup table for distance between any two pixels.
|
||||||
|
*
|
||||||
|
* The values are stored as a triangular matrix which cuts the space requirement roughly in half.
|
||||||
|
*/
|
||||||
|
static uint8_t
|
||||||
|
pixel_distance[((DT_INST_PROP_LEN(0, pixels) + 1) * DT_INST_PROP_LEN(0, pixels)) / 2];
|
||||||
|
|
||||||
|
uint8_t zmk_animation_get_pixel_distance(size_t pixel_idx, size_t other_pixel_idx) {
|
||||||
|
if (pixel_idx < other_pixel_idx) {
|
||||||
|
return zmk_animation_get_pixel_distance(other_pixel_idx, pixel_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixel_distance[(((pixel_idx + 1) * pixel_idx) >> 1) + other_pixel_idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void zmk_animation_tick(struct k_work *work) {
|
||||||
|
LOG_DBG("Animation tick");
|
||||||
|
animation_render_frame(animation_root, &pixels[0], pixels_size);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < pixels_size; ++i) {
|
||||||
|
zmk_rgb_to_led_rgb(&pixels[i].value, &px_buffer[i]);
|
||||||
|
|
||||||
|
// Reset values for the next cycle
|
||||||
|
pixels[i].value.r = 0;
|
||||||
|
pixels[i].value.g = 0;
|
||||||
|
pixels[i].value.b = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pixels_updated = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < drivers_size; ++i) {
|
||||||
|
led_strip_update_rgb(drivers[i], &px_buffer[pixels_updated], pixels_per_driver[i]);
|
||||||
|
|
||||||
|
pixels_updated += pixels_per_driver[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
K_WORK_DEFINE(animation_work, zmk_animation_tick);
|
||||||
|
|
||||||
|
static void zmk_animation_tick_handler(struct k_timer *timer) {
|
||||||
|
if (--animation_timer_countdown == 0) {
|
||||||
|
k_timer_stop(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
k_work_submit(&animation_work);
|
||||||
|
}
|
||||||
|
|
||||||
|
K_TIMER_DEFINE(animation_tick, zmk_animation_tick_handler, NULL);
|
||||||
|
|
||||||
|
void zmk_animation_request_frames(uint32_t frames) {
|
||||||
|
if (frames <= animation_timer_countdown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animation_timer_countdown == 0) {
|
||||||
|
k_timer_start(&animation_tick, K_MSEC(1000 / CONFIG_ZMK_ANIMATION_FPS),
|
||||||
|
K_MSEC(1000 / CONFIG_ZMK_ANIMATION_FPS));
|
||||||
|
}
|
||||||
|
|
||||||
|
animation_timer_countdown = frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int zmk_animation_on_activity_state_changed(const zmk_event_t *event) {
|
||||||
|
const struct zmk_activity_state_changed *activity_state_event;
|
||||||
|
|
||||||
|
if ((activity_state_event = as_zmk_activity_state_changed(event)) == NULL) {
|
||||||
|
// Event not supported.
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (activity_state_event->state) {
|
||||||
|
case ZMK_ACTIVITY_ACTIVE:
|
||||||
|
animation_start(animation_root);
|
||||||
|
return 0;
|
||||||
|
case ZMK_ACTIVITY_SLEEP:
|
||||||
|
animation_stop(animation_root);
|
||||||
|
k_timer_stop(&animation_tick);
|
||||||
|
animation_timer_countdown = 0;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int zmk_animation_init(const struct device *dev) {
|
||||||
|
#if defined(CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE) && (CONFIG_ZMK_ANIMATION_PIXEL_DISTANCE == 1)
|
||||||
|
// Prefill the pixel distance lookup table
|
||||||
|
int k = 0;
|
||||||
|
for (size_t i = 0; i < pixels_size; ++i) {
|
||||||
|
for (size_t j = 0; j <= i; ++j) {
|
||||||
|
// Distances are normalized to fit inside 0-255 range to fit inside uint8_t
|
||||||
|
// for better space efficiency
|
||||||
|
pixel_distance[k++] = sqrt(pow(pixels[i].position_x - pixels[j].position_x, 2) +
|
||||||
|
pow(pixels[i].position_y - pixels[j].position_y, 2)) *
|
||||||
|
255 / 360;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOG_INF("ZMK Animation Ready");
|
||||||
|
|
||||||
|
animation_start(animation_root);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SYS_INIT(zmk_animation_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
|
||||||
|
|
||||||
|
ZMK_LISTENER(amk_animation, zmk_animation_on_activity_state_changed);
|
||||||
|
ZMK_SUBSCRIPTION(amk_animation, zmk_activity_state_changed);
|
73
app/src/animation/animation_compose.c
Normal file
73
app/src/animation/animation_compose.c
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT zmk_animation_compose
|
||||||
|
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
|
||||||
|
#include <drivers/animation.h>
|
||||||
|
|
||||||
|
#include <zmk/animation.h>
|
||||||
|
|
||||||
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
|
#define PHANDLE_TO_DEVICE(node_id, prop, idx) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)),
|
||||||
|
|
||||||
|
struct animation_compose_config {
|
||||||
|
const struct device **animations;
|
||||||
|
const size_t animations_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void animation_compose_render_frame(const struct device *dev, struct animation_pixel *pixels,
|
||||||
|
size_t num_pixels) {
|
||||||
|
const struct animation_compose_config *config = dev->config;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < config->animations_size; ++i) {
|
||||||
|
animation_render_frame(config->animations[i], pixels, num_pixels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void animation_compose_start(const struct device *dev) {
|
||||||
|
const struct animation_compose_config *config = dev->config;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < config->animations_size; ++i) {
|
||||||
|
animation_start(config->animations[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void animation_compose_stop(const struct device *dev) {
|
||||||
|
const struct animation_compose_config *config = dev->config;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < config->animations_size; ++i) {
|
||||||
|
animation_stop(config->animations[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int animation_compose_init(const struct device *dev) { return 0; }
|
||||||
|
|
||||||
|
static const struct animation_api animation_compose_api = {
|
||||||
|
.on_start = animation_compose_start,
|
||||||
|
.on_stop = animation_compose_stop,
|
||||||
|
.render_frame = animation_compose_render_frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ANIMATION_COMPOSE_DEVICE(idx) \
|
||||||
|
\
|
||||||
|
static const struct device *animation_compose_##idx##_animations[] = { \
|
||||||
|
DT_INST_FOREACH_PROP_ELEM(idx, animations, PHANDLE_TO_DEVICE)}; \
|
||||||
|
\
|
||||||
|
static struct animation_compose_config animation_compose_##idx##_config = { \
|
||||||
|
.animations = animation_compose_##idx##_animations, \
|
||||||
|
.animations_size = DT_INST_PROP_LEN(idx, animations), \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
DEVICE_DT_INST_DEFINE(idx, &animation_compose_init, NULL, NULL, \
|
||||||
|
&animation_compose_##idx##_config, POST_KERNEL, \
|
||||||
|
CONFIG_APPLICATION_INIT_PRIORITY, &animation_compose_api);
|
||||||
|
|
||||||
|
DT_INST_FOREACH_STATUS_OKAY(ANIMATION_COMPOSE_DEVICE);
|
264
app/src/animation/animation_control.c
Normal file
264
app/src/animation/animation_control.c
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT zmk_animation_control
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <zephyr/settings/settings.h>
|
||||||
|
|
||||||
|
#include <drivers/animation.h>
|
||||||
|
|
||||||
|
#include <zmk/animation.h>
|
||||||
|
#include <zmk/animation/animation_control.h>
|
||||||
|
|
||||||
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
|
#define PHANDLE_TO_DEVICE(node_id, prop, idx) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)),
|
||||||
|
|
||||||
|
struct animation_control_work_context {
|
||||||
|
const struct device *animation;
|
||||||
|
struct k_work_delayable save_work;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct animation_control_config {
|
||||||
|
const struct device **animations;
|
||||||
|
const size_t animations_size;
|
||||||
|
const uint8_t brightness_steps;
|
||||||
|
struct animation_control_work_context *work;
|
||||||
|
struct settings_handler *settings_handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct animation_control_data {
|
||||||
|
bool active;
|
||||||
|
uint8_t brightness;
|
||||||
|
size_t current_animation;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int animation_control_load_settings(const struct device *dev, const char *name, size_t len,
|
||||||
|
settings_read_cb read_cb, void *cb_arg) {
|
||||||
|
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||||
|
const char *next;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (settings_name_steq(name, "state", &next) && !next) {
|
||||||
|
if (len != sizeof(struct animation_control_data)) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = read_cb(cb_arg, dev->data, sizeof(struct animation_control_data));
|
||||||
|
if (rc >= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ENOENT;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif /* IS_ENABLED(CONFIG_SETTINGS) */
|
||||||
|
}
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||||
|
static void animation_control_save_work(struct k_work *work) {
|
||||||
|
struct animation_control_work_context *ctx =
|
||||||
|
CONTAINER_OF(work, struct animation_control_work_context, save_work);
|
||||||
|
const struct device *dev = ctx->animation;
|
||||||
|
|
||||||
|
char path[40];
|
||||||
|
snprintf(path, 40, "%s/state", dev->name);
|
||||||
|
|
||||||
|
settings_save_one(path, dev->data, sizeof(struct animation_control_data));
|
||||||
|
};
|
||||||
|
|
||||||
|
static int animation_control_save_settings(const struct device *dev) {
|
||||||
|
const struct animation_control_config *config = dev->config;
|
||||||
|
struct animation_control_work_context *ctx = config->work;
|
||||||
|
|
||||||
|
k_work_cancel_delayable(&ctx->save_work);
|
||||||
|
|
||||||
|
return k_work_reschedule(&ctx->save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE));
|
||||||
|
}
|
||||||
|
#endif /* IS_ENABLED(CONFIG_SETTINGS) */
|
||||||
|
|
||||||
|
int animation_control_handle_command(const struct device *dev, uint8_t command, uint8_t param) {
|
||||||
|
const struct animation_control_config *config = dev->config;
|
||||||
|
struct animation_control_data *data = dev->data;
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case ANIMATION_CMD_TOGGLE:
|
||||||
|
data->active = !data->active;
|
||||||
|
|
||||||
|
if (data->active) {
|
||||||
|
animation_start(config->animations[data->current_animation]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation_stop(config->animations[data->current_animation]);
|
||||||
|
break;
|
||||||
|
case ANIMATION_CMD_NEXT:
|
||||||
|
data->current_animation++;
|
||||||
|
|
||||||
|
if (data->current_animation == config->animations_size) {
|
||||||
|
data->current_animation = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ANIMATION_CMD_PREVIOUS:
|
||||||
|
if (data->current_animation == 0) {
|
||||||
|
data->current_animation = config->animations_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->current_animation--;
|
||||||
|
break;
|
||||||
|
case ANIMATION_CMD_SELECT:
|
||||||
|
if (config->animations_size < param) {
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->current_animation = param;
|
||||||
|
break;
|
||||||
|
case ANIMATION_CMD_DIM:
|
||||||
|
if (data->brightness == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->brightness--;
|
||||||
|
|
||||||
|
if (data->brightness == 0) {
|
||||||
|
animation_stop(config->animations[data->current_animation]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ANIMATION_CMD_BRIGHTEN:
|
||||||
|
if (data->brightness == config->brightness_steps) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->brightness == 0) {
|
||||||
|
animation_start(config->animations[data->current_animation]);
|
||||||
|
}
|
||||||
|
|
||||||
|
data->brightness++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||||
|
animation_control_save_settings(dev);
|
||||||
|
#endif /* IS_ENABLED(CONFIG_SETTINGS) */
|
||||||
|
|
||||||
|
// Force refresh
|
||||||
|
zmk_animation_request_frames(1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void animation_control_render_frame(const struct device *dev, struct animation_pixel *pixels,
|
||||||
|
size_t num_pixels) {
|
||||||
|
const struct animation_control_config *config = dev->config;
|
||||||
|
const struct animation_control_data *data = dev->data;
|
||||||
|
|
||||||
|
if (!data->active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation_render_frame(config->animations[data->current_animation], pixels, num_pixels);
|
||||||
|
|
||||||
|
if (data->brightness == config->brightness_steps) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float brightness = (float)data->brightness / (float)config->brightness_steps;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_pixels; ++i) {
|
||||||
|
pixels[i].value.r *= brightness;
|
||||||
|
pixels[i].value.g *= brightness;
|
||||||
|
pixels[i].value.b *= brightness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void animation_control_start(const struct device *dev) {
|
||||||
|
const struct animation_control_config *config = dev->config;
|
||||||
|
const struct animation_control_data *data = dev->data;
|
||||||
|
|
||||||
|
if (!data->active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation_start(config->animations[data->current_animation]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void animation_control_stop(const struct device *dev) {
|
||||||
|
const struct animation_control_config *config = dev->config;
|
||||||
|
const struct animation_control_data *data = dev->data;
|
||||||
|
|
||||||
|
animation_stop(config->animations[data->current_animation]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int animation_control_init(const struct device *dev) {
|
||||||
|
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||||
|
const struct animation_control_config *config = dev->config;
|
||||||
|
|
||||||
|
settings_subsys_init();
|
||||||
|
|
||||||
|
settings_register(config->settings_handler);
|
||||||
|
|
||||||
|
k_work_init_delayable(&config->work->save_work, animation_control_save_work);
|
||||||
|
|
||||||
|
settings_load_subtree(dev->name);
|
||||||
|
#endif /* IS_ENABLED(CONFIG_SETTINGS) */
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct animation_api animation_control_api = {
|
||||||
|
.on_start = animation_control_start,
|
||||||
|
.on_stop = animation_control_stop,
|
||||||
|
.render_frame = animation_control_render_frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ANIMATION_CONTROL_DEVICE(idx) \
|
||||||
|
\
|
||||||
|
static const struct device *animation_control_##idx##_animations[] = { \
|
||||||
|
DT_INST_FOREACH_PROP_ELEM(idx, animations, PHANDLE_TO_DEVICE)}; \
|
||||||
|
\
|
||||||
|
static struct animation_control_work_context animation_control_##idx##_work = { \
|
||||||
|
.animation = DEVICE_DT_GET(DT_DRV_INST(idx)), \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
static int animation_control_##idx##_load_settings(const char *name, size_t len, \
|
||||||
|
settings_read_cb read_cb, void *cb_arg) { \
|
||||||
|
const struct device *dev = DEVICE_DT_GET(DT_DRV_INST(idx)); \
|
||||||
|
\
|
||||||
|
return animation_control_load_settings(dev, name, len, read_cb, cb_arg); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
static struct settings_handler animation_control_##idx##_settings_handler = { \
|
||||||
|
.name = DT_INST_PROP(idx, label), \
|
||||||
|
.h_set = animation_control_##idx##_load_settings, \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
static const struct animation_control_config animation_control_##idx##_config = { \
|
||||||
|
.animations = animation_control_##idx##_animations, \
|
||||||
|
.animations_size = DT_INST_PROP_LEN(idx, animations), \
|
||||||
|
.brightness_steps = DT_INST_PROP(idx, brightness_steps) - 1, \
|
||||||
|
.work = &animation_control_##idx##_work, \
|
||||||
|
.settings_handler = &animation_control_##idx##_settings_handler, \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
static struct animation_control_data animation_control_##idx##_data = { \
|
||||||
|
.active = true, \
|
||||||
|
.brightness = DT_INST_PROP(idx, brightness_steps) - 1, \
|
||||||
|
.current_animation = 0, \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
DEVICE_DT_INST_DEFINE(idx, &animation_control_init, NULL, &animation_control_##idx##_data, \
|
||||||
|
&animation_control_##idx##_config, POST_KERNEL, \
|
||||||
|
CONFIG_APPLICATION_INIT_PRIORITY, &animation_control_api);
|
||||||
|
|
||||||
|
DT_INST_FOREACH_STATUS_OKAY(ANIMATION_CONTROL_DEVICE);
|
209
app/src/animation/animation_ripple.c
Normal file
209
app/src/animation/animation_ripple.c
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
121
app/src/animation/animation_solid.c
Normal file
121
app/src/animation/animation_solid.c
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT zmk_animation_solid
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
|
||||||
|
#include <drivers/animation.h>
|
||||||
|
|
||||||
|
#include <zmk/animation.h>
|
||||||
|
|
||||||
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
|
struct animation_solid_config {
|
||||||
|
size_t *pixel_map;
|
||||||
|
size_t pixel_map_size;
|
||||||
|
struct zmk_color_hsl *colors;
|
||||||
|
uint8_t num_colors;
|
||||||
|
uint16_t duration;
|
||||||
|
uint16_t transition_duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct animation_solid_data {
|
||||||
|
uint16_t counter;
|
||||||
|
|
||||||
|
struct zmk_color_hsl current_hsl;
|
||||||
|
struct zmk_color_rgb current_rgb;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void animation_solid_update_color(const struct device *dev) {
|
||||||
|
const struct animation_solid_config *config = dev->config;
|
||||||
|
struct animation_solid_data *data = dev->data;
|
||||||
|
|
||||||
|
const size_t from = data->counter / config->transition_duration;
|
||||||
|
const size_t to = (from + 1) % config->num_colors;
|
||||||
|
|
||||||
|
struct zmk_color_hsl next_hsl;
|
||||||
|
|
||||||
|
zmk_interpolate_hsl(&config->colors[from], &config->colors[to], &next_hsl,
|
||||||
|
(data->counter % config->transition_duration) /
|
||||||
|
(float)config->transition_duration);
|
||||||
|
|
||||||
|
data->current_hsl = next_hsl;
|
||||||
|
zmk_hsl_to_rgb(&data->current_hsl, &data->current_rgb);
|
||||||
|
|
||||||
|
data->counter = (data->counter + 1) % config->duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void animation_solid_render_frame(const struct device *dev, struct animation_pixel *pixels,
|
||||||
|
size_t num_pixels) {
|
||||||
|
const struct animation_solid_config *config = dev->config;
|
||||||
|
struct animation_solid_data *data = dev->data;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < config->pixel_map_size; ++i) {
|
||||||
|
pixels[config->pixel_map[i]].value = data->current_rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config->num_colors == 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request frames on counter reset
|
||||||
|
if (data->counter == 0) {
|
||||||
|
zmk_animation_request_frames(config->duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
animation_solid_update_color(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void animation_solid_start(const struct device *dev) { zmk_animation_request_frames(1); }
|
||||||
|
|
||||||
|
static void animation_solid_stop(const struct device *dev) {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
static int animation_solid_init(const struct device *dev) {
|
||||||
|
const struct animation_solid_config *config = dev->config;
|
||||||
|
struct animation_solid_data *data = dev->data;
|
||||||
|
|
||||||
|
data->counter = 0;
|
||||||
|
data->current_hsl = config->colors[0];
|
||||||
|
|
||||||
|
zmk_hsl_to_rgb(&data->current_hsl, &data->current_rgb);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct animation_api animation_solid_api = {
|
||||||
|
.on_start = animation_solid_start,
|
||||||
|
.on_stop = animation_solid_stop,
|
||||||
|
.render_frame = animation_solid_render_frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ANIMATION_SOLID_DEVICE(idx) \
|
||||||
|
\
|
||||||
|
static struct animation_solid_data animation_solid_##idx##_data; \
|
||||||
|
\
|
||||||
|
static size_t animation_ripple_##idx##_pixel_map[] = DT_INST_PROP(idx, pixels); \
|
||||||
|
\
|
||||||
|
static uint32_t animation_solid_##idx##_colors[] = DT_INST_PROP(idx, colors); \
|
||||||
|
\
|
||||||
|
static struct animation_solid_config animation_solid_##idx##_config = { \
|
||||||
|
.pixel_map = &animation_ripple_##idx##_pixel_map[0], \
|
||||||
|
.pixel_map_size = DT_INST_PROP_LEN(idx, pixels), \
|
||||||
|
.colors = (struct zmk_color_hsl *)animation_solid_##idx##_colors, \
|
||||||
|
.num_colors = DT_INST_PROP_LEN(idx, colors), \
|
||||||
|
.duration = DT_INST_PROP(idx, duration) * CONFIG_ZMK_ANIMATION_FPS, \
|
||||||
|
.transition_duration = (DT_INST_PROP(idx, duration) * CONFIG_ZMK_ANIMATION_FPS) / \
|
||||||
|
DT_INST_PROP_LEN(idx, colors), \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
DEVICE_DT_INST_DEFINE(idx, &animation_solid_init, NULL, &animation_solid_##idx##_data, \
|
||||||
|
&animation_solid_##idx##_config, POST_KERNEL, \
|
||||||
|
CONFIG_APPLICATION_INIT_PRIORITY, &animation_solid_api);
|
||||||
|
|
||||||
|
DT_INST_FOREACH_STATUS_OKAY(ANIMATION_SOLID_DEVICE);
|
138
app/src/animation/color.c
Normal file
138
app/src/animation/color.c
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <zmk/animation.h>
|
||||||
|
|
||||||
|
static float fmod(float a, float b) {
|
||||||
|
float mod = a < 0 ? -a : a;
|
||||||
|
float x = b < 0 ? -b : b;
|
||||||
|
|
||||||
|
while (mod >= x) {
|
||||||
|
mod = mod - x;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a < 0 ? -mod : mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float fabs(float a) { return a < 0 ? -a : a; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HSL chosen over HSV/HSB as it shares the same parameters with LCh or HSLuv.
|
||||||
|
* The latter color spaces could be interesting to experiment with because of their
|
||||||
|
* perceptual uniformity, but it would come at the cost of some performance.
|
||||||
|
* Using the same parameters would make it easy to toggle any such behavior
|
||||||
|
* using a single config flag.
|
||||||
|
*
|
||||||
|
* Algorithm source: https://www.tlbx.app/color-converter
|
||||||
|
*/
|
||||||
|
void zmk_hsl_to_rgb(const struct zmk_color_hsl *hsl, struct zmk_color_rgb *rgb) {
|
||||||
|
float s = (float)hsl->s / 100;
|
||||||
|
float l = (float)hsl->l / 100;
|
||||||
|
|
||||||
|
float a = (float)hsl->h / 60;
|
||||||
|
float chroma = s * (1 - fabs(2 * l - 1));
|
||||||
|
float x = chroma * (1 - fabs(fmod(a, 2) - 1));
|
||||||
|
float m = l - chroma / 2;
|
||||||
|
|
||||||
|
switch ((uint8_t)a % 6) {
|
||||||
|
case 0:
|
||||||
|
rgb->r = m + chroma;
|
||||||
|
rgb->g = m + x;
|
||||||
|
rgb->b = m;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
rgb->r = m + x;
|
||||||
|
rgb->g = m + chroma;
|
||||||
|
rgb->b = m;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
rgb->r = m;
|
||||||
|
rgb->g = m + chroma;
|
||||||
|
rgb->b = m + x;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
rgb->r = m;
|
||||||
|
rgb->g = m + x;
|
||||||
|
rgb->b = m + chroma;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
rgb->r = m + x;
|
||||||
|
rgb->g = m;
|
||||||
|
rgb->b = m + chroma;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
rgb->r = m + chroma;
|
||||||
|
rgb->g = m;
|
||||||
|
rgb->b = m + x;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts ZMKs RGB (float) to Zephyr's led_rgb (uint8_t) format.
|
||||||
|
*/
|
||||||
|
void zmk_rgb_to_led_rgb(const struct zmk_color_rgb *rgb, struct led_rgb *led) {
|
||||||
|
led->r = rgb->r * 255;
|
||||||
|
led->g = rgb->g * 255;
|
||||||
|
led->b = rgb->b * 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two HSL colors.
|
||||||
|
*/
|
||||||
|
bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b) {
|
||||||
|
return a->h == b->h && a->s == b->s && a->l == b->l;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpolate between two colors using the cylindrical model (HSL).
|
||||||
|
*/
|
||||||
|
void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_color_hsl *to,
|
||||||
|
struct zmk_color_hsl *result, float step) {
|
||||||
|
int16_t hue_delta;
|
||||||
|
|
||||||
|
hue_delta = from->h - to->h;
|
||||||
|
hue_delta = hue_delta + (180 < abs(hue_delta) ? (hue_delta < 0 ? 360 : -360) : 0);
|
||||||
|
|
||||||
|
result->h = (uint16_t)(360 + from->h - (hue_delta * step)) % 360;
|
||||||
|
result->s = from->s - (from->s - to->s) * step;
|
||||||
|
result->l = from->l - (from->l - to->l) * step;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct zmk_color_rgb __zmk_apply_blending_mode(struct zmk_color_rgb base_value,
|
||||||
|
struct zmk_color_rgb blend_value, uint8_t mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case ZMK_ANIMATION_BLENDING_MODE_MULTIPLY:
|
||||||
|
base_value.r = base_value.r * blend_value.r;
|
||||||
|
base_value.g = base_value.g * blend_value.g;
|
||||||
|
base_value.b = base_value.b * blend_value.b;
|
||||||
|
break;
|
||||||
|
case ZMK_ANIMATION_BLENDING_MODE_LIGHTEN:
|
||||||
|
base_value.r = base_value.r > blend_value.r ? base_value.r : blend_value.r;
|
||||||
|
base_value.g = base_value.g > blend_value.g ? base_value.g : blend_value.g;
|
||||||
|
base_value.b = base_value.b > blend_value.b ? base_value.b : blend_value.b;
|
||||||
|
break;
|
||||||
|
case ZMK_ANIMATION_BLENDING_MODE_DARKEN:
|
||||||
|
base_value.r = base_value.r > blend_value.r ? blend_value.r : base_value.r;
|
||||||
|
base_value.g = base_value.g > blend_value.g ? blend_value.g : base_value.g;
|
||||||
|
base_value.b = base_value.b > blend_value.b ? blend_value.b : base_value.b;
|
||||||
|
break;
|
||||||
|
case ZMK_ANIMATION_BLENDING_MODE_SCREEN:
|
||||||
|
base_value.r = base_value.r + (1.0f - base_value.r) * blend_value.r;
|
||||||
|
base_value.g = base_value.g + (1.0f - base_value.g) * blend_value.g;
|
||||||
|
base_value.b = base_value.b + (1.0f - base_value.b) * blend_value.b;
|
||||||
|
break;
|
||||||
|
case ZMK_ANIMATION_BLENDING_MODE_SUBTRACT:
|
||||||
|
base_value.r = base_value.r - base_value.r * blend_value.r;
|
||||||
|
base_value.g = base_value.g - base_value.g * blend_value.g;
|
||||||
|
base_value.b = base_value.b - base_value.b * blend_value.b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base_value;
|
||||||
|
}
|
94
app/src/behaviors/behavior_animation.c
Normal file
94
app/src/behaviors/behavior_animation.c
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
|
||||||
|
#include <drivers/behavior.h>
|
||||||
|
|
||||||
|
#include <zmk/animation/animation_control.h>
|
||||||
|
|
||||||
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT zmk_animation_control
|
||||||
|
|
||||||
|
#define DEVICE_ADDR(idx) DEVICE_DT_GET(DT_INST(idx, zmk_animation_control)),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control animation instance pointers.
|
||||||
|
*/
|
||||||
|
static const struct device *control_animations[] = {DT_INST_FOREACH_STATUS_OKAY(DEVICE_ADDR)};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of control animation instance pointers array.
|
||||||
|
*/
|
||||||
|
static const uint8_t control_animations_size = sizeof(control_animations);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index of the current default control animation instance.
|
||||||
|
*/
|
||||||
|
static uint8_t current_zone = 0;
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT zmk_behavior_animation
|
||||||
|
|
||||||
|
static int
|
||||||
|
on_keymap_binding_convert_central_state_dependent_params(struct zmk_behavior_binding *binding,
|
||||||
|
struct zmk_behavior_binding_event event) {
|
||||||
|
if ((binding->param1 >> 24) == 0xff) {
|
||||||
|
binding->param1 = (current_zone << 24) | (binding->param1 & 0x00ffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
|
||||||
|
struct zmk_behavior_binding_event event) {
|
||||||
|
uint8_t value = binding->param1 & 0xff;
|
||||||
|
uint8_t command = (binding->param1 >> 16) & 0xff;
|
||||||
|
uint8_t zone = (binding->param1 >> 24) & 0xff;
|
||||||
|
|
||||||
|
if (command == ANIMATION_CMD_NEXT_CONTROL_ZONE) {
|
||||||
|
current_zone++;
|
||||||
|
|
||||||
|
if (current_zone == control_animations_size) {
|
||||||
|
current_zone = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command == ANIMATION_CMD_PREVIOUS_CONTROL_ZONE) {
|
||||||
|
if (current_zone == 0) {
|
||||||
|
current_zone = control_animations_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_zone--;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (control_animations_size <= zone) {
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return animation_control_handle_command(control_animations[zone], command, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
|
||||||
|
struct zmk_behavior_binding_event event) {
|
||||||
|
return ZMK_BEHAVIOR_OPAQUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int behavior_animation_init(const struct device *dev) { return 0; }
|
||||||
|
|
||||||
|
static const struct behavior_driver_api behavior_animation_driver_api = {
|
||||||
|
.binding_convert_central_state_dependent_params =
|
||||||
|
on_keymap_binding_convert_central_state_dependent_params,
|
||||||
|
.binding_pressed = on_keymap_binding_pressed,
|
||||||
|
.binding_released = on_keymap_binding_released,
|
||||||
|
};
|
||||||
|
|
||||||
|
DEVICE_DT_INST_DEFINE(0, behavior_animation_init, NULL, NULL, NULL, APPLICATION,
|
||||||
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_animation_driver_api);
|
52
docs/docs/behaviors/animations.md
Normal file
52
docs/docs/behaviors/animations.md
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
title: Animation Control Behavior
|
||||||
|
sidebar_label: Animation Control
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This page contains [Animations](../features/animations.md) behaviors supported by ZMK.
|
||||||
|
|
||||||
|
## Animation Control Defines
|
||||||
|
|
||||||
|
RGB actions defines are provided through the `dt-bindings/zmk/animation.h` header,
|
||||||
|
which is added at the top of the keymap file:
|
||||||
|
|
||||||
|
```
|
||||||
|
#include <dt-bindings/zmk/animation.h>
|
||||||
|
```
|
||||||
|
|
||||||
|
This will allow you to reference the actions defined in this header such as
|
||||||
|
`ANIMATION_TOGGLE`.
|
||||||
|
|
||||||
|
Here is a table describing the action for each define:
|
||||||
|
|
||||||
|
| Define | Action |
|
||||||
|
| ------------------------------------ | ------------------------------------------------------------- |
|
||||||
|
| `ANIMATION_TOGGLE` | Toggles active animation |
|
||||||
|
| `ANIMATION_NEXT` | Selects next animation within root animation node array |
|
||||||
|
| `ANIMATION_PREVIOUS` | Selects previous animation within root animation node array |
|
||||||
|
| `ANIMATION_SELECT(x)` | Selects animation within root animation node at index `x` |
|
||||||
|
| `ANIMATION_BRIGHTEN` | Increases brightness (applied after other blending) |
|
||||||
|
| `ANIMATION_DIM` | Decreases brightness (applied after other blending) |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
1. Toggle active animation on/off
|
||||||
|
|
||||||
|
```
|
||||||
|
&animation ANIMATION_TOGGLE(0)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Switch to next animation
|
||||||
|
|
||||||
|
```
|
||||||
|
&animation ANIMATION_NEXT(0)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Select animation at index 3
|
||||||
|
|
||||||
|
```
|
||||||
|
&animation ANIMATION_SELECT(3)
|
||||||
|
```
|
||||||
|
|
226
docs/docs/features/animations.md
Normal file
226
docs/docs/features/animations.md
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
---
|
||||||
|
title: RGB Animations
|
||||||
|
sidebar_label: RGB Animations
|
||||||
|
---
|
||||||
|
|
||||||
|
ZMK supports animation effects on RGB control strips via the animation
|
||||||
|
subsystem.
|
||||||
|
|
||||||
|
## Enabling Animation Support
|
||||||
|
|
||||||
|
Set `CONFIG_ZMK_ANIMATION=y` to enable animation support on your keyboard.
|
||||||
|
See animation configuration for instructions on how to customize animation
|
||||||
|
support.
|
||||||
|
|
||||||
|
If your board does not have animation support configured, see
|
||||||
|
[Adding Animation Support.](#adding-animation-support)
|
||||||
|
|
||||||
|
## Color settings
|
||||||
|
|
||||||
|
All colors are given in [HSL](https://www.w3schools.com/colors/colors_hsl.asp)
|
||||||
|
format. The user should specify colors in this format, although they will
|
||||||
|
be written to the LED chain driver in RGB format.
|
||||||
|
|
||||||
|
## System Design
|
||||||
|
|
||||||
|
This subsystem is made up of 3 key components:
|
||||||
|
- The core animations layer
|
||||||
|
- Animation drivers
|
||||||
|
- Color utilites
|
||||||
|
|
||||||
|
### Core Animation Layer
|
||||||
|
|
||||||
|
The core animation layer is responsible for scheduling animations, kicking
|
||||||
|
off rendering of new frames, and sending frame data to the LED strip via
|
||||||
|
Zephyr's LED Strip API.
|
||||||
|
|
||||||
|
### Animation Drivers
|
||||||
|
|
||||||
|
Each animation effect is implemented as a driver. The animation API exposes
|
||||||
|
the following functions:
|
||||||
|
|
||||||
|
| Function | Purpose |
|
||||||
|
| ------------------------ | ------------------------------------- |
|
||||||
|
| `animation_start` | starts rendering the animation effect |
|
||||||
|
| `animation_stop` | ends rendering of the effect |
|
||||||
|
| `animation_render_frame` | renders a frame of the animation |
|
||||||
|
|
||||||
|
Each driver will also likely utilize `ZMK_LISTENER` to listen for keypress
|
||||||
|
events. A driver can then request the animation subsystem render frames
|
||||||
|
using `zmk_animation_request_frames`.
|
||||||
|
|
||||||
|
|
||||||
|
### Color Utilities
|
||||||
|
|
||||||
|
Color utilities are used by the animation drivers to perform color conversions.
|
||||||
|
Since colors are given in HSL format, `zmk_hsl_to_rgb` is provided to
|
||||||
|
convert user color input from devicetree to RGB data before writing to an
|
||||||
|
animation output pixel.
|
||||||
|
|
||||||
|
## Adding Animation Support
|
||||||
|
|
||||||
|
Adding animation support to your board will require the following devicetree
|
||||||
|
changes:
|
||||||
|
- Animation core node with `zmk,animation` compatible describing
|
||||||
|
"pixels" within animation as well as LED drivers to write output pixels to
|
||||||
|
- root animation node with `zmk,animation-control` compatible to cycle though
|
||||||
|
animations
|
||||||
|
- animation driver nodes for all animations the user would like to implement
|
||||||
|
|
||||||
|
### Animation Core Node
|
||||||
|
|
||||||
|
This node describes all output "pixels" on your keyboard. These pixels
|
||||||
|
correspond to individual LEDs on your LED chain. The animation subsystem
|
||||||
|
will write these pixels to your LED chain driver in the order they are
|
||||||
|
declared.
|
||||||
|
|
||||||
|
Each pixel has an X and Y coordinate, which helps the animation
|
||||||
|
subsystem determine how effects like a ripple should be rendered on your
|
||||||
|
keyboard. X and Y coordinates can range from 0-255.
|
||||||
|
|
||||||
|
The node also needs to have references to your LED strip driver nodes within
|
||||||
|
devicetree. Multiple LED strip nodes can be provided, but each node must have
|
||||||
|
a `chain-length` property equal to the number of pixels in the LED chain.
|
||||||
|
When multiple LED strip nodes are provided, the `chain-length` of each will be
|
||||||
|
used to determine how many pixels to write. Pixels are written sequentially, so
|
||||||
|
the pixels for the second LED driver should be declared after those written
|
||||||
|
to the first one.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
The total number of pixels defined in your `pixel` array property should match
|
||||||
|
the sum of all `chain-length` properties for the LED strip drivers you will be
|
||||||
|
using.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
For example, here is a chain of 6 pixels, using two LED drivers:
|
||||||
|
```
|
||||||
|
animation {
|
||||||
|
compatible = "zmk,animation";
|
||||||
|
drivers = <&led_ctrl1 &led_ctrl2>;
|
||||||
|
pixels = <&pixel 0 0>, /* Pixel 0 is at (0,0) */
|
||||||
|
<&pixel 21 0>, /* Pixel 1 is (0, 21) */
|
||||||
|
<&pixel 42 0>,
|
||||||
|
<&pixel 63 0>,
|
||||||
|
<&pixel 84 0>,
|
||||||
|
<&pixel 105 0>,
|
||||||
|
<&pixel 255 255>; /* Max coordinate (255, 255) */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, if `led_ctrl1` has a `chain-length` of 4 and `led_ctrl2` has
|
||||||
|
a `chain-length` of 2, then the first 4 pixels will be written to `led_ctrl1`,
|
||||||
|
and the final two will be written to `led_ctrl2`.
|
||||||
|
|
||||||
|
|
||||||
|
## Animation Root Node
|
||||||
|
|
||||||
|
Your animation root node should be an instance of the `zmk,animation-control`
|
||||||
|
driver. This driver allows you to cycle though other animation effects using
|
||||||
|
a keybinding.
|
||||||
|
|
||||||
|
A simple root animation node looks like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
root_animation: animation_0 {
|
||||||
|
compatible = "zmk,animation-control";
|
||||||
|
label = "ANIMATION_CTRL_0";
|
||||||
|
/* List of all animations to cycle through */
|
||||||
|
animations = <&ripple_effect &solid_color>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
You should set the `zmk,animation` chosen node to this root animation:
|
||||||
|
|
||||||
|
```
|
||||||
|
chosen {
|
||||||
|
zmk,animation = &root_animation;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, you should add a keybinding to use the animation control feature
|
||||||
|
within your keymap:
|
||||||
|
```
|
||||||
|
#include <dt-bindings/zmk/animation.h>
|
||||||
|
|
||||||
|
default_layer {
|
||||||
|
/* Basic binding, toggles animation on and off */
|
||||||
|
bindings = <&animation ANIMATION_TOGGLE(0)>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Animation Drivers
|
||||||
|
|
||||||
|
Animation drivers allow you to create custom effects on your keyboard,
|
||||||
|
and implement different animation zones. Zone implementation is performed
|
||||||
|
using the `pixels` property, which sets the pixels from the `pixels` property
|
||||||
|
in the core animation node that the animation effect will apply to.
|
||||||
|
|
||||||
|
### Blending Modes
|
||||||
|
|
||||||
|
Several animations can utilize blending effects via the
|
||||||
|
`zmk_apply_blending_mode` API. The following blending modes are supported:
|
||||||
|
|
||||||
|
| Mode | Affect |
|
||||||
|
|----------|---------------------------------------------------------------------------|
|
||||||
|
| Multiply | Multiplies each pixel's color by the new effect's pixel color |
|
||||||
|
| Lighten | Selects the highest R, G, and B values between old and new for each pixel |
|
||||||
|
| Darken | Selects the lowest R, G, and B values between old and new for each pixel |
|
||||||
|
| Screen | Adds (1 - base {R,G,B}) * new {R,G,B} value to base {R,G,B} value |
|
||||||
|
|
||||||
|
|
||||||
|
### Ripple Effect
|
||||||
|
|
||||||
|
Creates a ripple effect on your keyboard each time a key is pressed,
|
||||||
|
originating at the pixel corresponding to the key. An example of
|
||||||
|
a ripple effect node is given below.
|
||||||
|
|
||||||
|
```
|
||||||
|
ripple_effect: animation_1 {
|
||||||
|
compatible = "zmk,animation-ripple";
|
||||||
|
status = "okay";
|
||||||
|
/* Will only apply the effect to the first 4 pixels */
|
||||||
|
pixels = <0 1 2 3>;
|
||||||
|
/* Sets how this effect will be blended with other animations */
|
||||||
|
blending-mode = <BLENDING_MODE_NORMAL>;
|
||||||
|
/* Duration of animation in ms */
|
||||||
|
duration = <1000>;
|
||||||
|
/* Will result in a pure white */
|
||||||
|
color = <HSL(0, 0, 100)>;
|
||||||
|
ripple-width = <50>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Solid Color
|
||||||
|
|
||||||
|
Creates a solid color background. This effect can be combined with other
|
||||||
|
effects such as the ripple animation to produce a static background, or
|
||||||
|
zone specific lighting
|
||||||
|
|
||||||
|
```
|
||||||
|
solid_color: color {
|
||||||
|
compatible = "zmk,animation-solid";
|
||||||
|
status = "okay";
|
||||||
|
/* All pixels will be illuminated */
|
||||||
|
pixels = <0 1 2 3 4 5 6>;
|
||||||
|
colors = <HSL(236, 30, 50)>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compose Animation
|
||||||
|
|
||||||
|
Composes multiple animations together, allowing you to combine animation zones
|
||||||
|
or effects. Each animation will be rendered sequentially. If an animation has
|
||||||
|
a blending mode set, then it will be blended onto the current state of the
|
||||||
|
pixels
|
||||||
|
|
||||||
|
```
|
||||||
|
combine_zones: animation_1 {
|
||||||
|
compatible = "zmk,animation-compose";
|
||||||
|
status = "okay";
|
||||||
|
animations = <
|
||||||
|
&solid_color
|
||||||
|
&ripple_effect
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
```
|
|
@ -20,6 +20,7 @@ module.exports = {
|
||||||
"features/backlight",
|
"features/backlight",
|
||||||
"features/battery",
|
"features/battery",
|
||||||
"features/beta-testing",
|
"features/beta-testing",
|
||||||
|
"features/animations",
|
||||||
],
|
],
|
||||||
Behaviors: [
|
Behaviors: [
|
||||||
"behaviors/key-press",
|
"behaviors/key-press",
|
||||||
|
@ -42,6 +43,7 @@ module.exports = {
|
||||||
"behaviors/underglow",
|
"behaviors/underglow",
|
||||||
"behaviors/backlight",
|
"behaviors/backlight",
|
||||||
"behaviors/power",
|
"behaviors/power",
|
||||||
|
"behaviors/animations",
|
||||||
],
|
],
|
||||||
Codes: [
|
Codes: [
|
||||||
"codes/index",
|
"codes/index",
|
||||||
|
|
Loading…
Add table
Reference in a new issue