aboutsummaryrefslogtreecommitdiff
// -*- mode: c++ -*-
#pragma once

/*
  this class implements a color theme editor as a LED mode

  when this mode is active, nothing gets sent to the host

  the Fn keys switch into editing mode, with that half housing the controls

  the controls are:

  * 8 keys for the 8 colors we currently use
  * 3 rows of 4 keys to change the active color in the HSV space

  pressing a key on the non-control half of the keyboard assigns the
  current color to that key
 */

#include <Kaleidoscope-LEDControl.h>
#include <kaleidoscope/plugin/LEDControl/LEDUtils.h>
#include <Kaleidoscope-FocusSerial.h>

class ColorPicker : public kaleidoscope::plugin::LEDMode {
public:
  ColorPicker(void) : current_index(0), which_half(BOTH_HALVES),
                      colors{}, map{},
                      hsv_colors{
                                 { .h=0, .s=150, .v=128 },
                                 { .h=32, .s=150, .v=128 },
                                 { .h=64, .s=150, .v=128 },
                                 { .h=96, .s=150, .v=128 },
                                 { .h=128, .s=150, .v=128 },
                                 { .h=160, .s=150, .v=128 },
                                 { .h=192, .s=150, .v=128 },
                                 { .h=224, .s=150, .v=128 },
                      }
  {
    for (uint8_t i=0;i<16;++i) {
      hsv& col = hsv_colors[i];
      colors[i] = hsvToRgb(col.h,col.s,col.v);
    }
  }

  kaleidoscope::EventHandlerResult onKeyswitchEvent(KeyEvent &event) {
    if (!Kaleidoscope.has_leds || !is_active())
      return kaleidoscope::EventHandlerResult::OK;

    // only care at press time
    if (!keyToggledOn(event.state))
      return kaleidoscope::EventHandlerResult::EVENT_CONSUMED;

    // uh?
    if (event.addr.row() >= kaleidoscope_internal::device.matrix_rows || event.addr.col() >= kaleidoscope_internal::device.matrix_columns)
      return kaleidoscope::EventHandlerResult::EVENT_CONSUMED;

    // pressing a Fn makes that half the "editing" half; pressing it
    // again disables editing

    picker_key key = whichKey(event.addr.row(),event.addr.col());
    switch (key) {
    case OFF:
      break;
    case LED_KEY:
      return kaleidoscope::EventHandlerResult::OK; // let other plugins deal with this
    case SHOW:
      map[event.addr.row()][event.addr.col()] = current_index; break;
    case SWITCH_LEFT:
      which_half = which_half == RIGHT_HALF ? BOTH_HALVES : RIGHT_HALF; break;
    case SWITCH_RIGHT:
      which_half = which_half == LEFT_HALF ? BOTH_HALVES : LEFT_HALF; break;
    case COLOR_1 ... COLOR_8:
      current_index = key - COLOR_1; break;
    case HUE_M10:
      updateCurrentColor(-10,0,0); break;
    case HUE_M1:
      updateCurrentColor(-1,0,0); break;
    case HUE_P1:
      updateCurrentColor(+1,0,0); break;
    case HUE_P10:
      updateCurrentColor(+10,0,0); break;
    case SAT_M10:
      updateCurrentColor(0,-10,0); break;
    case SAT_M1:
      updateCurrentColor(0,-1,0); break;
    case SAT_P1:
      updateCurrentColor(0,+1,0); break;
    case SAT_P10:
      updateCurrentColor(0,+10,0); break;
    case VAL_M10:
      updateCurrentColor(0,0,-10); break;
    case VAL_M1:
      updateCurrentColor(0,0,-1); break;
    case VAL_P1:
      updateCurrentColor(0,0,+1); break;
    case VAL_P10:
      updateCurrentColor(0,0,+10); break;
    };

    return kaleidoscope::EventHandlerResult::EVENT_CONSUMED;
  }

  kaleidoscope::EventHandlerResult onFocusEvent(const char *command) {
    if (Focus.handleHelp(command, PSTR("color-picker.dump")))
      return kaleidoscope::EventHandlerResult::OK;

    if (strncmp_P(command, PSTR("color-picker."), 13) != 0)
      return kaleidoscope::EventHandlerResult::OK;

    if (strcmp_P(command + 13, PSTR("dump")) != 0)
      return kaleidoscope::EventHandlerResult::OK;

    for (uint8_t i=0;i<16;++i) {
      Focus.send(F("color"),i,colors[i],Focus.NEWLINE);
    }
    Focus.send(F("map\n"));
    for (uint8_t r=0;r<kaleidoscope_internal::device.matrix_rows;++r) {
      for (uint8_t c=0;c<kaleidoscope_internal::device.matrix_columns;++c) {
        Focus.send(map[r][c]);
      }
      Focus.send(Focus.NEWLINE);
    }

    return kaleidoscope::EventHandlerResult::EVENT_CONSUMED;
  }

protected:
  void update(void) {
    for (KeyAddr key_addr : KeyAddr::all()) {
      LEDControl.setCrgbAt(key_addr, getColor(key_addr.row(),key_addr.col()     ));
    }
  }
  void refreshAt(KeyAddr key_addr) final {
    LEDControl.setCrgbAt(key_addr, getColor(key_addr.row(),key_addr.col()));
  }

private:
  uint8_t current_index;
  enum { BOTH_HALVES, LEFT_HALF, RIGHT_HALF } which_half;
  cRGB colors[16];
  uint8_t map[kaleidoscope_internal::device.matrix_rows][kaleidoscope_internal::device.matrix_columns];
  struct hsv { uint8_t h; uint8_t s; uint8_t v; }
    hsv_colors[16];

