Turning on specific leds when hitting a specific layer

Currently, when I hold down the right fn key, my home row becomes my number keys. I removed the number keys and placed fn keys there. I would l like to light up the home row when I hold that fn key. What is the best way to make this happen?

Is there something like the stacked layout for LEDs?

There are a few ways you can do this. One is to use the Colormap plugin, which allows you to specify a colormap on a per-layer basis. The downside of this is that at the moment, it is hard to set up, because it stores the colormap and the palette in EEPROM. Chrysalis has been making big steps towards supporting this (thanks to @james.nvc!), and may be enough to get started.

Colormap is a LED mode, and as such, no other modes can be active at the same time, so you can’t use it to have a rainbow effect, and override only some of the LEDs when switching layers.

If you want to override some keys, the easiest is probably to write a loop hook, something along these lines:

static void layerColorOverride(bool post_clear) {
  if (!post_clear)
    return;

  LEDControl.setCrgbAt(0, 0, CRGB(255, 0, 0));
  /* and so on... params are row, col, color */
}

And in your setup(), place Kaleidoscope.useLoopHook(layerColorOverride) somewhere after the Kaleidoscope.use call.

There isn’t. I was working on something like that, but it ended up either being incredibly slow, or incredibly complicated, or both.

2 Likes

Thanks for this! I will get on this.

How does this know when to change the colors? I don’t see how to make the loop kick off when I press the fn key.

The loop runs every cycle, so you don’t need to explicitly trigger it yourself. What I forgot to write, is that it should return early if you’re not on the right layer:

if (!Layer.isOn(LAYER_YOU_WANT_TO_COLOR))
  return;

This way if you are not on the layer to color, it will do nothing. If you are, it will - in each cycle - set the LED colors to the whatever you wanted. If the colors didn’t change between cycles, this will be a fast operation, because LEDControl is smart enough to only refresh LEDs when there is change. This also makes it possible to override dynamic LED effects, because we only sync LEDs at the end, so if they change the color, you change it back, the net effect is that the color will remain the same as in the previous cycle.

3 Likes

That worked great. Now I need to start organizing the repo a little better. I’m sure some header files would help a lot. You are an amazing help, @algernon.

2 Likes

I am so excited to say that I am on Day 5 of Model 01 adoption and I was able to successfully use the above advice to build a separate layer for a FnKey’lock mode that highlights the numeric keys a different color (our console-based IDE is very function key heavyI)

3 Likes

I’d love to apply colors to layers (like numlock). However, looking on git at the documentation, its a bit complex. Could I eventually figure it out? Probably, but when colormap has a dependency on (kaleidoscope-)focus, and focus is still experimental/passing, I’m more inclined to wait a bit. Besides, I’m waiting on @Jennigma to put her outstanding documentation skills to use.

Focus & Colormap are okay, it’s the apps that use Focus that are still in progress :stuck_out_tongue:
Chrysalis can currently do one layer with Colormap, but hopefully I’ll be able to get it doing multiple layers tomorrow.

1 Like

I looked at the ColorMap plugin, but couldn’t get it to work.
As an alternative I used the Led-ActiveModColor and changed the code of that plugin.

