FunctionalColor v2 - Automatically Label Keys with Colors

Plugin name: FunctionalColor
Author: JD Lien
Source URL: https://github.com/jdlien/Kaleidoscope-LEDEffect-FunctionalColor

Kaleidoscope-LEDEffect-FunctionalColor

This plugin automatically colors groups of keys based on the current function of the key on the active layer, dynamically switching colors when the fn key is pressed.

This is a significant improvement on the original FunctionalColor in terms of both flexibility and memory usage and has almost completely been rewritten - there is no reverse compatibility with prior versions, so if you have used this before, don’t update without reading this first.

Basic Use of FunctionalColor

To get started with FunctionalColor and use the default colors, you can just include the header, declare an instance using the FCPlugin class, and tell the firmware to use it. For working example .ino files, see the examples folder.

// Automatically sets key's LED on active layer based on the function of the key
#include <Kaleidoscope-LEDEffect-FunctionalColor.h>
kaleidoscope::LEDFunctionalColor::FCPlugin funColor;

void setup() {
  Kaleidoscope.setup();
  Kaleidoscope.use(&funColor);
}

Easily Use Included Themes

If you have extra Program memory available (about 3kB at present), you can use a shortcut to easily specify a built-in theme after the brightness.

Initializing with a theme is not recommended, as it is much less efficient, but it makes it easy to try out and switch between included themes if you can spare the memory.

To do this, specify a theme name (or an integer) after the brightness. These are within the kaleidosope::LEDFunctionalColor namespace and are
Base, Default, Fruit, Mono, Duo, Princess, Sea, Flower, Kids, RedWhiteBlue

// Without using the LEDFunctionalColor namespace, specifying a theme looks like this
kaleidoscope::LEDFunctionalColor::FCPlugin funColor1(240, kaleidoscope::LEDFunctionalColor::Fruit);

// Add this line to make invocation much simpler since you don't need to specify the namespace
using namespace kaleidosope::LEDFunctionalColor;

FCPlugin funColor1(240, Fruit);
FCPlugin funColor2(240, Mono);
FCPlugin funColor3(240, Duo);
FCPlugin funColor4(240, Princess);
FCPlugin funColor5(240, Sea);

//You can also specify a colorlist at the same time as an included theme
FCPlugin funColor6(FC_COLOR_LIST(customColors), 240, Flower);
FCPlugin funColor7(FC_COLOR_LIST(customColors), 240, Kids);
FCPlugin funColor8(FC_COLOR_LIST(customColors), 240, RedWhiteBlue);

Advanced Usage

FunctionalColor allows you to create completely custom themes, assigning any color to any function that can be performed on the Model 01. It’s probably easiest to examine the well-commented example .ino files included in this repository to get up and running and gain a full understanding of what is possible, but here is a summary.

Declare FCPlugin instances in the following ways:

// With no arguments to get the default theme and brightness.
kaleidoscope::LEDFunctionalColor::FCPlugin funColor1;

// Specify an optional brightness 0-255, and an optional colorList can follow.
kaleidoscope::LEDFunctionalColor::FCPlugin funColor2(200);

// Specify only a override colorList defined previously, beginning with FC_START_COLOR_LIST(customColors)
// See below for information on how to use override colorLists.
kaleidoscope::LEDFunctionalColor::FCPlugin funColor3(FC_COLOR_LIST(customColors));

//You can also specify a colorList with the brightness after.
kaleidoscope::LEDFunctionalColor::FCPlugin funColor4(FC_COLOR_LIST(customColors), 255);

// You can create and use custom themes - these are applied later in the setup() part of this .ino
kaleidoscope::LEDFunctionalColor::FCPlugin funColor5;

// Note that you can combine custom color overrides with a custom theme, demonstrated in funColor6
kaleidoscope::LEDFunctionalColor::FCPlugin funColor6(FC_COLOR_LIST(customColors));

Using As Little Memory As Possible

You can save memory by specifying false after your brightness setting, and no theme will be applied. This is useful if

  1. Your needs are simple, say, you only want to color a few keys or most keys will be the same color
  2. You are creating and applying a custom theme and won’t use the default theme anyways.
// Apply no theme to save memory. We will apply a colorList.
kaleidoscope::LEDFunctionalColor::FCPlugin funColorSimple(FC_COLOR_LIST(customColors), 240, false);

// We don't need the theme as we will apply our own later. We won't use a colorList here.
kaleidoscope::LEDFunctionalColor::FCPlugin funColorCustom(240, false);

Using Custom Themes

If you want to customize one of the included themes, make a subclass of colorMap or any theme struct that you want to use as a starting point. They are colorMap, colorMapDefault, colorMapFruit, colorMapMono, colorMapDuo, colorMapPrincess, colorMapSea, colorMapFlower, colorMapKids, colorMapRedWhiteBlue.