  static inline uint8_t add_and_clamp(uint8_t value, int8_t delta) {
    int16_t result = ((int16_t)value) + delta;
    if (result < 0) return 0;
    if (result > 255) return 255;
    return result;
  }
  static inline uint8_t add_and_wrap(uint8_t value, int8_t delta) {
    int16_t result = ((int16_t)value) + delta;
    if (result < 0) return result+255;
    if (result > 255) return result-255;
    return result;
  }

  void updateCurrentColor(int8_t delta_h, int8_t delta_s, int8_t delta_v) {
    hsv& color = hsv_colors[current_index];
    color.h = add_and_wrap(color.h,delta_h);
    color.s = add_and_clamp(color.s,delta_s);
    color.v = add_and_clamp(color.v,delta_v);

    colors[current_index] = hsvToRgb(color.h,color.s,color.v);
  }
  
  bool is_active() {
    return LEDControl.get_mode() == this;
  }

  typedef enum { OFF, LED_KEY, SHOW,
                 SWITCH_LEFT, SWITCH_RIGHT,
                 COLOR_1, COLOR_2, COLOR_3, COLOR_4,
                 COLOR_5, COLOR_6, COLOR_7, COLOR_8,
                 HUE_M10, HUE_M1, HUE_P1, HUE_P10,
                 SAT_M10, SAT_M1, SAT_P1, SAT_P10,
                 VAL_M10, VAL_M1, VAL_P1, VAL_P10 }
    picker_key;

  picker_key whichKey(byte row, byte col) {
    if (row == 3 && col == 6) {
      return SWITCH_LEFT;
    }
    else if (row == 3 && col == 9) {
      return SWITCH_RIGHT;
    }
    else if (row == 0 && col == 6) {
      return LED_KEY;
    }

    // non-editing half? show it
    if (which_half == BOTH_HALVES ||
        (col < 8 && which_half == LEFT_HALF) ||
        (col >= 8 && which_half == RIGHT_HALF)) {
      return SHOW;
    }

    // pretend we're always showing the right half, by changing col if we're not
    if (which_half == LEFT_HALF) {
      switch (col) {
      case 8: col=6; break;
      case 9: col=7; break;
      case 10: col=2; break;
      case 11: col=3; break;
      case 12: col=4; break;
      case 13: col=5; break;
      case 14: col=0; break;
      case 15: col=1; break;
      }
    }

    switch (col) {
    case 0:
      switch (row) {
      case 0:
        return COLOR_1;
      case 1:
        return COLOR_2;
      case 2:
        return COLOR_3;
      case 3:
        return COLOR_4;
      default:
        return OFF;
      };
    case 1:
      switch (row) {
      case 0:
        return COLOR_5;
      case 1:
        return COLOR_6;
      case 2:
        return COLOR_7;
      case 3:
        return COLOR_8;
      default:
        return OFF;
      };
    case 2:
      switch (row) {
      case 1:
        return HUE_M10;
      case 2:
        return SAT_M10;
      case 3:
        return VAL_M10;
      default:
        return OFF;
      };
    case 3:
      switch (row) {
      case 1:
        return HUE_M1;
      case 2:
        return SAT_M1;
      case 3:
        return VAL_M1;
      default:
        return OFF;
      };
    case 4:
      switch (row) {
      case 1:
        return HUE_P1;
      case 2:
        return SAT_P1;
      case 3:
        return VAL_P1;
      default:
        return OFF;
      };
    case 5:
      switch (row) {
      case 1:
        return HUE_P10;
      case 2:
        return SAT_P10;
      case 3:
        return VAL_P10;
      default:
        return OFF;
      };
    default:
      return OFF;
    };

    // won't be reached
    return SHOW;
  }
  
  cRGB getColor(byte row, byte col) {
    picker_key key = whichKey(row,col);
    switch (key) {
    case SHOW:
      return colors[map[row][col]];
    case OFF:
    case LED_KEY:
    case SWITCH_LEFT:
    case SWITCH_RIGHT:
      return CRGB(0,0,0);
    case COLOR_1...COLOR_8:
      return colors[key - COLOR_1];

    case HUE_M10:
      return hsvToRgb(add_and_wrap(hsv_colors[current_index].h,-10),255,128);
    case HUE_M1:
      return hsvToRgb(add_and_wrap(hsv_colors[current_index].h,-1),255,128);
    case HUE_P1:
      return hsvToRgb(add_and_wrap(hsv_colors[current_index].h,+1),255,128);
    case HUE_P10:
      return hsvToRgb(add_and_wrap(hsv_colors[current_index].h,+10),255,128);
    case SAT_M10:
      return hsvToRgb(hsv_colors[current_index].h,50,128);
    case SAT_M1:
      return hsvToRgb(hsv_colors[current_index].h,100,128);
    case SAT_P1:
      return hsvToRgb(hsv_colors[current_index].h,150,128);
    case SAT_P10:
      return hsvToRgb(hsv_colors[current_index].h,200,128);

    case VAL_M10:
      return hsvToRgb(hsv_colors[current_index].h,128,50);
    case VAL_M1:
      return hsvToRgb(hsv_colors[current_index].h,128,100);
    case VAL_P1:
      return hsvToRgb(hsv_colors[current_index].h,128,150);
    case VAL_P10:
      return hsvToRgb(hsv_colors[current_index].h,128,200);
    };

    // won't be reached
    return colors[map[row][col]];
  }
};

ColorPicker theColorPicker;