I currently have the following code:

  for (byte r = 0; r < ROWS; r++) {
    for (byte c = 0; c < COLS; c++) {
      Key k = Layer.lookupOnActiveLayer(r, c);

      if (Layer.isOn(1) || Layer.isOn(2)) {
        if ((k.raw >= Key_RightArrow.raw) && (k.raw <= Key_UpArrow.raw)) {
          ::LEDControl.setCrgbAt(r, c, highlight_color);
        } else {
          if ((k.raw == Key_Home.raw) || (k.raw == Key_End.raw) || (k.raw == Key_PageUp.raw) || (k.raw == Key_PageDown.raw)) {
            ::LEDControl.setCrgbAt(r, c, sticky_color);
          } else {
            if ((k.raw == Key_LeftControl.raw) || (k.raw == Key_RightControl.raw) || (k.raw == Key_LeftShift.raw) || (k.raw == Key_RightShift.raw)) {
              ::LEDControl.setCrgbAt(r, c, ccp_color);
            } else {
              ::LEDControl.refreshAt(r, c);
            }
          }
        }
      }  else {
        if (hid::isModifierKeyActive(k) /*&& ((k.raw == Key_Z.raw) || (k.raw == Key_X.raw) || (k.raw == Key_C.raw) || (k.raw == Key_V.raw))*/) {
          ::LEDControl.setCrgbAt(r, c, ccp_color);
        } else {
          ::LEDControl.refreshAt(r, c);
        }
      }

I would like to color the Z, X, C and V buttons green (=ccp_color) when the control button is pressed.
I tried checking for hid::isModifierKeyActive, but that didn’t work. How can I check if the Control button is pressed?

Update:
I figured it out. The parameter of the isModifierKeyActive() has to be the Key_RightControl instead of k.

  for (byte r = 0; r < ROWS; r++) {
    for (byte c = 0; c < COLS; c++) {
      Key k = Layer.lookupOnActiveLayer(r, c);

      if (Layer.isOn(1) || Layer.isOn(2)) {
        if ((k.raw >= Key_RightArrow.raw) && (k.raw <= Key_UpArrow.raw)) {
          ::LEDControl.setCrgbAt(r, c, highlight_color);
        } else
        if ((k.raw >= Key_F1.raw) && (k.raw <= Key_F12.raw)) {
          ::LEDControl.setCrgbAt(r, c, fn_color);
        } else
        if ((k.raw == Key_Home.raw) || (k.raw == Key_End.raw) || (k.raw == Key_PageUp.raw) || (k.raw == Key_PageDown.raw)) {
          ::LEDControl.setCrgbAt(r, c, sticky_color);
        } else
        if ((k.raw == Key_LeftControl.raw) || (k.raw == Key_RightControl.raw) || (k.raw == Key_LeftShift.raw) || (k.raw == Key_RightShift.raw)) {
          ::LEDControl.setCrgbAt(r, c, ccp_color);
        } else {
          ::LEDControl.refreshAt(r, c);
        }
      }  else {
        if (hid::isModifierKeyActive(k)) {
          ::LEDControl.setCrgbAt(r, c, highlight_color);
        } else
        if ((hid::isModifierKeyActive(Key_RightControl)) && ((k.raw == Key_Z.raw) || (k.raw == Key_X.raw) || (k.raw == Key_C.raw) || (k.raw == Key_V.raw)))  {
          ::LEDControl.setCrgbAt(r, c, ccp_color);
        } else {
          ::LEDControl.refreshAt(r, c);
        }
      }
1 Like

I can’t get this to work. When I set up colors for the function layer, those colors stay even when I go back to the qwerty level. I tried setting them all to 0, but then those keys are off on any LED setting.

static void layerColorOverride(bool post_clear) {
  if (!post_clear)
    return;
  if (!Layer.isOn(NUMPAD) && !Layer.isOn(FUNCTION)) { return; }
  if (Layer.isOn(NUMPAD) || Layer.isOn(FUNCTION)) {
    LEDControl.setCrgbAt(1, 3, CRGB(255, 255, 255));
    LEDControl.setCrgbAt(2, 2, CRGB(255, 255, 255));
    LEDControl.setCrgbAt(2, 3, CRGB(255, 255, 255));
    LEDControl.setCrgbAt(2, 4, CRGB(255, 255, 255));
    LEDControl.setCrgbAt(1, 2, CRGB(0, 255, 255));
    LEDControl.setCrgbAt(1, 4, CRGB(0, 255, 255));
    LEDControl.setCrgbAt(1, 1, CRGB(0, 0, 255));
    LEDControl.setCrgbAt(1, 5, CRGB(0, 0, 255));
    LEDControl.setCrgbAt(2, 1, CRGB(0, 0, 255));
    LEDControl.setCrgbAt(2, 5, CRGB(0, 0, 255));
    LEDControl.setCrgbAt(3, 1, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(3, 2, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(3, 3, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(3, 4, CRGB(255, 255, 0));
  }
  if (Layer.isOn(FUNCTION)) {
    LEDControl.setCrgbAt(2, 10, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(2, 11, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(2, 12, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(2, 13, CRGB(255, 255, 0));
  }
  /* and so on... params are row, col, color */
}

With them all on 0:

static void layerColorOverride(bool post_clear) {
  if (!post_clear)
    return;
  if (!Layer.isOn(NUMPAD) && !Layer.isOn(FUNCTION) {
    LEDControl.setCrgbAt(1, 3, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(2, 2, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(2, 3, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(2, 4, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(1, 2, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(1, 4, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(1, 1, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(1, 5, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(2, 1, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(2, 5, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(3, 1, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(3, 2, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(3, 3, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(3, 4, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(2, 10, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(2, 11, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(2, 12, CRGB(0, 0, 0));
    LEDControl.setCrgbAt(2, 13, CRGB(0, 0, 0));
  }
  if (Layer.isOn(NUMPAD) || Layer.isOn(FUNCTION)) {
    LEDControl.setCrgbAt(1, 3, CRGB(255, 255, 255));
    LEDControl.setCrgbAt(2, 2, CRGB(255, 255, 255));
    LEDControl.setCrgbAt(2, 3, CRGB(255, 255, 255));
    LEDControl.setCrgbAt(2, 4, CRGB(255, 255, 255));
    LEDControl.setCrgbAt(1, 2, CRGB(0, 255, 255));
    LEDControl.setCrgbAt(1, 4, CRGB(0, 255, 255));
    LEDControl.setCrgbAt(1, 1, CRGB(0, 0, 255));
    LEDControl.setCrgbAt(1, 5, CRGB(0, 0, 255));
    LEDControl.setCrgbAt(2, 1, CRGB(0, 0, 255));
    LEDControl.setCrgbAt(2, 5, CRGB(0, 0, 255));
    LEDControl.setCrgbAt(3, 1, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(3, 2, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(3, 3, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(3, 4, CRGB(255, 255, 0));
  }
  if (Layer.isOn(FUNCTION)) {
    LEDControl.setCrgbAt(2, 10, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(2, 11, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(2, 12, CRGB(255, 255, 0));
    LEDControl.setCrgbAt(2, 13, CRGB(255, 255, 0));
  }
  /* and so on... params are row, col, color */
}

Try LEDControl.refreshAt(row, col) instead of setting them to zero.

Hi @algernon, I am trying to do it with three main colors: one is a base color that most keys will have, another a highlighted color for the home row, and another for the numpad keys.

I devised a way to specify a map of sorts using a bidimensional array and also used a for loop to set the colors by coordinates.

Also, I am storing the last layer that was active during the previous call to skip the processing if it is the same (also, function layer and qwerty share the led settings, so I will not change the scheme if qwerty is active and the new layer is function, and vice versa).

I am assuming that Layer.top() returns the currently active layer.

I am a newcomer to c++ and I have a java background. Just getting this to compile was quite a challenge for me (oh my god, pointers!).
Here is my code, can you think of any improvements? I don’t have the keyboardio yet, do you think this would work? Thanks in advance!

const cRGB base = CRGB(255,255,100);
const cRGB high = CRGB(0,0,255);
const cRGB nump = CRGB(255,0,0);
uint8_t LAST_LAYER = -1;

const cRGB * qwerty [ROWS][COLS] = {
  {&base,&base,&base,&base,&base,&base,&base,       &base,  &base,        &base,&base,&base,&base,&base,&base,&base},
  {&base,&base,&base,&base,&base,&base,&base,       &base,  &base,        &base,&base,&base,&base,&base,&base,&base},
  {&base,&high,&high,&high,&high,&base,&base,       &base,  &base,        &base,&base,&high,&high,&high,&high,&base},
  {&base,&base,&base,&base,&base,&base,    &base,   &base,  &base,    &base,    &base,&base,&base,&base,&base,&base},
};


const cRGB * numpad [ROWS][COLS] = {
  {&base,&base,&base,&base,&base,&base,&base,       &base,  &base,        &base,&base,&nump,&nump,&nump,&base,&base},
  {&base,&base,&nump,&base,&base,&base,&base,       &base,  &base,        &base,&base,&nump,&nump,&nump,&nump,&base},
  {&base,&nump,&nump,&nump,&base,&base,&base,       &base,  &base,        &base,&base,&nump,&nump,&nump,&nump,&base},
  {&base,&base,&base,&base,&base,&base,    &base,   &base,  &base,    &base,    &nump,&nump,&nump,&nump,&nump,&nump},
};

/**
   Loop hook to override LED colors
*/
static void layerColorOverride(bool post_clear) {
    if (!post_clear)
      return;

    uint8_t top = Layer.top();
    if(LAST_LAYER == top || 
      (LAST_LAYER == QWERTY && top == FUNCTION) || 
      (LAST_LAYER == FUNCTION && top == QWERTY))
      return;

    LAST_LAYER = top;
    const cRGB * (*scheme)[ROWS][COLS];
    
    if (top == NUMPAD) {
      scheme = &numpad;
    } else {
      scheme = &qwerty;
    }
    
    for(byte x = 0; x < ROWS;x++) {
      for(byte y = 0; y < COLS;y++){
        LEDControl.setCrgbAt(x, y, **scheme[x][y]);
      }
    }
}

The biggest improvement would be to use a color array, and indexes in the numpad and qwerty color maps: a pointer takes two bytes, while an index is only one, so you could save 128 bytes of RAM just by this. Then, moving those two arrays to PROGMEM would save some more RAM, at the cost of some program space. These are two reasonably simple, yet, big savings. You could tune it further if you use a limited amount of colors (or even use the existing Colormap plugin, which stores the palette and the colors in EEPROM, but is a bit harder to set up right now), but doing so may not be worth it.

Other than that, the code looks good after a quick glance.

1 Like

Thanks for the response. I am left in doubt, however. When you say to use indexes you mean something like this?

const cRGB colors[3] = {CRGB(255,255,100),CRGB(0,0,255),CRGB(255,0,0) };
const byte qwerty [ROWS][COLS] = {1,1,1,1,1,1 ... 2, 3, 1,1,1,1, ...

Also, how does one move stuff over to PROGMEM?
You mentioned using a limited amount of colors. I’m just using three, did you mean something else I’m not getting?
Cheers, glad you think it looks all right.

Yep, that’s what I meant.

static const byte qwerty[ROWS][COLS] PROGMEM = {....};

// and to retrieve data from there:
byte colorIndex = pgm_read_word(&(scheme[row][col]));

Putting the palette into PROGMEM is probably not worth it, because it’s small enough to stay in RAM, and moving it to PROGMEM would complicate the code too much (and introduce a dependency on the order of members in cRGB, because we’d have to pull byte by byte).

I meant that you only use a small number of colors, so using a palette is a viable optimization.

1 Like

OK, now I got almost everything you said :slight_smile: Just missing the pallette, is that a type or something that I could use? Or do you mean the thing that the colormap plugin stores in EEPROM?

const cRGB colors[3] = {CRGB(255,255,100),CRGB(0,0,255),CRGB(255,0,0) };

This is the palette =)

A set of colors you use in your… picture (in our case, painted by LEDs on the keyboard, so to say).

1 Like

Ah, ok! So basically what we talked about earlier. Well, by mid-april I should hopefully let you know how it went. Thank you so much!

In case anyone is interested, this is the latest version of my code. It is untested but @algernon approves which sort of validates it to some extent.
You can change or add colors to the colors array, and also add more variations of the color scheme by adding variables with the same structure as qwerty and numpad.
If you look at the indentation in these variables, the two center columns are the thumb buttons, whith each column matching the top to bottom placement of the cluster. And the two indented numbers at the bottom rows match the fn keys.
Of course if you add more schemes, you need to update the layerColorOverride logic to use them.

If you set any key to 0, it means it will switch off the key LED. This is my personal layout and I have all keys lit at all times, but it can be changed at your leisure.

// Color pallette. Can have as many colors as needed. 
// The only requirement is for the first item to remain unchanged. 
// The first item is used to shut down the led in any given key. 
const cRGB colors[4] = {
  CRGB(0,0,0),       // Off state, index 0
  CRGB(255,255,100), // Base color, index 1
  CRGB(0,0,255),     // blue highlighting, index 2
  CRGB(255,0,0)      // red highlighting, index 3
};


// Each number here is referencing an index from the colors array. 
// The formatting reflects the actual key layout on the board
static const byte qwerty [ROWS][COLS] PROGMEM =  {
  {1,1,1,1,1,1,1,       1,  1,        1,1,1,1,1,1,1},
  {1,1,1,1,1,1,1,       1,  1,        1,1,1,1,1,1,1},
  {1,2,2,2,2,1,1,       1,  1,        1,1,2,2,2,2,1},
  {1,1,1,1,1,1,    1,   1,  1,    1,    1,1,1,1,1,1}
};

// I moved wasd to esdf for FPS so they get highlighted for fraggin' :-)
static const byte numpad [ROWS][COLS] PROGMEM = {
  {1,1,1,1,1,1,1,       1,  1,        1,1,3,3,3,1,1},
  {1,1,3,1,1,1,1,       1,  1,        1,1,3,3,3,3,1},
  {1,3,3,3,1,1,1,       1,  1,        1,1,3,3,3,3,1},
  {1,1,1,1,1,1,    1,   1,  1,    1,    3,3,3,3,3,3}
};

// A global variable to store the previously selected layer
uint8_t previous_layer = -1;

/**
   Loop hook to override LED colors
*/
static void layerColorOverride(bool post_clear) {
    if (!post_clear)
      return;

    uint8_t top = Layer.top();

    // If the layer coming in is the same as last time, do nothing. 
    // Also in this case, qwerty and function are equivalent. 
    if(previous_layer == top || 
      (previous_layer == QWERTY && top == FUNCTION) || 
      (previous_layer == FUNCTION && top == QWERTY))
      return;

    // Remember this layer for future calls
    previous_layer = top;

    // Reference for the color scheme to use
    const byte (*scheme)[ROWS][COLS];

    // Select the color scheme to use
    if (top == NUMPAD) {
      scheme = &numpad;
    } else {
      scheme = &qwerty;
    }

    // Apply the scheme to the button LEDs
    for(byte x = 0; x < ROWS;x++) {
      for(byte y = 0; y < COLS;y++){
        byte colorIndex = pgm_read_byte(*scheme[x][y]);
        LEDControl.setCrgbAt(x, y, colors[colorIndex]);
        // Ensuring led shuts down if it was assigned to be off
        if(0 == colorIndex)
          LEDControl.refreshAt(x,y);
      }
    }
}

Don’t forget to add the loop hook inside of the setup() method:

Kaleidoscope.useLoopHook(layerColorOverride);

Please let me know if you try it!

1 Like