Your colormap must be applied in the setup() function. (See Below).

For colors, you can use cRGB objects with CRGB(red, green, blue). Note that FunctionalColor includes a large list of predefined colors that match the CSS color names. For the most part, if you can think of a color name, it’ll be defined. In addition, you can also tweak the brightness of a color using the included dim() function. For a dark red color, you could use something like dim(red, 100).

// Add this line after the FunctionalColor include if you don't want to
// have to prefix colors and functions with kaleidoscope::LEDFunctionalColor::
using namespace kaleidoscope::LEDFunctionalColor;

struct myTheme: public colorMapMono {
  static constexpr cRGB shift = darkseagreen;
  static constexpr cRGB control = skyblue;
  static constexpr cRGB alt = forestgreen;
  static constexpr cRGB gui = pink; 
  //You can also set something to "nocolor" which will avoid coloring a set of keys
  // if they already are part of another larger group - ie set shift to nocolor and
  // shiftkeys will inherit the color assigned to modifier
  // static constexpr cRGB shift = nocolor;
};

After creating your custom theme struct, apply it using FC_SET_THEME() in the setup() function near the bottom of the .ino file as shown here.

void setup() {
  Kaleidoscope.setup();
  Kaleidoscope.use(
    // All FunctionalColor instances go here in the order you want them in
    &funColor1,&funColor2,&funColor3,&funColor4,&funColor5,&funColor6
  );

  // Use the FC_SET_THEME() to apply colorMaps here.
  // If you aren't using namespace kaleidoscope::LEDFunctionalColor;
  // prefix built-in themes with that namespace
  // Here are all the defaults available:

  // The default is already used without specifying it anyways, but it's here for completeness
  FC_SET_THEME(funColor1, kaleidoscope::LEDFunctionalColor::colorMapDefault);
  FC_SET_THEME(funColor2, kaleidoscope::LEDFunctionalColor::colorMapMono);
  // The themes are: colorMap, colorMapDefault, colorMapFruit, colorMapMono, colorMapDuo,
  // colorMapPrincess, colorMapSea, colorMapFlower, colorMapKids, colorMapRedWhiteBlue.

  // This applies our custom themes to funColor5 and funColor6
  FC_SET_THEME(funColor5, myTheme);
  FC_SET_THEME(funColor6, colorMapGreen);

} // end setup()

For reference, here is a full, annotated list of all the properties that are supported by FunctionalColors

// This is the only way to color "prog" if you don't assign a function to it.
defaultColor // used when there is no color defined for a key.

// shift, control, gui, and alt can all be colored by "modifier" if nocolor is set here.
shift
control // gui are Windows Logo or, on macOS, command keys 
gui
alt

modifier
alpha
number
punctuation

function // F1-F12 and F13-F24

navigation // Page Up, Page Down, Home, End, Insert, and Delete (if del has nocolor)

system // Print Screen, Pause/Break, and Scroll Lock keys (brightness on Macs)

arrow
keypad

media // Includes play/pause, next/prev, volume control, mute, etc.

mouseWheel
mouseButton
mouseWarp
mouseMove
mouse //includes the four above groups if nocolor is set for those
space
tab
enter
backspace
escape
del //Forward delete key

//fn will work properly for ShiftToLayer() with layers 1-3
fn

//NumLock and other layer locks
lock
LEDEffectNext // led key

Setting individual keys with FC_COLOR_LIST

If you want to set specific colors for individual keys that are not specified in the colorMap struct, you can use a set of included macros to create a custom color override function before you declare a FunctionalColors instance, then specify your colorList when you initialize FunctionalColor.

Also, if you merely want a very simple configuration that is primarily one color with a few exceptions, you can create a colorList that ends with FC_END_COLOR_LIST_DEFAULT(color). Note that this default color will only be applied if you instantiate FCPlugin with “false” after the brightness, which prevents any theme from being applied.

The following examples show how these things can be done.
Note that this example is done within the kaleidoscope::LEDFunctionalColor namespace – add
using namespace kaleidoscope::LEDFunctionalColor;
to avoid needing to prefix colors and functions.

// Make a new colorList named "customColors"
FC_START_COLOR_LIST(customColors)
 // Use any number of FCGROUPKEYs above a FC_KEYCOLOR to set several keys to the same color
 FC_GROUPKEY(Key_A)
 FC_GROUPKEY(Key_S)
 FC_GROUPKEY(Key_D)
 FC_KEYCOLOR(Key_F, blue)

 FC_GROUPKEY(Key_J)
 FC_GROUPKEY(Key_K)
 FC_GROUPKEY(Key_L)
 FC_KEYCOLOR(Key_Semicolon, red)

 //FC_NOCOLOR makes a key not change color, as if "transparent".
 // In this example The uparrow key will not change the key color, even when on the active layer.
 FC_NOCOLOR(Key_UpArrow)

 // This shows how you can set the color of custom macros
 FC_GROUPKEY(M(MACRO_FCUP))
 FC_KEYCOLOR(M(MACRO_FCDOWN), cyan)
