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.
daktil_manuform
Nathan Gray 2020-03-10 12:50:01 -07:00 committed by GitHub
parent 2a8ccafe6e
commit 2ffb08843b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 162 additions and 0 deletions

View File

@ -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
* `#define RGBLIGHT_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`
* number of LEDs
* `#define RGBLIGHT_SPLIT`

View File

@ -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};
```
## 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
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_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
|Function |Description |
|-----------------------|-----------------|

View File

@ -38,17 +38,23 @@
# include "velocikey.h"
#endif
#ifndef MIN
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifdef RGBLIGHT_SPLIT
/* for split keyboard */
# 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_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_ANIMATION_TICK rgblight_status.change_flags |= RGBLIGHT_STATUS_ANIMATION_TICK
#else
# define RGBLIGHT_SPLIT_SET_CHANGE_MODE
# define RGBLIGHT_SPLIT_SET_CHANGE_HSVS
# define RGBLIGHT_SPLIT_SET_CHANGE_MODEHSVS
# define RGBLIGHT_SPLIT_SET_CHANGE_LAYERS
# define RGBLIGHT_SPLIT_SET_CHANGE_TIMER_ENABLE
# define RGBLIGHT_SPLIT_ANIMATION_TICK
#endif
@ -97,6 +103,10 @@ LED_TYPE led[RGBLED_NUM];
# define LED_ARRAY led
#endif
#ifdef RGBLIGHT_LAYERS
rgblight_segment_t const * const *rgblight_layers = NULL;
#endif
static uint8_t clipping_start_pos = 0;
static uint8_t clipping_num_leds = RGBLED_NUM;
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); }
#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
void rgblight_set(void) {
LED_TYPE *start_led;
uint16_t num_leds = clipping_num_leds;
# ifdef RGBLIGHT_LAYERS
if (rgblight_layers != NULL) {
rgblight_layers_write();
}
# endif
if (!rgblight_config.enable) {
for (uint8_t i = effect_start_pos; i < effect_end_pos; i++) {
led[i].r = 0;
@ -652,6 +718,11 @@ void rgblight_get_syncinfo(rgblight_syncinfo_t *syncinfo) {
/* for split keyboard slave side */
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->config.enable) {
rgblight_config.enable = 1; // == rgblight_enable_noeeprom();

View File

@ -170,6 +170,29 @@ enum RGBLIGHT_EFFECT_MODE {
# include <avr/pgmspace.h>
# 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 const uint8_t RGBLED_BREATHING_INTERVALS[4] PROGMEM;
@ -199,6 +222,9 @@ typedef struct _rgblight_status_t {
# ifdef RGBLIGHT_SPLIT
uint8_t change_flags;
# endif
# ifdef RGBLIGHT_LAYERS
uint8_t enabled_layer_mask;
# endif
} rgblight_status_t;
/* === Utility Functions ===*/
@ -313,6 +339,7 @@ void rgblight_timer_toggle(void);
# define RGBLIGHT_STATUS_CHANGE_HSVS (1 << 1)
# define RGBLIGHT_STATUS_CHANGE_TIMER (1 << 2)
# define RGBLIGHT_STATUS_ANIMATION_TICK (1 << 3)
# define RGBLIGHT_STATUS_CHANGE_LAYERS (1 << 4)
typedef struct _rgblight_syncinfo_t {
rgblight_config_t config;