mirror of
https://github.com/qmk/qmk_firmware
synced 2024-11-19 03:36:28 +00:00
Feature: RGBLight layers (#7768)
* New feature: RGBLIGHT_LAYERS This feature allows users to define multiple independent layers of lighting that can be toggled on and off individually, making it easy to use your RGB lighting to indicate things like active keyboard layer & modifier state. * Demonstrate built in functions for layer state checking Also link the video in the docs. * Follow existing pattern for setting rgblight_status flags * Eliminate rgblight_is_static_mode since it's not needed Just check to see if the timer is enabled directly.
This commit is contained in:
parent
2a8ccafe6e
commit
2ffb08843b
4 changed files with 162 additions and 0 deletions
|
@ -190,6 +190,8 @@ If you define these options you will enable the associated feature, which may in
|
||||||
* pin the DI on the WS2812 is hooked-up to
|
* pin the DI on the WS2812 is hooked-up to
|
||||||
* `#define RGBLIGHT_ANIMATIONS`
|
* `#define RGBLIGHT_ANIMATIONS`
|
||||||
* run RGB animations
|
* run RGB animations
|
||||||
|
* `#define RGBLIGHT_LAYERS`
|
||||||
|
* Lets you define [lighting layers](feature_rgblight.md) that can be toggled on or off. Great for showing the current keyboard layer or caps lock state.
|
||||||
* `#define RGBLED_NUM 12`
|
* `#define RGBLED_NUM 12`
|
||||||
* number of LEDs
|
* number of LEDs
|
||||||
* `#define RGBLIGHT_SPLIT`
|
* `#define RGBLIGHT_SPLIT`
|
||||||
|
|
|
@ -172,6 +172,62 @@ const uint8_t RGBLED_KNIGHT_INTERVALS[] PROGMEM = {127, 63, 31};
|
||||||
const uint8_t RGBLED_GRADIENT_RANGES[] PROGMEM = {255, 170, 127, 85, 64};
|
const uint8_t RGBLED_GRADIENT_RANGES[] PROGMEM = {255, 170, 127, 85, 64};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Lighting Layers
|
||||||
|
|
||||||
|
By including `#define RGBLIGHT_LAYERS` in your `config.h` file you can enable lighting layers. These make
|
||||||
|
it easy to use your underglow LEDs as status indicators to show which keyboard layer is currently active, or the state of caps lock, all without disrupting any animations. [Here's a video](https://youtu.be/uLGE1epbmdY) showing an example of what you can do.
|
||||||
|
|
||||||
|
To define a layer, we modify `keymap.c` to list out LED ranges and the colors we want to overlay on them using an array of `rgblight_segment_t` using the `RGBLIGHT_LAYER_SEGMENTS` macro. We can define multiple layers and enable/disable them independently:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Light LEDs 6 to 9 and 12 to 15 red when caps lock is active. Hard to ignore!
|
||||||
|
const rgblight_segment_t PROGMEM my_capslock_layer[] = RGBLIGHT_LAYER_SEGMENTS(
|
||||||
|
{6, 4, HSV_RED}, // Light 4 LEDs, starting with LED 6
|
||||||
|
{12, 4, HSV_RED} // Light 4 LEDs, starting with LED 12
|
||||||
|
);
|
||||||
|
// Light LEDs 9 & 10 in cyan when keyboard layer 1 is active
|
||||||
|
const rgblight_segment_t PROGMEM my_layer1_layer[] = RGBLIGHT_LAYER_SEGMENTS(
|
||||||
|
{9, 2, HSV_CYAN}
|
||||||
|
);
|
||||||
|
// Light LEDs 11 & 12 in purple when keyboard layer 2 is active
|
||||||
|
const rgblight_segment_t PROGMEM my_layer2_layer[] = RGBLIGHT_LAYER_SEGMENTS(
|
||||||
|
{11, 2, HSV_PURPLE},
|
||||||
|
);
|
||||||
|
// etc..
|
||||||
|
```
|
||||||
|
|
||||||
|
We combine these layers into an array using the `RGBLIGHT_LAYERS_LIST` macro, and assign it to the `rgblight_layers` variable during keyboard setup. Note that you can only define up to 8 lighting layers. Any extra layers will be ignored. Since the different lighting layers overlap, the order matters in the array, with later layers taking precedence:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Now define the array of layers. Later layers take precedence
|
||||||
|
const rgblight_segment_t* const PROGMEM my_rgb_layers[] = RGBLIGHT_LAYERS_LIST(
|
||||||
|
my_capslock_layer,
|
||||||
|
my_layer1_layer, // Overrides caps lock layer
|
||||||
|
my_layer2_layer // Overrides other layers
|
||||||
|
);
|
||||||
|
|
||||||
|
void keyboard_post_init_user(void) {
|
||||||
|
// Enable the LED layers
|
||||||
|
rgblight_layers = my_rgb_layers;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, we enable and disable the lighting layers whenever the state of the keyboard changes:
|
||||||
|
|
||||||
|
```c
|
||||||
|
layer_state_t layer_state_set_user(layer_state_t state) {
|
||||||
|
// Both layers will light up if both kb layers are active
|
||||||
|
rgblight_set_layer_state(1, layer_state_cmp(state, 1));
|
||||||
|
rgblight_set_layer_state(2, layer_state_cmp(state, 2));
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool led_update_user(led_t led_state) {
|
||||||
|
rgblight_set_layer_state(0, led_state.caps_lock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
If you need to change your RGB lighting in code, for example in a macro to change the color whenever you switch layers, QMK provides a set of functions to assist you. See [`rgblight.h`](https://github.com/qmk/qmk_firmware/blob/master/quantum/rgblight.h) for the full list, but the most commonly used functions include:
|
If you need to change your RGB lighting in code, for example in a macro to change the color whenever you switch layers, QMK provides a set of functions to assist you. See [`rgblight.h`](https://github.com/qmk/qmk_firmware/blob/master/quantum/rgblight.h) for the full list, but the most commonly used functions include:
|
||||||
|
@ -263,6 +319,12 @@ rgblight_sethsv(HSV_GREEN, 2); // led 2
|
||||||
|`rgblight_sethsv(h, s, v)` |Set effect range LEDs to the given HSV value where `h`/`s`/`v` are between 0 and 255 |
|
|`rgblight_sethsv(h, s, v)` |Set effect range LEDs to the given HSV value where `h`/`s`/`v` are between 0 and 255 |
|
||||||
|`rgblight_sethsv_noeeprom(h, s, v)` |Set effect range LEDs to the given HSV value where `h`/`s`/`v` are between 0 and 255 (not written to EEPROM) |
|
|`rgblight_sethsv_noeeprom(h, s, v)` |Set effect range LEDs to the given HSV value where `h`/`s`/`v` are between 0 and 255 (not written to EEPROM) |
|
||||||
|
|
||||||
|
#### layer functions
|
||||||
|
|Function |Description |
|
||||||
|
|--------------------------------------------|-------------|
|
||||||
|
|`rgblight_get_layer_state(i)` |Returns `true` if lighting layer `i` is enabled |
|
||||||
|
|`rgblight_set_layer_state(i, is_on)` |Enable or disable lighting layer `i` based on value of `bool is_on` |
|
||||||
|
|
||||||
#### query
|
#### query
|
||||||
|Function |Description |
|
|Function |Description |
|
||||||
|-----------------------|-----------------|
|
|-----------------------|-----------------|
|
||||||
|
|
|
@ -38,17 +38,23 @@
|
||||||
# include "velocikey.h"
|
# include "velocikey.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef MIN
|
||||||
|
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef RGBLIGHT_SPLIT
|
#ifdef RGBLIGHT_SPLIT
|
||||||
/* for split keyboard */
|
/* for split keyboard */
|
||||||
# define RGBLIGHT_SPLIT_SET_CHANGE_MODE rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_MODE
|
# define RGBLIGHT_SPLIT_SET_CHANGE_MODE rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_MODE
|
||||||
# define RGBLIGHT_SPLIT_SET_CHANGE_HSVS rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_HSVS
|
# define RGBLIGHT_SPLIT_SET_CHANGE_HSVS rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_HSVS
|
||||||
# define RGBLIGHT_SPLIT_SET_CHANGE_MODEHSVS rgblight_status.change_flags |= (RGBLIGHT_STATUS_CHANGE_MODE | RGBLIGHT_STATUS_CHANGE_HSVS)
|
# define RGBLIGHT_SPLIT_SET_CHANGE_MODEHSVS rgblight_status.change_flags |= (RGBLIGHT_STATUS_CHANGE_MODE | RGBLIGHT_STATUS_CHANGE_HSVS)
|
||||||
|
# define RGBLIGHT_SPLIT_SET_CHANGE_LAYERS rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_LAYERS
|
||||||
# define RGBLIGHT_SPLIT_SET_CHANGE_TIMER_ENABLE rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_TIMER
|
# define RGBLIGHT_SPLIT_SET_CHANGE_TIMER_ENABLE rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_TIMER
|
||||||
# define RGBLIGHT_SPLIT_ANIMATION_TICK rgblight_status.change_flags |= RGBLIGHT_STATUS_ANIMATION_TICK
|
# define RGBLIGHT_SPLIT_ANIMATION_TICK rgblight_status.change_flags |= RGBLIGHT_STATUS_ANIMATION_TICK
|
||||||
#else
|
#else
|
||||||
# define RGBLIGHT_SPLIT_SET_CHANGE_MODE
|
# define RGBLIGHT_SPLIT_SET_CHANGE_MODE
|
||||||
# define RGBLIGHT_SPLIT_SET_CHANGE_HSVS
|
# define RGBLIGHT_SPLIT_SET_CHANGE_HSVS
|
||||||
# define RGBLIGHT_SPLIT_SET_CHANGE_MODEHSVS
|
# define RGBLIGHT_SPLIT_SET_CHANGE_MODEHSVS
|
||||||
|
# define RGBLIGHT_SPLIT_SET_CHANGE_LAYERS
|
||||||
# define RGBLIGHT_SPLIT_SET_CHANGE_TIMER_ENABLE
|
# define RGBLIGHT_SPLIT_SET_CHANGE_TIMER_ENABLE
|
||||||
# define RGBLIGHT_SPLIT_ANIMATION_TICK
|
# define RGBLIGHT_SPLIT_ANIMATION_TICK
|
||||||
#endif
|
#endif
|
||||||
|
@ -97,6 +103,10 @@ LED_TYPE led[RGBLED_NUM];
|
||||||
# define LED_ARRAY led
|
# define LED_ARRAY led
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef RGBLIGHT_LAYERS
|
||||||
|
rgblight_segment_t const * const *rgblight_layers = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
static uint8_t clipping_start_pos = 0;
|
static uint8_t clipping_start_pos = 0;
|
||||||
static uint8_t clipping_num_leds = RGBLED_NUM;
|
static uint8_t clipping_num_leds = RGBLED_NUM;
|
||||||
static uint8_t effect_start_pos = 0;
|
static uint8_t effect_start_pos = 0;
|
||||||
|
@ -604,11 +614,67 @@ void rgblight_sethsv_master(uint8_t hue, uint8_t sat, uint8_t val) { rgblight_se
|
||||||
void rgblight_sethsv_slave(uint8_t hue, uint8_t sat, uint8_t val) { rgblight_sethsv_range(hue, sat, val, (uint8_t)RGBLED_NUM / 2, (uint8_t)RGBLED_NUM); }
|
void rgblight_sethsv_slave(uint8_t hue, uint8_t sat, uint8_t val) { rgblight_sethsv_range(hue, sat, val, (uint8_t)RGBLED_NUM / 2, (uint8_t)RGBLED_NUM); }
|
||||||
#endif // ifndef RGBLIGHT_SPLIT
|
#endif // ifndef RGBLIGHT_SPLIT
|
||||||
|
|
||||||
|
#ifdef RGBLIGHT_LAYERS
|
||||||
|
void rgblight_set_layer_state(uint8_t layer, bool enabled) {
|
||||||
|
uint8_t mask = 1 << layer;
|
||||||
|
if (enabled) {
|
||||||
|
rgblight_status.enabled_layer_mask |= mask;
|
||||||
|
} else {
|
||||||
|
rgblight_status.enabled_layer_mask &= ~mask;
|
||||||
|
}
|
||||||
|
RGBLIGHT_SPLIT_SET_CHANGE_LAYERS;
|
||||||
|
// Static modes don't have a ticker running to update the LEDs
|
||||||
|
if (rgblight_status.timer_enabled == false) {
|
||||||
|
rgblight_mode_noeeprom(rgblight_config.mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rgblight_get_layer_state(uint8_t layer) {
|
||||||
|
uint8_t mask = 1 << layer;
|
||||||
|
return (rgblight_status.enabled_layer_mask & mask) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write any enabled LED layers into the buffer
|
||||||
|
static void rgblight_layers_write(void) {
|
||||||
|
uint8_t i = 0;
|
||||||
|
// For each layer
|
||||||
|
for (const rgblight_segment_t * const *layer_ptr = rgblight_layers; i < RGBLIGHT_MAX_LAYERS; layer_ptr++, i++) {
|
||||||
|
if (!rgblight_get_layer_state(i)) {
|
||||||
|
continue; // Layer is disabled
|
||||||
|
}
|
||||||
|
const rgblight_segment_t * segment_ptr = pgm_read_ptr(layer_ptr);
|
||||||
|
if (segment_ptr == NULL) {
|
||||||
|
break; // No more layers
|
||||||
|
}
|
||||||
|
// For each segment
|
||||||
|
while (1) {
|
||||||
|
rgblight_segment_t segment;
|
||||||
|
memcpy_P(&segment, segment_ptr, sizeof(rgblight_segment_t));
|
||||||
|
if (segment.index == RGBLIGHT_END_SEGMENT_INDEX) {
|
||||||
|
break; // No more segments
|
||||||
|
}
|
||||||
|
// Write segment.count LEDs
|
||||||
|
LED_TYPE * const limit = &led[MIN(segment.index + segment.count, RGBLED_NUM)];
|
||||||
|
for (LED_TYPE *led_ptr = &led[segment.index]; led_ptr < limit; led_ptr++) {
|
||||||
|
sethsv(segment.hue, segment.sat, segment.val, led_ptr);
|
||||||
|
}
|
||||||
|
segment_ptr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef RGBLIGHT_CUSTOM_DRIVER
|
#ifndef RGBLIGHT_CUSTOM_DRIVER
|
||||||
void rgblight_set(void) {
|
void rgblight_set(void) {
|
||||||
LED_TYPE *start_led;
|
LED_TYPE *start_led;
|
||||||
uint16_t num_leds = clipping_num_leds;
|
uint16_t num_leds = clipping_num_leds;
|
||||||
|
|
||||||
|
# ifdef RGBLIGHT_LAYERS
|
||||||
|
if (rgblight_layers != NULL) {
|
||||||
|
rgblight_layers_write();
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
if (!rgblight_config.enable) {
|
if (!rgblight_config.enable) {
|
||||||
for (uint8_t i = effect_start_pos; i < effect_end_pos; i++) {
|
for (uint8_t i = effect_start_pos; i < effect_end_pos; i++) {
|
||||||
led[i].r = 0;
|
led[i].r = 0;
|
||||||
|
@ -652,6 +718,11 @@ void rgblight_get_syncinfo(rgblight_syncinfo_t *syncinfo) {
|
||||||
|
|
||||||
/* for split keyboard slave side */
|
/* for split keyboard slave side */
|
||||||
void rgblight_update_sync(rgblight_syncinfo_t *syncinfo, bool write_to_eeprom) {
|
void rgblight_update_sync(rgblight_syncinfo_t *syncinfo, bool write_to_eeprom) {
|
||||||
|
# ifdef RGBLIGHT_LAYERS
|
||||||
|
if (syncinfo->status.change_flags & RGBLIGHT_STATUS_CHANGE_LAYERS) {
|
||||||
|
rgblight_status.enabled_layer_mask = syncinfo->status.enabled_layer_mask;
|
||||||
|
}
|
||||||
|
# endif
|
||||||
if (syncinfo->status.change_flags & RGBLIGHT_STATUS_CHANGE_MODE) {
|
if (syncinfo->status.change_flags & RGBLIGHT_STATUS_CHANGE_MODE) {
|
||||||
if (syncinfo->config.enable) {
|
if (syncinfo->config.enable) {
|
||||||
rgblight_config.enable = 1; // == rgblight_enable_noeeprom();
|
rgblight_config.enable = 1; // == rgblight_enable_noeeprom();
|
||||||
|
|
|
@ -170,6 +170,29 @@ enum RGBLIGHT_EFFECT_MODE {
|
||||||
# include <avr/pgmspace.h>
|
# include <avr/pgmspace.h>
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
|
# ifdef RGBLIGHT_LAYERS
|
||||||
|
typedef struct {
|
||||||
|
uint8_t index; // The first LED to light
|
||||||
|
uint8_t count; // The number of LEDs to light
|
||||||
|
uint8_t hue;
|
||||||
|
uint8_t sat;
|
||||||
|
uint8_t val;
|
||||||
|
} rgblight_segment_t;
|
||||||
|
|
||||||
|
# define RGBLIGHT_END_SEGMENT_INDEX (255)
|
||||||
|
# define RGBLIGHT_END_SEGMENTS {RGBLIGHT_END_SEGMENT_INDEX, 0, 0, 0}
|
||||||
|
# define RGBLIGHT_MAX_LAYERS 8
|
||||||
|
# define RGBLIGHT_LAYER_SEGMENTS(...) { __VA_ARGS__, RGBLIGHT_END_SEGMENTS }
|
||||||
|
# define RGBLIGHT_LAYERS_LIST(...) { __VA_ARGS__, NULL }
|
||||||
|
|
||||||
|
// Get/set enabled rgblight layers
|
||||||
|
void rgblight_set_layer_state(uint8_t layer, bool enabled);
|
||||||
|
bool rgblight_get_layer_state(uint8_t layer);
|
||||||
|
|
||||||
|
// Point this to an array of rgblight_segment_t arrays in keyboard_post_init_user to use rgblight layers
|
||||||
|
extern const rgblight_segment_t * const * rgblight_layers;
|
||||||
|
# endif
|
||||||
|
|
||||||
extern LED_TYPE led[RGBLED_NUM];
|
extern LED_TYPE led[RGBLED_NUM];
|
||||||
|
|
||||||
extern const uint8_t RGBLED_BREATHING_INTERVALS[4] PROGMEM;
|
extern const uint8_t RGBLED_BREATHING_INTERVALS[4] PROGMEM;
|
||||||
|
@ -199,6 +222,9 @@ typedef struct _rgblight_status_t {
|
||||||
# ifdef RGBLIGHT_SPLIT
|
# ifdef RGBLIGHT_SPLIT
|
||||||
uint8_t change_flags;
|
uint8_t change_flags;
|
||||||
# endif
|
# endif
|
||||||
|
# ifdef RGBLIGHT_LAYERS
|
||||||
|
uint8_t enabled_layer_mask;
|
||||||
|
# endif
|
||||||
} rgblight_status_t;
|
} rgblight_status_t;
|
||||||
|
|
||||||
/* === Utility Functions ===*/
|
/* === Utility Functions ===*/
|
||||||
|
@ -313,6 +339,7 @@ void rgblight_timer_toggle(void);
|
||||||
# define RGBLIGHT_STATUS_CHANGE_HSVS (1 << 1)
|
# define RGBLIGHT_STATUS_CHANGE_HSVS (1 << 1)
|
||||||
# define RGBLIGHT_STATUS_CHANGE_TIMER (1 << 2)
|
# define RGBLIGHT_STATUS_CHANGE_TIMER (1 << 2)
|
||||||
# define RGBLIGHT_STATUS_ANIMATION_TICK (1 << 3)
|
# define RGBLIGHT_STATUS_ANIMATION_TICK (1 << 3)
|
||||||
|
# define RGBLIGHT_STATUS_CHANGE_LAYERS (1 << 4)
|
||||||
|
|
||||||
typedef struct _rgblight_syncinfo_t {
|
typedef struct _rgblight_syncinfo_t {
|
||||||
rgblight_config_t config;
|
rgblight_config_t config;
|
||||||
|
|
Loading…
Reference in a new issue