FC_END_COLOR_LIST


// An example simple configuration with no theme and a default color of pink.
// This is used in funColor7
FC_START_COLOR_LIST(simpleColors)
 // Make homing keys yellow
 FC_GROUPKEY(Key_A)
 FC_GROUPKEY(Key_F)
 FC_GROUPKEY(Key_J)
 FC_KEYCOLOR(Key_Semicolon, yellow)
// If you want to specify a default color and you are not using a theme,
// use FC_END_COLOR_LIST_DEFAULT and specify the default color for all keys not specified above.
FC_END_COLOR_LIST_DEFAULT(pink)

// Create a FunctionalColors instance using this new customColors colorList, with full brightness.
kaleidoscope::LEDFunctionalColor::FCPlugin funColorCustom(FC_COLOR_LIST(customColors), 255);

// This instance will not have a theme applied and will be pink except for the homing keys
kaleidoscope::LEDFunctionalColor::FCPlugin funColorSimple(FC_COLOR_LIST(simpleColors), 255, false);

Now you can add &funColorCustom to the Kaleidoscope.use() list to make it show up on your keyboard.

Brightness Control with Macros

FunctionalColor supports macros that allow you to add keys to adjust the brightness of your theme while using the keyboard. To do this, first ensure that you have MACRO_FCUP and MACRO_FCDOWN in the enum near the beginning of the .ino.

enum { MACRO_VERSION_INFO,
       MACRO_ANY,
       MACRO_FCUP,
       MACRO_FCDOWN
     };

Assign M(MACRO_FCDOWN) and M(MACRO_FCUP) to the keys you would like to use to control the brightness of the active FunctionalColor instance.

Finally, add the case statements to the macroAction function in your .ino file. If you’re keeping the VERSION_INFO and ANY macros that come with the stock firmware, macroAction should look like this:

const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
  switch (macroIndex) {

  case MACRO_VERSION_INFO:
    versionInfoMacro(keyState);
    break;

  case MACRO_ANY:
    anyKeyMacro(keyState);
    break;

  // Add the following two case statements to make the FCUP/FCDOWN macros adjust brightness.
  case MACRO_FCUP:
    kaleidoscope::LEDFunctionalColor::FCPlugin::brightnessUp(keyState);
    break;
   
  case MACRO_FCDOWN:
    kaleidoscope::LEDFunctionalColor::FCPlugin::brightnessDown(keyState);
    break;
  }
  return MACRO_NONE;
}

Questions or Comments?

If you have any questions or comments please let me know.

I want to give my thanks to @noseglasses for being tremendously helpful to me in making this plugin as efficient and user-friendly as it is. My early attempts at this consumed almost all the memory on the keyboard and this version should take about 4KB, which is a massive improvement.

Dependencies

6 Likes

I made an update today changes the constructors for FunctionalColors. The documentation above is now updated to reflect this.

To improve memory usage, I’ve reduced the prominence of the feature that allows selecting built-in themes by specifying an ID in the constructor. If you want to change it, it is highly recommended that do so using the FC_SET_THEME() macro in the setup section.

You may still add a theme by specifying an int after the brightness (or using one of the keywords, now in the LEDFunctionalColors scope: Base, Default, Fruit, Mono, Duo, Princess, Sea, Flower, Kids, RedWhiteBlue

Sorry for breaking this if anyone has already been using it!

Update:
I added several more themes and fixed a major bug tonight preventing colorList exceptions from working under some circumstances. Please let me know if you notice any other issues.

Hey, I love this plugin! This is exactly the kind of thing I wanted to be able to do once I got my Model01.

One question. Will there be support, in the future, for coloring shifted/capslocked/numlocked keys differently? Mind if I add this to the issue tracker? If I’m holding shift, I want my alphanumerics to change to a highly visible color. With caps-lock, just the alphas. If (OS) numlock is off, I want to be able to color the numpad 0-9 keys differently, to quickly and obviously indicate that I’m not typing what I think I’m typing. I think there might be a standalone plugin just for this functionality. I’ll have to look, though. I’m worried about whether they’d be compatible, in any case.

Ideally, it’d be great if shifted/modified/whatever keys are independently mappable from unmodified keys. I see that shifted Key_9 & Key_0 count as punctuation, which is awesome, so I’d hope that wouldn’t break. It might be nice, also, for ctrl + a/x/c/v to have a different color, in reference to their use as keyboard shortcuts. This is less important, though.

For anyone interested, here’s how I’ve got my color theme set up currently. I used some consts at the top to make sure I stayed on-palette, with a small palette, and to allow easier tweaking.

It’s an absolutely terrific way of being able to tell what layout I’m in at a glance (dvorak vs qwerty).

Hi Tim, I’m glad you like FunctionalColor. I worked pretty hard to make it useful while being very efficient.

Unfortunately, I don’t think that FunctionalColor can do exactly what you’re asking – at least not without substantial modification, which I’d call out of scope. The reason for this is that FC works by setting a color for Key structs, and when you hold the shift key, the other keys are still sending the same key scan code - the “A” key still sends the same scan code whether you are holding shift or not, so the keyboard firmware doesn’t really see any difference, and thus FunctionalColor can’t change the colour based on that.

That said, it would be possible with a very odd configuration to do something like what you’re asking, I think. You’d have to make a new function layer, and then the shift keys activate that layer instead of acting like normal shift keys, then you make a bunch of new key structs that are actually shift+alpha keys… in this way, you could use the COLOR_LIST macros in FunctionalColor to color these keys differently. Any key struct can be coloured with FunctionalColor, even things like Macros and custom things, but you’ll likely have to use the COLOR_LIST macros to do this since the ColorMap structs don’t have anything defined for keys that didn’t exist before - ColorMaps are really just a shortcut for defining groups of keys, but excepting through the creative use of defaults, you can’t necessarily color every possible key combination that way. With the COLOR_LIST macros, you can.

Unless you really want this, I wouldn’t recommend it, however, as it would probably cause some issues with using modifiers for shortcut keys or games or other things… it’s just my first thought.

I hope this info helps - sorry I couldn’t be more helpful!

All right. Thanks anyway.

…but one way or another, I’ll make it work eventually. The function layer solution thing is too hacky for me, I might make a plugin myself, or fork your project. If my solution is too resource intensive, I’ll have to put my code between #ifdef guards, or not even do a pull request, or just code it correctly so it does nothing to the compilation if it’s not used.

I’ve had a very clear idea of exactly how I’ve wanted this keyboard to work for quite some time, and your plugin is a huge part of that, so thanks again.

@jdlien, I am not sure if it is that complicated. Maybe I am overlooking something here, but I think it could be done with the current FunctionalColor plugin.

Mind you, color selection is eventually just a mapping of a keycode to a cRGB color. This is done via color selection functions (named FC_COLOR_LIST(<chosen name>) that can rely on a context (e.g. shift and caps lock state) to do advanced selection. Like in the example below, color selection functions can be arbitrarily nested.

Assuming that there are different color maps ShiftColorMap, CapsLockColorMap and NonShiftColorMap, one could switch between those by checking shift and caps lock state.

// In the sketch

// define ShiftColorMap, CapsLockColorMap, NonShiftColorMap
struct ShiftColorMap : public colorMap {
...
};
...

cRGB FC_COLOR_LIST(myColorList)(const Key &k, bool &skip, bool &none) {
   if(/* Check capslock state (see below) */) {
      return kaleidoscope::LEDFunctionalColor::groupColorLookup<CapsLockColorMap>(k, skip, none);
   }
   else if(hid::isModifierKeyActive(Key_LeftShift)) {
      return kaleidoscope::LEDFunctionalColor::groupColorLookup<ShiftColorMap>(k, skip, none);
   }
   return kaleidoscope::LEDFunctionalColor::groupColorLookup<NonShiftColorMap>(k, skip, none);
}

FCPlugin funColor(FC_COLOR_LIST(myColorList));

@atimholt, I am not sure if there is a perfectly safe way to determine whether caps lock is active, but you can have a look at the CapsLock plugin to see how you could keep track of caps lock state. By writing a small plugin that does only the caps lock tracking and exports caps lock state as a global variable, you can let the color list selection function FC_COLOR_LIST(myColorList) have access to it.

2 Likes

Okay, this sounds like an interesting approach… Maybe I’ll poke at it a bit when I get some time (although I don’t know when this will be – I work full time and am also working on my CompSci degree.)

Since I haven’t dug into Kaleidoscope in some time, @noseglasses was nice enough to update FunctionalColors for Kaleidoscope API v2. I have merged his pull request so it should be working. Thank you so much for doing that!

I will update this thread later with any significant changes to usage or required documentation changes. Please leave a comment if anything doesn’t seem to be working.

2 Likes

Hi everyone! It has been about a year since I have put any real effort into maintaining FunctionalColors, but I am happy to report that I have finally updated everything to work with the latest version of Kaleidoscope. The documentation on GitHub is correct.

Please ignore the documentation in the first post of this thread as there have been major changes since that was last updated and it will not run with the latest version of Kaleidoscope.

Again, many thanks to @noseglasses for assisting me in getting this running again.

Please let me know if anyone discovers any bugs or issues with the documentation and don’t hesitate to open a bug on GitHub or comment here.

1 Like