diff --git a/src/Kaleidoscope-LEDControl.h b/src/Kaleidoscope-LEDControl.h index 3564aa5a..6a1ea252 100644 --- a/src/Kaleidoscope-LEDControl.h +++ b/src/Kaleidoscope-LEDControl.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include #include diff --git a/src/kaleidoscope/Kaleidoscope.h b/src/kaleidoscope/Kaleidoscope.h index d77a6020..20948d42 100644 --- a/src/kaleidoscope/Kaleidoscope.h +++ b/src/kaleidoscope/Kaleidoscope.h @@ -49,6 +49,7 @@ extern HARDWARE_IMPLEMENTATION KeyboardHardware; #include "kaleidoscope/layers.h" #include "kaleidoscope/macro_map.h" #include "kaleidoscope_internal/event_dispatch.h" +#include "kaleidoscope_internal/LEDModeManager.h" #include "kaleidoscope/macro_helpers.h" #include "kaleidoscope/plugin.h" diff --git a/src/kaleidoscope/plugin/Colormap.cpp b/src/kaleidoscope/plugin/Colormap.cpp index ac7b3144..8c000908 100644 --- a/src/kaleidoscope/plugin/Colormap.cpp +++ b/src/kaleidoscope/plugin/Colormap.cpp @@ -38,23 +38,23 @@ void ColormapEffect::max_layers(uint8_t max_) { map_base_ = ::LEDPaletteTheme.reserveThemes(max_layers_); } -void ColormapEffect::onActivate(void) { +void ColormapEffect::TransientLEDMode::onActivate(void) { if (!Kaleidoscope.has_leds) return; - top_layer_ = Layer.top(); - if (top_layer_ <= max_layers_) - ::LEDPaletteTheme.updateHandler(map_base_, top_layer_); + parent_->top_layer_ = Layer.top(); + if (parent_->top_layer_ <= parent_->max_layers_) + ::LEDPaletteTheme.updateHandler(parent_->map_base_, parent_->top_layer_); } -void ColormapEffect::refreshAt(byte row, byte col) { - if (top_layer_ <= max_layers_) - ::LEDPaletteTheme.refreshAt(map_base_, top_layer_, row, col); +void ColormapEffect::TransientLEDMode::refreshAt(byte row, byte col) { + if (parent_->top_layer_ <= parent_->max_layers_) + ::LEDPaletteTheme.refreshAt(parent_->map_base_, parent_->top_layer_, row, col); } EventHandlerResult ColormapEffect::onLayerChange() { - if (::LEDControl.get_mode() == this) - onActivate(); + if (::LEDControl.get_mode_index() == led_mode_id_) + ::LEDControl.get_mode()->onActivate(); return EventHandlerResult::OK; } diff --git a/src/kaleidoscope/plugin/Colormap.h b/src/kaleidoscope/plugin/Colormap.h index 7837a8c3..eedc2db0 100644 --- a/src/kaleidoscope/plugin/Colormap.h +++ b/src/kaleidoscope/plugin/Colormap.h @@ -22,7 +22,9 @@ namespace kaleidoscope { namespace plugin { -class ColormapEffect : public LEDMode { +class ColormapEffect : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: ColormapEffect(void) {} @@ -31,9 +33,28 @@ class ColormapEffect : public LEDMode { EventHandlerResult onLayerChange(); EventHandlerResult onFocusEvent(const char *command); - protected: - void onActivate(void) final; - void refreshAt(byte row, byte col) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const ColormapEffect *parent) : parent_(parent) {} + + protected: + + friend class ColormapEffect; + + virtual void onActivate(void) final; + virtual void refreshAt(byte row, byte col) final; + + private: + + const ColormapEffect *parent_; + }; private: static uint8_t top_layer_; diff --git a/src/kaleidoscope/plugin/FingerPainter.cpp b/src/kaleidoscope/plugin/FingerPainter.cpp index f52c9ed0..622d131d 100644 --- a/src/kaleidoscope/plugin/FingerPainter.cpp +++ b/src/kaleidoscope/plugin/FingerPainter.cpp @@ -30,7 +30,6 @@ bool FingerPainter::edit_mode_; EventHandlerResult FingerPainter::onSetup() { color_base_ = ::LEDPaletteTheme.reserveThemes(1); - ::LEDControl.mode_add(this); return EventHandlerResult::OK; } diff --git a/src/kaleidoscope/plugin/FingerPainter.h b/src/kaleidoscope/plugin/FingerPainter.h index e13e58b3..add9f175 100644 --- a/src/kaleidoscope/plugin/FingerPainter.h +++ b/src/kaleidoscope/plugin/FingerPainter.h @@ -21,6 +21,11 @@ namespace kaleidoscope { namespace plugin { + +// This class is implemented as a persistent LED mode +// as there is no benefit of transforming it into a dynamic +// LED mode in terms of PROGMEM or RAM. +// class FingerPainter : public LEDMode { public: FingerPainter(void) {} diff --git a/src/kaleidoscope/plugin/HardwareTestMode.cpp b/src/kaleidoscope/plugin/HardwareTestMode.cpp index 04aece10..d36f0f9b 100644 --- a/src/kaleidoscope/plugin/HardwareTestMode.cpp +++ b/src/kaleidoscope/plugin/HardwareTestMode.cpp @@ -56,10 +56,18 @@ void HardwareTestMode::testLeds(void) { setLeds(blue); setLeds(green); setLeds(red); + + // This works under the assumption that LEDRainbowEffect + // has been registered with KALEIDOSCOPE_INIT_PLUGINS in + // the sketch. Otherwise LEDRainbowEffect would not be + // known to LEDControl. + // + ::LEDControl.activate(&::LEDRainbowEffect); + // rainbow for 10 seconds ::LEDRainbowEffect.update_delay(5); for (auto i = 0; i < 300; i++) { - ::LEDRainbowEffect.update(); + ::LEDControl.update(); ::LEDControl.syncLeds(); } } diff --git a/src/kaleidoscope/plugin/Heatmap.cpp b/src/kaleidoscope/plugin/Heatmap.cpp index 49096efe..b2a4dcff 100644 --- a/src/kaleidoscope/plugin/Heatmap.cpp +++ b/src/kaleidoscope/plugin/Heatmap.cpp @@ -17,16 +17,11 @@ #include #include +#include namespace kaleidoscope { namespace plugin { -// store the number of times each key has been strock -uint16_t Heatmap::heatmap_[ROWS][COLS]; -// max of heatmap_ (we divide by it so we start at 1) -uint16_t Heatmap::highest_ = 1; -// next heatmap computation time -uint32_t Heatmap::next_heatmap_comp_time_ = 0; // in the cRGB struct the order is blue, green, red (It should be called cBGR…) // default heat_colors black green yellow red static const cRGB heat_colors_default_[] PROGMEM = {{0, 0, 0}, {25, 255, 25}, {25, 255, 255}, {25, 25, 255}}; @@ -37,7 +32,17 @@ uint8_t Heatmap::heat_colors_length = 4; // number of millisecond to wait between each heatmap computation uint16_t Heatmap::update_delay = 1000; -cRGB Heatmap::computeColor(float v) { +Heatmap::TransientLEDMode::TransientLEDMode(const Heatmap *parent) + : parent_(parent), + // store the number of times each key has been strock + heatmap_{}, + // max of heatmap_ (we divide by it so we start at 1) + highest_(1), + // next heatmap computation time + next_heatmap_comp_time_(0) +{} + +cRGB Heatmap::TransientLEDMode::computeColor(float v) { // compute the color corresponding to a value between 0 and 1 /* @@ -95,7 +100,7 @@ cRGB Heatmap::computeColor(float v) { return {b, g, r}; } -void Heatmap::shiftStats(void) { +void Heatmap::TransientLEDMode::shiftStats(void) { // this method is called when: // 1. a value in heatmap_ reach INT8_MAX // 2. highest_ reach heat_colors_length*512 (see Heatmap::loopHook) @@ -112,6 +117,15 @@ void Heatmap::shiftStats(void) { } void Heatmap::resetMap(void) { + + if (::LEDControl.get_mode_index() != led_mode_id_) + return; + + ::LEDControl.get_mode()->resetMap(); +} + +void Heatmap::TransientLEDMode::resetMap() { + // this method can be used as a way to work around an existing bug with a single key // getting special attention or if the user just wants a button to reset the map for (uint8_t r = 0; r < ROWS; r++) { @@ -138,6 +152,15 @@ EventHandlerResult Heatmap::onKeyswitchEvent(Key &mapped_key, byte row, byte col if (!keyToggledOn(key_state)) return EventHandlerResult::OK; + // if the LED mode is not current, skip it + if (::LEDControl.get_mode_index() != led_mode_id_) + return EventHandlerResult::OK; + + return ::LEDControl.get_mode() + ->onKeyswitchEvent(mapped_key, row, col, key_state); +} + +EventHandlerResult Heatmap::TransientLEDMode::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) { // increment the heatmap_ value related to the key heatmap_[row][col]++; @@ -160,6 +183,13 @@ EventHandlerResult Heatmap::beforeEachCycle() { if (!Kaleidoscope.has_leds) return EventHandlerResult::OK; + if (::LEDControl.get_mode_index() != led_mode_id_) + return EventHandlerResult::OK; + + return ::LEDControl.get_mode()->beforeEachCycle(); +} + +EventHandlerResult Heatmap::TransientLEDMode::beforeEachCycle() { // this methode is called frequently by Kaleidoscope // even if the module isn't activated @@ -175,7 +205,7 @@ EventHandlerResult Heatmap::beforeEachCycle() { return EventHandlerResult::OK; } -void Heatmap::update(void) { +void Heatmap::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; diff --git a/src/kaleidoscope/plugin/Heatmap.h b/src/kaleidoscope/plugin/Heatmap.h index cb15da61..82cd61f0 100644 --- a/src/kaleidoscope/plugin/Heatmap.h +++ b/src/kaleidoscope/plugin/Heatmap.h @@ -22,7 +22,9 @@ namespace kaleidoscope { namespace plugin { -class Heatmap : public LEDMode { +class Heatmap : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: Heatmap(void) {} @@ -34,16 +36,38 @@ class Heatmap : public LEDMode { EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state); EventHandlerResult beforeEachCycle(); - protected: - void update(void) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - private: - static uint16_t heatmap_[ROWS][COLS]; - static uint16_t highest_; - static uint32_t next_heatmap_comp_time_; + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const Heatmap *parent); - static void shiftStats(void); - static cRGB computeColor(float v); + void resetMap(); + EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state); + EventHandlerResult beforeEachCycle(); + + protected: + + virtual void update() final; + + private: + + const Heatmap *parent_; + + uint16_t heatmap_[ROWS][COLS]; + uint16_t highest_; + uint32_t next_heatmap_comp_time_; + + void shiftStats(void); + cRGB computeColor(float v); + + friend class Heatmap; + }; }; } } diff --git a/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp b/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp index ae7fd618..cc424987 100644 --- a/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp +++ b/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp @@ -20,26 +20,31 @@ namespace kaleidoscope { namespace plugin { -cRGB LEDActiveLayerColorEffect::active_color_; const cRGB *LEDActiveLayerColorEffect::colormap_; +LEDActiveLayerColorEffect::TransientLEDMode::TransientLEDMode( + const LEDActiveLayerColorEffect *parent) + : parent_(parent), + active_color_{0, 0, 0} +{} + void LEDActiveLayerColorEffect::setColormap(const cRGB colormap[]) { colormap_ = colormap; } -cRGB LEDActiveLayerColorEffect::getActiveColor() { +cRGB LEDActiveLayerColorEffect::TransientLEDMode::getActiveColor() { cRGB color; uint8_t top_layer = ::Layer.top(); - color.r = pgm_read_byte(&(colormap_[top_layer].r)); - color.g = pgm_read_byte(&(colormap_[top_layer].g)); - color.b = pgm_read_byte(&(colormap_[top_layer].b)); + color.r = pgm_read_byte(&(parent_->colormap_[top_layer].r)); + color.g = pgm_read_byte(&(parent_->colormap_[top_layer].g)); + color.b = pgm_read_byte(&(parent_->colormap_[top_layer].b)); return color; } -void LEDActiveLayerColorEffect::onActivate(void) { +void LEDActiveLayerColorEffect::TransientLEDMode::onActivate(void) { if (!Kaleidoscope.has_leds) return; @@ -47,13 +52,14 @@ void LEDActiveLayerColorEffect::onActivate(void) { ::LEDControl.set_all_leds_to(active_color_); } -void LEDActiveLayerColorEffect::refreshAt(byte row, byte col) { +void LEDActiveLayerColorEffect::TransientLEDMode::refreshAt(byte row, byte col) { ::LEDControl.setCrgbAt(row, col, active_color_); } EventHandlerResult LEDActiveLayerColorEffect::onLayerChange() { - if (::LEDControl.get_mode() == this) - onActivate(); + if (::LEDControl.get_mode_index() == led_mode_id_) + ::LEDControl.get_mode()->onActivate(); + return EventHandlerResult::OK; } diff --git a/src/kaleidoscope/plugin/LED-ActiveLayerColor.h b/src/kaleidoscope/plugin/LED-ActiveLayerColor.h index 611b6b19..2030e0ed 100644 --- a/src/kaleidoscope/plugin/LED-ActiveLayerColor.h +++ b/src/kaleidoscope/plugin/LED-ActiveLayerColor.h @@ -21,22 +21,45 @@ namespace kaleidoscope { namespace plugin { -class LEDActiveLayerColorEffect : public LEDMode { +class LEDActiveLayerColorEffect : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: LEDActiveLayerColorEffect(void) {} EventHandlerResult onLayerChange(); void setColormap(const cRGB colormap[]); - protected: - void onActivate(void) final; - void refreshAt(byte row, byte col) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDActiveLayerColorEffect *parent); + + protected: + + virtual void onActivate(void) final; + virtual void refreshAt(byte row, byte col) final; + + private: + + const LEDActiveLayerColorEffect *parent_; + + cRGB active_color_; + + cRGB getActiveColor(); + + friend class LEDActiveLayerColorEffect; + }; private: - static const cRGB *colormap_; - static cRGB active_color_; - static cRGB getActiveColor(); + static const cRGB *colormap_; }; } } diff --git a/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp b/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp index 692a7151..6b6d428f 100644 --- a/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp +++ b/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp @@ -21,10 +21,15 @@ namespace kaleidoscope { namespace plugin { uint16_t AlphaSquareEffect::length = 1000; -uint32_t AlphaSquareEffect::end_time_left_, AlphaSquareEffect::end_time_right_; -Key AlphaSquareEffect::last_key_left_, AlphaSquareEffect::last_key_right_; -void AlphaSquareEffect::update(void) { +AlphaSquareEffect::TransientLEDMode::TransientLEDMode(AlphaSquareEffect */*parent*/) + : end_time_left_(0), + end_time_right_(0), + last_key_left_(Key{}), + last_key_right_(Key{}) +{} + +void AlphaSquareEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; @@ -38,7 +43,7 @@ void AlphaSquareEffect::update(void) { } } -void AlphaSquareEffect::refreshAt(byte row, byte col) { +void AlphaSquareEffect::TransientLEDMode::refreshAt(byte row, byte col) { bool timed_out; uint8_t display_col = 2; Key key = last_key_left_; @@ -59,7 +64,7 @@ EventHandlerResult AlphaSquareEffect::onKeyswitchEvent(Key &mappedKey, byte row, if (!Kaleidoscope.has_leds) return EventHandlerResult::OK; - if (::LEDControl.get_mode() != &::AlphaSquareEffect) + if (::LEDControl.get_mode_index() != led_mode_id_) return EventHandlerResult::OK; if (keyState & INJECTED) @@ -72,15 +77,17 @@ EventHandlerResult AlphaSquareEffect::onKeyswitchEvent(Key &mappedKey, byte row, return EventHandlerResult::OK; uint8_t display_col = 2; - Key prev_key = last_key_left_; + auto this_led_mode = ::LEDControl.get_mode(); + + Key prev_key = this_led_mode->last_key_left_; if (col < COLS / 2) { - last_key_left_ = mappedKey; - end_time_left_ = millis() + length; + this_led_mode->last_key_left_ = mappedKey; + this_led_mode->end_time_left_ = millis() + length; } else { - prev_key = last_key_right_; - last_key_right_ = mappedKey; - end_time_right_ = millis() + length; + prev_key = this_led_mode->last_key_right_; + this_led_mode->last_key_right_ = mappedKey; + this_led_mode->end_time_right_ = millis() + length; display_col = 10; } diff --git a/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h b/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h index 43579e6b..57b68645 100644 --- a/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h +++ b/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h @@ -22,7 +22,9 @@ namespace kaleidoscope { namespace plugin { -class AlphaSquareEffect : public LEDMode { +class AlphaSquareEffect : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: AlphaSquareEffect(void) {} @@ -30,13 +32,22 @@ class AlphaSquareEffect : public LEDMode { EventHandlerResult onKeyswitchEvent(Key &mappedKey, byte row, byte col, uint8_t keyState); - protected: - void update(void) final; - void refreshAt(byte row, byte col) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + TransientLEDMode(AlphaSquareEffect *parent); - private: - static uint32_t end_time_left_, end_time_right_; - static Key last_key_left_, last_key_right_; + protected: + void update(void) final; + void refreshAt(byte row, byte col) final; + + private: + uint32_t end_time_left_, end_time_right_; + Key last_key_left_, last_key_right_; + + friend class AlphaSquareEffect; + }; }; } } diff --git a/src/kaleidoscope/plugin/LED-Stalker.cpp b/src/kaleidoscope/plugin/LED-Stalker.cpp index fb1c83c9..d7ea6185 100644 --- a/src/kaleidoscope/plugin/LED-Stalker.cpp +++ b/src/kaleidoscope/plugin/LED-Stalker.cpp @@ -16,21 +16,22 @@ */ #include +#include namespace kaleidoscope { namespace plugin { -uint8_t StalkerEffect::map_[ROWS][COLS]; StalkerEffect::ColorComputer *StalkerEffect::variant; uint16_t StalkerEffect::step_length = 50; -uint16_t StalkerEffect::step_start_time_; cRGB StalkerEffect::inactive_color = (cRGB) { 0, 0, 0 }; -void StalkerEffect::onActivate(void) { - memset(map_, 0, sizeof(map_)); -} +StalkerEffect::TransientLEDMode::TransientLEDMode(const StalkerEffect *parent) + : parent_(parent), + step_start_time_(0), + map_{} +{} EventHandlerResult StalkerEffect::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState) { if (!Kaleidoscope.has_leds) @@ -39,35 +40,38 @@ EventHandlerResult StalkerEffect::onKeyswitchEvent(Key &mapped_key, byte row, by if (row >= ROWS || col >= COLS) return EventHandlerResult::OK; + if (::LEDControl.get_mode_index() != led_mode_id_) + return EventHandlerResult::OK; + if (keyIsPressed(keyState)) { - map_[row][col] = 0xff; + ::LEDControl.get_mode()->map_[row][col] = 0xff; } return EventHandlerResult::OK; } -void StalkerEffect::update(void) { +void StalkerEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; - if (!variant) + if (!parent_->variant) return; uint16_t elapsed = Kaleidoscope.millisAtCycleStart() - step_start_time_; - if (elapsed < step_length) + if (elapsed < parent_->step_length) return; for (byte r = 0; r < ROWS; r++) { for (byte c = 0; c < COLS; c++) { uint8_t step = map_[r][c]; if (step) { - ::LEDControl.setCrgbAt(r, c, variant->compute(&step)); + ::LEDControl.setCrgbAt(r, c, parent_->variant->compute(&step)); } map_[r][c] = step; if (!map_[r][c]) - ::LEDControl.setCrgbAt(r, c, inactive_color); + ::LEDControl.setCrgbAt(r, c, parent_->inactive_color); } } diff --git a/src/kaleidoscope/plugin/LED-Stalker.h b/src/kaleidoscope/plugin/LED-Stalker.h index 80e893e0..dc7ca1a3 100644 --- a/src/kaleidoscope/plugin/LED-Stalker.h +++ b/src/kaleidoscope/plugin/LED-Stalker.h @@ -24,7 +24,9 @@ namespace kaleidoscope { namespace plugin { -class StalkerEffect : public LEDMode { +class StalkerEffect : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: class ColorComputer { public: @@ -39,13 +41,30 @@ class StalkerEffect : public LEDMode { EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState); - protected: - void onActivate(void) final; - void update(void) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - private: - static uint16_t step_start_time_; - static uint8_t map_[ROWS][COLS]; + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const StalkerEffect *parent); + + protected: + + virtual void update() final; + + private: + + const StalkerEffect *parent_; + + uint16_t step_start_time_; + uint8_t map_[ROWS][COLS]; + + friend class StalkerEffect; + }; }; namespace stalker { diff --git a/src/kaleidoscope/plugin/LED-Wavepool.cpp b/src/kaleidoscope/plugin/LED-Wavepool.cpp index d0a5ee37..767b4ef6 100644 --- a/src/kaleidoscope/plugin/LED-Wavepool.cpp +++ b/src/kaleidoscope/plugin/LED-Wavepool.cpp @@ -27,52 +27,64 @@ namespace plugin { #define MS_PER_FRAME 40 // 40 = 25 fps #define FRAMES_PER_DROP 120 // max time between raindrops during idle animation -int8_t WavepoolEffect::surface[2][WP_WID * WP_HGT]; -uint8_t WavepoolEffect::page = 0; -uint8_t WavepoolEffect::frames_since_event = 0; uint16_t WavepoolEffect::idle_timeout = 5000; // 5 seconds int16_t WavepoolEffect::ripple_hue = WavepoolEffect::rainbow_hue; // automatic hue // map native keyboard coordinates (16x4) into geometric space (14x5) -PROGMEM const uint8_t WavepoolEffect::rc2pos[ROWS * COLS] = { +PROGMEM const uint8_t WavepoolEffect::TransientLEDMode::rc2pos[ROWS * COLS] = { 0, 1, 2, 3, 4, 5, 6, 59, 66, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 34, 60, 65, 35, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 48, 61, 64, 49, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 58, 62, 63, 67, 50, 51, 52, 53, 54, 55, }; +WavepoolEffect::TransientLEDMode::TransientLEDMode(const WavepoolEffect *parent) + : parent_(parent), + frames_since_event_(0), + surface_{}, + page_(0) +{} + EventHandlerResult WavepoolEffect::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) { if (row >= ROWS || col >= COLS) return EventHandlerResult::OK; + if (::LEDControl.get_mode_index() != led_mode_id_) + return EventHandlerResult::OK; + + return ::LEDControl.get_mode() + ->onKeyswitchEvent(mapped_key, row, col, key_state); +} + +EventHandlerResult WavepoolEffect::TransientLEDMode::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) { if (keyIsPressed(key_state)) { uint8_t offset = (row * COLS) + col; - surface[page][pgm_read_byte(rc2pos + offset)] = 0x7f; - frames_since_event = 0; + surface_[page_][pgm_read_byte(rc2pos + offset)] = 0x7f; + frames_since_event_ = 0; } return EventHandlerResult::OK; } -void WavepoolEffect::raindrop(uint8_t x, uint8_t y, int8_t *page) { +void WavepoolEffect::TransientLEDMode::raindrop(uint8_t x, uint8_t y, int8_t *page_) { uint8_t rainspot = (y * WP_WID) + x; - page[rainspot] = 0x7f; - if (y > 0) page[rainspot - WP_WID] = 0x60; - if (y < (WP_HGT - 1)) page[rainspot + WP_WID] = 0x60; - if (x > 0) page[rainspot - 1] = 0x60; - if (x < (WP_WID - 1)) page[rainspot + 1] = 0x60; + page_[rainspot] = 0x7f; + if (y > 0) page_[rainspot - WP_WID] = 0x60; + if (y < (WP_HGT - 1)) page_[rainspot + WP_WID] = 0x60; + if (x > 0) page_[rainspot - 1] = 0x60; + if (x < (WP_WID - 1)) page_[rainspot + 1] = 0x60; } // this is a lot smaller than the standard library's rand(), // and still looks random-ish -uint8_t WavepoolEffect::wp_rand() { +uint8_t WavepoolEffect::TransientLEDMode::wp_rand() { static uint16_t offset = 0x400; offset = ((offset + 1) & 0x4fff) | 0x400; return (millis() / MS_PER_FRAME) + pgm_read_byte(offset); } -void WavepoolEffect::update(void) { +void WavepoolEffect::TransientLEDMode::update(void) { // limit the frame rate; one frame every 64 ms static uint8_t prev_time = 0; @@ -89,11 +101,11 @@ void WavepoolEffect::update(void) { static uint8_t current_hue = 0; current_hue ++; - frames_since_event ++; + frames_since_event_ ++; // needs two pages of height map to do the calculations - int8_t *newpg = &surface[page ^ 1][0]; - int8_t *oldpg = &surface[page][0]; + int8_t *newpg = &surface_[page_ ^ 1][0]; + int8_t *oldpg = &surface_[page_][0]; // rain a bit while idle static uint8_t frames_till_next_drop = 0; @@ -112,11 +124,11 @@ void WavepoolEffect::update(void) { raindrop(prev_x, prev_y, oldpg); prev_x = prev_y = -1; } - if (frames_since_event + if (frames_since_event_ >= (frames_till_next_drop + (idle_timeout / MS_PER_FRAME))) { frames_till_next_drop = 4 + (wp_rand() % FRAMES_PER_DROP); - frames_since_event = idle_timeout / MS_PER_FRAME; + frames_since_event_ = idle_timeout / MS_PER_FRAME; uint8_t x = wp_rand() % WP_WID; uint8_t y = wp_rand() % WP_HGT; @@ -195,7 +207,7 @@ void WavepoolEffect::update(void) { uint8_t value = (intensity >= 128) ? 255 : intensity << 1; int16_t hue = ripple_hue; - if (ripple_hue == rainbow_hue) { + if (ripple_hue == WavepoolEffect::rainbow_hue) { // color starts white but gets dimmer and more saturated as it fades, // with hue wobbling according to height map hue = (current_hue + height + (height >> 1)) & 0xff; @@ -209,10 +221,10 @@ void WavepoolEffect::update(void) { #ifdef INTERPOLATE // swap pages every other frame - if (!(now & 1)) page ^= 1; + if (!(now & 1)) page_ ^= 1; #else // swap pages every frame - page ^= 1; + page_ ^= 1; #endif } diff --git a/src/kaleidoscope/plugin/LED-Wavepool.h b/src/kaleidoscope/plugin/LED-Wavepool.h index c6fed02b..0799f3d3 100644 --- a/src/kaleidoscope/plugin/LED-Wavepool.h +++ b/src/kaleidoscope/plugin/LED-Wavepool.h @@ -28,7 +28,9 @@ namespace kaleidoscope { namespace plugin { -class WavepoolEffect : public LEDMode { +class WavepoolEffect : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: WavepoolEffect(void) {} @@ -39,17 +41,38 @@ class WavepoolEffect : public LEDMode { static int16_t ripple_hue; static constexpr int16_t rainbow_hue = INT16_MAX; - protected: - void update(void) final; - private: - static uint8_t frames_since_event; - static int8_t surface[2][WP_WID * WP_HGT]; - static uint8_t page; - static PROGMEM const uint8_t rc2pos[ROWS * COLS]; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - static void raindrop(uint8_t x, uint8_t y, int8_t *page); - static uint8_t wp_rand(); + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const WavepoolEffect *parent); + + EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state); + + protected: + + virtual void update() final; + + private: + + const WavepoolEffect *parent_; + + uint8_t frames_since_event_; + int8_t surface_[2][WP_WID * WP_HGT]; + uint8_t page_; + static PROGMEM const uint8_t rc2pos[ROWS * COLS]; + + void raindrop(uint8_t x, uint8_t y, int8_t *page); + uint8_t wp_rand(); + + friend class WavepoolEffect; + }; }; } diff --git a/src/kaleidoscope/plugin/LEDControl.cpp b/src/kaleidoscope/plugin/LEDControl.cpp index 1afec63e..2dd9d590 100644 --- a/src/kaleidoscope/plugin/LEDControl.cpp +++ b/src/kaleidoscope/plugin/LEDControl.cpp @@ -16,88 +16,72 @@ #include "Kaleidoscope-LEDControl.h" #include "Kaleidoscope-FocusSerial.h" +#include "kaleidoscope_internal/LEDModeManager.h" + +using namespace kaleidoscope::internal; namespace kaleidoscope { namespace plugin { -LEDMode *LEDControl::modes[LED_MAX_MODES]; -uint8_t LEDControl::mode; -uint16_t LEDControl::syncDelay = 32; // 32ms interval => 30Hz refresh rate +static constexpr uint8_t uninitialized_mode_id = 255; + +uint8_t LEDControl::mode_id = uninitialized_mode_id; +uint8_t LEDControl::num_led_modes_ = LEDModeManager::numLEDModes(); +LEDMode *LEDControl::cur_led_mode_; +uint16_t LEDControl::syncDelay = 32; uint16_t LEDControl::syncTimer; bool LEDControl::paused = false; -void LEDMode::activate(void) { - ::LEDControl.activate(this); -} - -kaleidoscope::EventHandlerResult LEDMode::onSetup() { - ::LEDControl.mode_add(this); - setup(); - - return EventHandlerResult::OK; -} - LEDControl::LEDControl(void) { - mode = 0; - memset(modes, 0, LED_MAX_MODES * sizeof(modes[0])); } void LEDControl::next_mode(void) { - mode++; + mode_id++; - if (mode >= LED_MAX_MODES || !modes[mode]) { + if (mode_id >= num_led_modes_) { return set_mode(0); } - return set_mode(mode); + return set_mode(mode_id); } void LEDControl::prev_mode(void) { - if (mode == 0) { + if (mode_id == 0) { // wrap around - mode = LED_MAX_MODES - 1; - // then count down until reaching a valid mode - while (mode > 0 && !modes[mode]) mode--; + mode_id = num_led_modes_ - 1; } else { - mode--; + mode_id--; } - return set_mode(mode); + return set_mode(mode_id); } void LEDControl::set_mode(uint8_t mode_) { - if (mode_ >= LED_MAX_MODES) + if (mode_ >= num_led_modes_) return; - mode = mode_; - refreshAll(); -} + mode_id = mode_; -uint8_t LEDControl::get_mode_index(void) { - return mode; -} + // Cache the LED mode + // + cur_led_mode_ = LEDModeManager::getLEDMode(mode_id); -LEDMode *LEDControl::get_mode(void) { - return modes[mode]; + refreshAll(); } -void LEDControl::activate(LEDMode *mode) { - for (uint8_t i = 0; i < LED_MAX_MODES; i++) { - if (modes[i] == mode) - return set_mode(i); - } -} +void LEDControl::activate(LEDModeInterface *plugin) { + for (uint8_t i = 0; i < num_led_modes_; i++) { + + led_mode_management::LEDModeFactory fac; -int8_t LEDControl::mode_add(LEDMode *mode) { - for (int i = 0; i < LED_MAX_MODES; i++) { - if (modes[i]) - continue; + LEDModeManager::retreiveLEDModeFactoryFromPROGMEM(i, fac); - modes[i] = mode; - return i; + if (fac.isAssociatedWithPlugin(plugin)) { + set_mode(i); + return; + } } - return -1; } void LEDControl::set_all_leds_to(uint8_t r, uint8_t g, uint8_t b) { @@ -139,13 +123,14 @@ void LEDControl::syncLeds(void) { kaleidoscope::EventHandlerResult LEDControl::onSetup() { set_all_leds_to({0, 0, 0}); - for (uint8_t i = 0; i < LED_MAX_MODES; i++) { - if (modes[i]) - (modes[i]->setup)(); - } + LEDModeManager::setupPersistentLEDModes(); syncTimer = millis() + syncDelay; + if (mode_id == uninitialized_mode_id) { + set_mode(0); + } + return EventHandlerResult::OK; } @@ -249,10 +234,10 @@ EventHandlerResult FocusLEDCommand::onFocusEvent(const char *command) { } else if (peek == 'p') { ::LEDControl.prev_mode(); } else { - uint8_t mode; + uint8_t mode_id; - ::Focus.read(mode); - ::LEDControl.set_mode(mode); + ::Focus.read(mode_id); + ::LEDControl.set_mode(mode_id); } break; } diff --git a/src/kaleidoscope/plugin/LEDControl.h b/src/kaleidoscope/plugin/LEDControl.h index a9faa6ee..7a500057 100644 --- a/src/kaleidoscope/plugin/LEDControl.h +++ b/src/kaleidoscope/plugin/LEDControl.h @@ -18,88 +18,20 @@ #include -#define LED_MAX_MODES 24 - #define LED_TOGGLE B00000001 // Synthetic, internal #define Key_LEDEffectNext Key(0, KEY_FLAGS | SYNTHETIC | IS_INTERNAL | LED_TOGGLE) #define Key_LEDEffectPrevious Key(1, KEY_FLAGS | SYNTHETIC | IS_INTERNAL | LED_TOGGLE) +#define _DEPRECATED_MESSAGE_LED_CONTROL_MODE_ADD \ + "LEDControl::mode_add(LEDMode *mode) is deprecated. LEDModes are now \n" \ + "automatically registered. You can safely remove any calls to \n" \ + "LEDControl::mode_add from your code." + namespace kaleidoscope { namespace plugin { -/** Base class for LED modes. - * - * LED modes are a special kind of plugin, they are in charge of updating LED - * colors, setting a theme. While it is possible to have other plugins - * override the mode's colors, the LED mode is the baseline. - * - * Most of its functionality is called via @ref LEDControl, with only a few - * public methods. - * - * A LED mode **must** implement at least one of @ref onActivate or @ref - * update, and possibly @ref refreshAt too. - */ -class LEDMode : public kaleidoscope::Plugin { - friend class LEDControl; - protected: - // These methods should only be called by LEDControl. - - /** One-time setup, called at keyboard boot. - * - * Any hooks that need registering, any one-time setup that needs to be - * performed, shall be done here. This is purely for preparation purposes, the - * LEDs should not be touched yet at this time. - */ - virtual void setup(void) {} - - /** Function to call whenever the mode is activated. - * - * Like @ref setup, this method need not touch LEDs, @ref update will be - * called right after it. The purpose of this callback is to allow a plugin to - * do some preparation whenever it is activated, instead of only on boot, or - * always at each cycle. - * - * However, unlike @ref setup, this method can change LED colors, if so - * desired. Either to provide an initial state, or a static color set. In the - * latter case, consider implementing @ref refreshAt too, because other - * plugins may override some of the colors set at activation time, and @ref - * refreshAt can be used to restore them when needed. - * - * Before the callback runs, LEDs will be blanked. - */ - virtual void onActivate(void) {} - - /** Update the LEDs once per cycle. - * - * Usually the brains of the plugin, which updates the LEDs each cycle. It is - * called after the matrix has been scanned, once per cycle. - */ - virtual void update(void) {} - - /** Refresh the color of a given key. - * - * If we have another plugin that overrides colors set by the active LED mode - * (either at @onActivate time, or via @ref update), if that plugin wants to - * restore whatever color the mode would set the key color to, this is the - * method it will call. - * - * @param row is the row coordinate of the key to refresh the color of. - * @param col is the column coordinate of the key to refresh the color of. - */ - virtual void refreshAt(byte row, byte col) {} - public: - /** Activate the current object as the LED mode. - */ - void activate(void); - - /** Plugin initialization. - * - * Called via `Kaleidoscope.use()`, registers the LED mode, and does the - * necessary initialization steps. Calls @ref setup at the end. - */ - kaleidoscope::EventHandlerResult onSetup(); -}; +class LEDMode; class LEDControl : public kaleidoscope::Plugin { public: @@ -112,19 +44,26 @@ class LEDControl : public kaleidoscope::Plugin { if (!Kaleidoscope.has_leds) return; - if (modes[mode]) - modes[mode]->update(); + cur_led_mode_->update(); } static void refreshAt(byte row, byte col) { if (!Kaleidoscope.has_leds) return; - if (modes[mode]) - modes[mode]->refreshAt(row, col); + cur_led_mode_->refreshAt(row, col); + } + static void set_mode(uint8_t mode_id); + static uint8_t get_mode_index() { + return mode_id; + } + static LEDMode *get_mode() { + return cur_led_mode_; + } + template + static LEDMode__ *get_mode() { + return static_cast(cur_led_mode_); } - static void set_mode(uint8_t mode); - static uint8_t get_mode_index(); - static LEDMode *get_mode(); + static void refreshAll() { if (!Kaleidoscope.has_leds) return; @@ -133,11 +72,14 @@ class LEDControl : public kaleidoscope::Plugin { return; set_all_leds_to({0, 0, 0}); - if (modes[mode]) - modes[mode]->onActivate(); + + cur_led_mode_->onActivate(); } - static int8_t mode_add(LEDMode *mode); + DEPRECATED(LED_CONTROL_MODE_ADD) + static int8_t mode_add(LEDMode *mode) { + return 0; + } static void setCrgbAt(int8_t i, cRGB crgb); static void setCrgbAt(byte row, byte col, cRGB color); @@ -147,7 +89,11 @@ class LEDControl : public kaleidoscope::Plugin { static void set_all_leds_to(uint8_t r, uint8_t g, uint8_t b); static void set_all_leds_to(cRGB color); - static void activate(LEDMode *mode); + // We restict activate to LEDModeInterface to make sure that + // a compiler error is thrown when activate() is accidentally + // applied to a non-LED mode plugin. + // + static void activate(LEDModeInterface *plugin); static uint16_t syncDelay; static bool paused; @@ -158,8 +104,9 @@ class LEDControl : public kaleidoscope::Plugin { private: static uint16_t syncTimer; - static LEDMode *modes[LED_MAX_MODES]; - static uint8_t mode; + static uint8_t mode_id; + static uint8_t num_led_modes_; + static LEDMode *cur_led_mode_; }; class FocusLEDCommand : public Plugin { diff --git a/src/kaleidoscope/plugin/LEDControl/LED-Off.h b/src/kaleidoscope/plugin/LEDControl/LED-Off.h index 3a931e9d..a13bec5a 100644 --- a/src/kaleidoscope/plugin/LEDControl/LED-Off.h +++ b/src/kaleidoscope/plugin/LEDControl/LED-Off.h @@ -20,6 +20,10 @@ namespace kaleidoscope { namespace plugin { + +// This is still an old style persistent LEDMode as it does not have +// any members and thus there would not be any gain from making it dynamic. +// class LEDOff : public LEDMode { public: LEDOff(void) { } diff --git a/src/kaleidoscope/plugin/LEDEffect-Breathe.cpp b/src/kaleidoscope/plugin/LEDEffect-Breathe.cpp index d3d35020..9a85fba6 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Breathe.cpp +++ b/src/kaleidoscope/plugin/LEDEffect-Breathe.cpp @@ -20,7 +20,7 @@ namespace kaleidoscope { namespace plugin { -void LEDBreatheEffect::update(void) { +void LEDBreatheEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; @@ -29,7 +29,7 @@ void LEDBreatheEffect::update(void) { return; last_update_ = now; - cRGB color = breath_compute(hue, saturation); + cRGB color = breath_compute(parent_->hue, parent_->saturation); ::LEDControl.set_all_leds_to(color); } } diff --git a/src/kaleidoscope/plugin/LEDEffect-Breathe.h b/src/kaleidoscope/plugin/LEDEffect-Breathe.h index ade1e7dc..814073b3 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Breathe.h +++ b/src/kaleidoscope/plugin/LEDEffect-Breathe.h @@ -20,18 +20,34 @@ namespace kaleidoscope { namespace plugin { -class LEDBreatheEffect : public LEDMode { +class LEDBreatheEffect : public Plugin, + public LEDModeInterface { public: LEDBreatheEffect(void) {} uint8_t hue = 170; uint8_t saturation = 255; - protected: - void update(void) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - private: - uint16_t last_update_ = 0; + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDBreatheEffect *parent) + : parent_(parent) {} + + protected: + virtual void update(void) final; + + private: + + const LEDBreatheEffect *parent_; + uint16_t last_update_ = 0; + }; }; } } diff --git a/src/kaleidoscope/plugin/LEDEffect-Chase.cpp b/src/kaleidoscope/plugin/LEDEffect-Chase.cpp index 0e18f28e..629b66ba 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Chase.cpp +++ b/src/kaleidoscope/plugin/LEDEffect-Chase.cpp @@ -19,19 +19,19 @@ namespace kaleidoscope { namespace plugin { -void LEDChaseEffect::update(void) { +void LEDChaseEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; uint16_t now = Kaleidoscope.millisAtCycleStart(); - if ((now - last_update_) < update_delay_) { + if ((now - last_update_) < parent_->update_delay_) { return; } last_update_ = now; // The red LED is at `pos_`; the blue one follows behind. `direction_` is // either +1 or -1; `distance_` is the gap between them. - int8_t pos2 = pos_ - (direction_ * distance_); + int8_t pos2 = pos_ - (direction_ * parent_->distance_); // First, we turn off the LEDs that were turned on in the previous update. // `pos_` is always in the valid range (0 <= pos_ < LED_COUNT), but after it diff --git a/src/kaleidoscope/plugin/LEDEffect-Chase.h b/src/kaleidoscope/plugin/LEDEffect-Chase.h index 52ee2798..6dd0da46 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Chase.h +++ b/src/kaleidoscope/plugin/LEDEffect-Chase.h @@ -20,7 +20,8 @@ namespace kaleidoscope { namespace plugin { -class LEDChaseEffect : public LEDMode { +class LEDChaseEffect : public Plugin, + public LEDModeInterface { public: LEDChaseEffect(void) {} @@ -37,15 +38,34 @@ class LEDChaseEffect : public LEDMode { distance_ = value; } - protected: - void update(void) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDChaseEffect *parent) + : parent_(parent) {} + + protected: + + virtual void update() final; + + private: + + const LEDChaseEffect *parent_; + + int8_t pos_ = 0; + int8_t direction_ = 1; + uint16_t last_update_ = 0; + }; private: - int8_t pos_ = 0; - int8_t direction_ = 1; uint8_t distance_ = 5; uint16_t update_delay_ = 150; - uint16_t last_update_ = 0; }; } } diff --git a/src/kaleidoscope/plugin/LEDEffect-Rainbow.cpp b/src/kaleidoscope/plugin/LEDEffect-Rainbow.cpp index 2b8b5b3e..f063897c 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Rainbow.cpp +++ b/src/kaleidoscope/plugin/LEDEffect-Rainbow.cpp @@ -19,18 +19,18 @@ namespace kaleidoscope { namespace plugin { -void LEDRainbowEffect::update(void) { +void LEDRainbowEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; uint16_t now = millis(); - if ((now - rainbow_last_update) < rainbow_update_delay) { + if ((now - rainbow_last_update) < parent_->rainbow_update_delay) { return; } else { rainbow_last_update = now; } - cRGB rainbow = hsvToRgb(rainbow_hue, rainbow_saturation, rainbow_value); + cRGB rainbow = hsvToRgb(rainbow_hue, rainbow_saturation, parent_->rainbow_value); rainbow_hue += rainbow_steps; if (rainbow_hue >= 255) { @@ -50,12 +50,12 @@ void LEDRainbowEffect::update_delay(byte delay) { // --------- -void LEDRainbowWaveEffect::update(void) { +void LEDRainbowWaveEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; uint16_t now = millis(); - if ((now - rainbow_last_update) < rainbow_update_delay) { + if ((now - rainbow_last_update) < parent_->rainbow_update_delay) { return; } else { rainbow_last_update = now; @@ -66,7 +66,7 @@ void LEDRainbowWaveEffect::update(void) { if (key_hue >= 255) { key_hue -= 255; } - cRGB rainbow = hsvToRgb(key_hue, rainbow_saturation, rainbow_value); + cRGB rainbow = hsvToRgb(key_hue, rainbow_saturation, parent_->rainbow_value); ::LEDControl.setCrgbAt(i, rainbow); } rainbow_hue += rainbow_wave_steps; diff --git a/src/kaleidoscope/plugin/LEDEffect-Rainbow.h b/src/kaleidoscope/plugin/LEDEffect-Rainbow.h index 3bc8f654..cfff7d0f 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Rainbow.h +++ b/src/kaleidoscope/plugin/LEDEffect-Rainbow.h @@ -20,7 +20,8 @@ namespace kaleidoscope { namespace plugin { -class LEDRainbowEffect : public LEDMode { +class LEDRainbowEffect : public Plugin, + public LEDModeInterface { public: LEDRainbowEffect(void) {} @@ -32,21 +33,40 @@ class LEDRainbowEffect : public LEDMode { byte update_delay(void) { return rainbow_update_delay; } - void update(void) final; - private: - uint16_t rainbow_hue = 0; // stores 0 to 614 + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - uint8_t rainbow_steps = 1; // number of hues we skip in a 360 range per update - uint16_t rainbow_last_update = 0; - uint16_t rainbow_update_delay = 40; // delay between updates (ms) + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDRainbowEffect *parent) + : parent_(parent) {} + + virtual void update() final; + + private: + + const LEDRainbowEffect *parent_; - byte rainbow_saturation = 255; + uint16_t rainbow_hue = 0; // stores 0 to 614 + + uint8_t rainbow_steps = 1; // number of hues we skip in a 360 range per update + uint16_t rainbow_last_update = 0; + + byte rainbow_saturation = 255; + }; + + private: + uint16_t rainbow_update_delay = 40; // delay between updates (ms) byte rainbow_value = 50; }; -class LEDRainbowWaveEffect : public LEDMode { +class LEDRainbowWaveEffect : public Plugin, public LEDModeInterface { public: LEDRainbowWaveEffect(void) {} @@ -58,16 +78,34 @@ class LEDRainbowWaveEffect : public LEDMode { byte update_delay(void) { return rainbow_update_delay; } - void update(void) final; - private: - uint16_t rainbow_hue = 0; // stores 0 to 614 + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - uint8_t rainbow_wave_steps = 1; // number of hues we skip in a 360 range per update - uint16_t rainbow_last_update = 0; - uint16_t rainbow_update_delay = 40; // delay between updates (ms) + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDRainbowWaveEffect *parent) + : parent_(parent) {} + + virtual void update() final; + + private: + + const LEDRainbowWaveEffect *parent_; - byte rainbow_saturation = 255; + uint16_t rainbow_hue = 0; // stores 0 to 614 + + uint8_t rainbow_wave_steps = 1; // number of hues we skip in a 360 range per update + uint16_t rainbow_last_update = 0; + + byte rainbow_saturation = 255; + }; + + uint16_t rainbow_update_delay = 40; // delay between updates (ms) byte rainbow_value = 50; }; } diff --git a/src/kaleidoscope/plugin/LEDEffect-SolidColor.cpp b/src/kaleidoscope/plugin/LEDEffect-SolidColor.cpp index d030e86d..ad929256 100644 --- a/src/kaleidoscope/plugin/LEDEffect-SolidColor.cpp +++ b/src/kaleidoscope/plugin/LEDEffect-SolidColor.cpp @@ -18,18 +18,18 @@ namespace kaleidoscope { namespace plugin { -LEDSolidColor::LEDSolidColor(uint8_t r, uint8_t g, uint8_t b) { - this->r = r; - this->g = g; - this->b = b; -} -void LEDSolidColor::onActivate(void) { - ::LEDControl.set_all_leds_to(r, g, b); +void LEDSolidColor::TransientLEDMode::onActivate(void) { + ::LEDControl.set_all_leds_to(parent_->r_, + parent_->g_, + parent_->b_); } -void LEDSolidColor::refreshAt(byte row, byte col) { - ::LEDControl.setCrgbAt(row, col, CRGB(r, g, b)); +void LEDSolidColor::TransientLEDMode::refreshAt(byte row, byte col) { + ::LEDControl.setCrgbAt(row, col, + CRGB(parent_->r_, + parent_->g_, + parent_->b_)); } } diff --git a/src/kaleidoscope/plugin/LEDEffect-SolidColor.h b/src/kaleidoscope/plugin/LEDEffect-SolidColor.h index 30ebc14d..d66971f9 100644 --- a/src/kaleidoscope/plugin/LEDEffect-SolidColor.h +++ b/src/kaleidoscope/plugin/LEDEffect-SolidColor.h @@ -20,16 +20,38 @@ namespace kaleidoscope { namespace plugin { -class LEDSolidColor : public LEDMode { +class LEDSolidColor : public Plugin, + public LEDModeInterface { public: - LEDSolidColor(uint8_t r, uint8_t g, uint8_t b); - protected: - void onActivate(void) final; - void refreshAt(byte row, byte col) final; + LEDSolidColor(uint8_t r, uint8_t g, uint8_t b) + : r_(r), g_(g), b_(b) + {} + + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDSolidColor *parent) + : parent_(parent) {} + + protected: + virtual void onActivate(void) final; + virtual void refreshAt(byte row, byte col) final; + + private: + + const LEDSolidColor *parent_; + }; private: - uint8_t r, g, b; + + uint8_t r_, g_, b_; }; } } diff --git a/src/kaleidoscope/plugin/LEDMode.cpp b/src/kaleidoscope/plugin/LEDMode.cpp new file mode 100644 index 00000000..ed422ab0 --- /dev/null +++ b/src/kaleidoscope/plugin/LEDMode.cpp @@ -0,0 +1,30 @@ +/* Kaleidoscope-LEDControl - LED control plugin for Kaleidoscope + * Copyright (C) 2017-2018 Keyboard.io, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + + +#include +#include +#include + +namespace kaleidoscope { +namespace plugin { + +void LEDModeInterface::activate() { + LEDControl::activate(this); +} + +} // end namespace plugin +} // end namespace kaleidoscope diff --git a/src/kaleidoscope/plugin/LEDMode.h b/src/kaleidoscope/plugin/LEDMode.h new file mode 100644 index 00000000..2459ba95 --- /dev/null +++ b/src/kaleidoscope/plugin/LEDMode.h @@ -0,0 +1,144 @@ +/* Kaleidoscope-LEDControl - LED control plugin for Kaleidoscope + * Copyright (C) 2017-2018 Keyboard.io, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "kaleidoscope/plugin.h" + +namespace kaleidoscope { + +namespace internal { +// Forward declaration +class LEDModeManager; +} // end namespace internal + +namespace plugin { + +class LEDModeInterface { + public: + + void activate(); + + // This auxiliary class helps to generate a verbose error message + // in case that there is no TransientLEDMode typedef or nested + // class present in a derived class of LEDModeInterface. + // + struct _____LEDModeInterface_derived_class_is_missing_a_TransientLEDMode_typedef_or_nested_class_____ {}; + typedef _____LEDModeInterface_derived_class_is_missing_a_TransientLEDMode_typedef_or_nested_class_____ NoLEDMode; + + // By redefining TransientLEDMode, derived plugins export + // a LED mode that becomes part of the set of LED modes whose + // lifetime is handled dynamically. + // + typedef NoLEDMode DynamicLEDMode; +}; + +class AccessTransientLEDMode { + public: + + // This method is called when a plugin's LED mode is activated. + // Derived plugins may reimplement it to store the id of their + // exported LED mode. A plugin can thus check + // whether their LED mode is currently active. + // + void registerLEDModeActivated(uint8_t led_mode_id) { + led_mode_id_ = led_mode_id; + } + + protected: + + uint8_t led_mode_id_ = 255; /* 255 means uninitialized */ +}; + +/** Base class for LED modes. +* +* LED modes are a special kind of plugin, they are in charge of updating LED +* colors, setting a theme. While it is possible to have other plugins +* override the mode's colors, the LED mode is the baseline. +* +* Most of its functionality is called via @ref LEDControl, with only a few +* public methods. +* +* A LED mode **must** implement at least one of @ref onActivate or @ref +* update, and possibly @ref refreshAt too. +*/ +class LEDMode : public kaleidoscope::Plugin, + public LEDModeInterface { + friend class LEDControl; + friend class kaleidoscope::internal::LEDModeManager; + protected: + // These methods should only be called by LEDControl. + + /** One-time setup, called at keyboard boot. + * + * Any hooks that need registering, any one-time setup that needs to be + * performed, shall be done here. This is purely for preparation purposes, the + * LEDs should not be touched yet at this time. + */ + virtual void setup(void) {} + + /** Function to call whenever the mode is activated. + * + * Like @ref setup, this method need not touch LEDs, @ref update will be + * called right after it. The purpose of this callback is to allow a plugin to + * do some preparation whenever it is activated, instead of only on boot, or + * always at each cycle. + * + * However, unlike @ref setup, this method can change LED colors, if so + * desired. Either to provide an initial state, or a static color set. In the + * latter case, consider implementing @ref refreshAt too, because other + * plugins may override some of the colors set at activation time, and @ref + * refreshAt can be used to restore them when needed. + * + * Before the callback runs, LEDs will be blanked. + */ + virtual void onActivate(void) {} + + /** Update the LEDs once per cycle. + * + * Usually the brains of the plugin, which updates the LEDs each cycle. It is + * called after the matrix has been scanned, once per cycle. + */ + virtual void update(void) {} + + /** Refresh the color of a given key. + * + * If we have another plugin that overrides colors set by the active LED mode + * (either at @onActivate time, or via @ref update), if that plugin wants to + * restore whatever color the mode would set the key color to, this is the + * method it will call. + * + * @param row is the row coordinate of the key to refresh the color of. + * @param col is the column coordinate of the key to refresh the color of. + */ + virtual void refreshAt(byte row, byte col) {} + + public: + + /** Plugin initialization. + * + * Called via `Kaleidoscope.use()`, registers the LED mode, and does the + * necessary initialization steps. Calls @ref setup at the end. + */ + kaleidoscope::EventHandlerResult onSetup() { + setup(); + + return EventHandlerResult::OK; + } +}; + +} // end namespace plugin +} // end namespace kaleidoscope diff --git a/src/kaleidoscope/plugin/Model01-TestMode.cpp b/src/kaleidoscope/plugin/Model01-TestMode.cpp index 27cf61ed..5789a314 100644 --- a/src/kaleidoscope/plugin/Model01-TestMode.cpp +++ b/src/kaleidoscope/plugin/Model01-TestMode.cpp @@ -19,6 +19,7 @@ #include "Kaleidoscope.h" #include "Kaleidoscope-Model01-TestMode.h" #include "Kaleidoscope-LEDEffect-Rainbow.h" +#include "kaleidoscope_internal/LEDModeManager.h" namespace kaleidoscope { namespace plugin { @@ -73,9 +74,17 @@ void TestMode::test_leds(void) { set_leds(blue); // make all the LEDs bright white (1.6A) set_leds(brightWhite); + + // This works under the assumption that LEDRainbowEffect + // has been registered with KALEIDOSCOPE_INIT_PLUGINS in + // the sketch. Otherwise LEDRainbowEffect would not be + // known to LEDControl. + // + ::LEDControl.activate(&::LEDRainbowEffect); + // rainbow for 10 seconds for (auto i = 0; i < 1000; i++) { - ::LEDRainbowEffect.update(); + ::LEDControl.update(); ::LEDControl.syncLeds(); } waitForKeypress(); diff --git a/src/kaleidoscope/plugin/TriColor.cpp b/src/kaleidoscope/plugin/TriColor.cpp index c13eb626..cb457ec5 100644 --- a/src/kaleidoscope/plugin/TriColor.cpp +++ b/src/kaleidoscope/plugin/TriColor.cpp @@ -26,18 +26,18 @@ TriColor::TriColor(cRGB base_color, cRGB mod_color, cRGB esc_color) { esc_color_ = esc_color; } -void TriColor::update(void) { +void TriColor::TransientLEDMode::update(void) { for (uint8_t r = 0; r < ROWS; r++) { for (uint8_t c = 0; c < COLS; c++) { Key k = Layer.lookup(r, c); // Special keys are always mod_color if (k.flags != 0) { - ::LEDControl.setCrgbAt(r, c, mod_color_); + ::LEDControl.setCrgbAt(r, c, parent_->mod_color_); continue; } - cRGB color = mod_color_; + cRGB color = parent_->mod_color_; switch (k.keyCode) { case Key_A.keyCode ... Key_0.keyCode: @@ -46,10 +46,10 @@ void TriColor::update(void) { case Key_Keypad1.keyCode ... Key_KeypadDot.keyCode: case Key_F1.keyCode ... Key_F4.keyCode: case Key_F9.keyCode ... Key_F12.keyCode: - color = base_color_; + color = parent_->base_color_; break; case Key_Escape.keyCode: - color = esc_color_; + color = parent_->esc_color_; break; } diff --git a/src/kaleidoscope/plugin/TriColor.h b/src/kaleidoscope/plugin/TriColor.h index b4d62ef9..ead3cd40 100644 --- a/src/kaleidoscope/plugin/TriColor.h +++ b/src/kaleidoscope/plugin/TriColor.h @@ -22,13 +22,32 @@ namespace kaleidoscope { namespace plugin { -class TriColor : public LEDMode { +class TriColor : public Plugin, + public LEDModeInterface { public: TriColor(cRGB base_color, cRGB mod_color, cRGB esc_color); TriColor(cRGB base_color, cRGB mod_color) : TriColor(base_color, mod_color, mod_color) {} - protected: - void update(void) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const TriColor *parent) + : parent_(parent) {} + + protected: + + virtual void update(void) final; + + private: + + const TriColor *parent_; + }; private: cRGB base_color_; diff --git a/src/kaleidoscope_internal/LEDModeManager.cpp b/src/kaleidoscope_internal/LEDModeManager.cpp new file mode 100644 index 00000000..151c0db6 --- /dev/null +++ b/src/kaleidoscope_internal/LEDModeManager.cpp @@ -0,0 +1,112 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2013-2018 Keyboard.io, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "kaleidoscope_internal/LEDModeManager.h" +#include "kaleidoscope/plugin/LEDMode.h" + +namespace kaleidoscope { +namespace internal { + +namespace { + +// We want to export as little symbols as possible. That's why the +// internal state of the LED mode management lives in +// an anonymous namespace. + +uint8_t cur_mode_id = 255; // We use 255 as a flag value that signals +// uninitialized. That's why we can only have +// LED mode ids in the range [0..254]. + +// A pointer to cache the current transient LED mode. +// +kaleidoscope::plugin::LEDMode *cur_led_mode = nullptr; + +bool current_led_mode_dynamic = false; + +} + +kaleidoscope::plugin::LEDMode *LEDModeManager::getLEDMode(uint8_t mode_id) { + + // If we end up here, the requested LED mode is a dynamic one. + + // Check if the requested LED mode is already active + // + if (cur_mode_id == mode_id) { + return cur_led_mode; + } + + // If there is already an active LED mode, its obviously the wrong one + // (see test above). To generate a new transient LED mode, we need + // to destroy the current one. To achieve this, we call its + // (possibly - see explanation below) virtual destuctor and let + // it take care of the cleanup. + // + // Please note that due to the fact that transient LED modes are + // allocated using placement new within a pre-existing static buffer, + // there is no need to free any memory after the destructor of the + // current LED mode was called. We can just reuse the buffer + // for the next LED mode instance. + // + // Please note: Currently, LEDMode has no virtual destructor. + // That's why the explicit destructor call below is a noop + // that is optimized out by the compiler. It is there + // to enable the possible future introduction of a virtual + // destructor for class LEDMode. + // + // A virtual destructor would enable LEDModes to do any types of + // cleanup on destruction. But as it's a virtual method, + // introducing such a destructor would create additional + // entries in vtables and mean higher PROGMEM consumption. + // If it will be introduced in future versions of Kaleidoscope + // uncomment the commented lines below. + // + if (current_led_mode_dynamic) { + cur_led_mode->~LEDMode(); + } + + // Store the current mode id to enable cache access (see above). + // + cur_mode_id = mode_id; + + // Get a factory struct for the creation of the new LED mode. + // + led_mode_management::LEDModeFactory fac; + + retreiveLEDModeFactoryFromPROGMEM(mode_id, fac); + + // The factories for persistent LED modes serve to pass through the LED + // mode plugin's pointer in parent_plugin_. + // + if (fac.isPersistentLEDMode()) { + current_led_mode_dynamic = false; + cur_led_mode = fac.getPersistentLEDMode(); + } else { + current_led_mode_dynamic = true; + + // Generate a new led mode by calling the factory function + // (fac.generate_led_mode_). + // + // We store a pointer to the newly created LED mode to enable + // cached access (see check above). + // + cur_led_mode = fac.generateTransientLEDMode(led_mode_buffer_, mode_id); + } + + return cur_led_mode; +} + +} // end namespace internal +} // end namespace kaleidoscope diff --git a/src/kaleidoscope_internal/LEDModeManager.h b/src/kaleidoscope_internal/LEDModeManager.h new file mode 100644 index 00000000..b8b7bfef --- /dev/null +++ b/src/kaleidoscope_internal/LEDModeManager.h @@ -0,0 +1,474 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2013-2018 Keyboard.io, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "kaleidoscope_internal/array_like_storage.h" +#include "kaleidoscope/plugin.h" +#include "kaleidoscope/plugin/LEDMode.h" +#include "kaleidoscope_internal/type_traits/has_method.h" + +#include + +// To enable placement new, we need to supply a global operator +// function. +// +inline void* operator new (size_t, void* __p) throw() { + return __p; +} + +namespace kaleidoscope { + +namespace plugin { +// Forward declarations to avoid header inclusions +class LEDControl; +class LEDModeInterface; +} // end namespace plugin + +namespace internal { +namespace led_mode_management { + +template struct Bool2Type {}; + +// Functions of this type allocate a new LED mode within a global +// buffer and give it access to it's parent plugin. +// This allows a transient LED mode to access member data of its parent +// plugin, which can help to reduce its RAM footprint. +// Some transient LED modes might ignore the parent pointer entirely, e.g. if +// they do not have a configurable internal state. +// +// We use a factory function instead of a polymorphic +// class to generate LED modes. By this means we can avoid the additional +// cost of vtables which safes some program memory. +// Conceptually, the factory function approach is similar to having +// a polymorphic class with only one method, that is accessed directly +// instead of the detour via the vtable. +// +typedef kaleidoscope::plugin::LEDMode* +(*LEDModeFactoryFunc)(void *raw_buffer, + kaleidoscope::plugin::LEDModeInterface *parent_plugin, + uint8_t mode_id + ); + +template +inline void registerLEDModeActivated(Bool2Type, + ParentPlugin__ *parent_plugin, + uint8_t mode_id) { + parent_plugin->registerLEDModeActivated(mode_id); +} + +template +inline void registerLEDModeActivated(Bool2Type, + ParentPlugin__ *parent_plugin, + uint8_t mode_id) { + // Noop +} + +DEFINE_HAS_METHOD_TRAITS(Plugin, registerLEDModeActivated, void, (uint8_t led_mode_id)) + +// A templated implementation of LEDModeFactoryFunc. +// +// Please note that the actual type of ParentPlugin__ is only +// known to the context where the function is invoked or in our case +// the function pointer to generateLEDMode is generated (at compile time). +// +// The caller can be completely agnostic of ParentPlugin__ an just pass +// a pointer to kaleidoscope::Plugin for argument parent_plugin. +// +// The function generateLEDMode knows the actual type of parent_plugin. +// Or, more precise, we can rely on the fact that we store a pointer to a plugin +// of appropriate type together with the generateLEDMode-pointer in +// an LEDModeFactory instance in PROGMEM. Thus, generateLEDMode will only +// be called with a parent_plugin of appropriate type. +// +template +static kaleidoscope::plugin::LEDMode * +generateLEDMode(void *raw_buffer, + kaleidoscope::plugin::LEDModeInterface *parent_plugin, + uint8_t mode_id + ) { + // We know the type of the parent plugin via the template parameter + // ParentPlugin__, thus it is safe to cast to the actual type. + // + auto parent_plugin_actual = static_cast(parent_plugin); + + // Types defined by template parameters like ParentPlugin__ must + // be declared explicitly using the "typename" keyword. + // Because of this lengthy syntax, we use a typedef to + // shorten the constructor call below. + // + typedef typename ParentPlugin__::TransientLEDMode TLM; + + // Generate a transient LED mode within the LED mode buffer. + // + auto led_mode_ptr + = new (raw_buffer) TLM{parent_plugin_actual}; + + constexpr bool accesses_transient_led_mode + = Plugin_HasMethod_registerLEDModeActivated::value; + + // Register the newly created LED mode with its parent plugin. + // Please note that this call is optimized away by the compiler + // for all those plugins that do not reimplement registerLEDModeActivated. + // + registerLEDModeActivated(Bool2Type(), + parent_plugin_actual, mode_id); + + return led_mode_ptr; +} + +// A data structure that contains all the information that is required +// to generate a transient LED mode. Instances of this class are stored +// in an array-like data structure in PROGMEM. +// +struct LEDModeFactory { + + // Don't try to make this a class with a constructor. This + // will turn it into a non POD which cannot be used as + // return type of a constexpr. + + bool isPersistentLEDMode() const { + return !generate_led_mode_; + } + + kaleidoscope::plugin::LEDMode *getPersistentLEDMode() const { + return static_cast(parent_plugin_); + } + + kaleidoscope::plugin::LEDMode *generateTransientLEDMode( + uint8_t *buffer, uint8_t mode_id) const { + return (*generate_led_mode_)(buffer, parent_plugin_, mode_id); + } + + bool isAssociatedWithPlugin(kaleidoscope::plugin::LEDModeInterface *plugin) const { + return parent_plugin_ == plugin; + } + + kaleidoscope::plugin::LEDModeInterface *parent_plugin_; + LEDModeFactoryFunc generate_led_mode_; +}; + +// The traits class remove_pointer is part of the C++ standard library +// but not present on Arduino. +// +template< class T > struct remove_pointer { + typedef T type; +}; +template< class T > struct remove_pointer { + typedef T type; +}; + +enum PluginType { + PluginType_NoLEDMode, + PluginType_PersistentLEDMode, + PluginType_TransientLEDMode +}; + +// The following three functions enable determining the PluginType +// at compile time by examining a constexpr pointer to one of the +// global plugin instances (pointers to global variables/objects are +// constexpr). +// +static constexpr int ledModePluginType(kaleidoscope::plugin::LEDMode *) { + return PluginType_PersistentLEDMode; +} + +static constexpr int ledModePluginType(kaleidoscope::plugin::LEDModeInterface *) { + return PluginType_TransientLEDMode; +} + +// Invoked for all plugins that inherit neither from LEDMode +// nor from LEDModeInterface. +// +static constexpr bool ledModePluginType(void *) { + return PluginType_NoLEDMode; +} + +// The following traits classes are used to distinguish between three cases +// +// 1) A plugin is unrelated to LEDModes (i.e. non of the other two applies). +// +// 2) A plugin is an old syle persistent LEDMode (plugin is derived from +// LED mode = persistent LED mode) +// +// 3) A plugin exports a LED mode with dynamic lifetime (= transient LED mode) + +// Checks if a plugin is related to LED modes in any kind of ways. +// This traits check if for a plugin +// an entry in the plugin factories array needs to be reserved. +// +constexpr bool pluginControlsLEDMode(int led_mode_plugin_type) { + return led_mode_plugin_type != PluginType_NoLEDMode; +} + +// The template GenerateLEDModeFactory generates LED mode factories +// in three different ways, depending on the type of a plugin (PluginType) + +// Generates a dummy factory (discarded at compile time) for +// those plugins that are unrelated to LED modes. This template +// is selected for LEDModePluginType__ == NoLEDMode. +// +template +struct GenerateLEDModeFactory { + + static constexpr LEDModeFactory apply(kaleidoscope::Plugin */* non LED mode plugin*/) { + return LEDModeFactory{nullptr, nullptr}; + } +}; + +template<> +struct GenerateLEDModeFactory { + + // This version of apply must be templated as we must know the actual + // type of plugin as the instanciatioin of template function + // generateLEDMode depends upon it. + // + template + static constexpr LEDModeFactory apply(Plugin__ *plugin) { + return LEDModeFactory{ + plugin, + generateLEDMode // pointer to template instantiation of + // generateLEDMode<...> + }; + } +}; + +template<> +struct GenerateLEDModeFactory { + + static constexpr LEDModeFactory apply(kaleidoscope::plugin::LEDModeInterface *plugin) { + + // For persistent LED modes, we use the LED factory to simply store + // the plugin pointer (a persistent LED mode is itself the LEDMode). + // Thus, no factory function is required. + // + return LEDModeFactory{plugin, nullptr}; + } +}; + +// The following template TransientLEDModeSize is used to determine +// the size of a transient LED mode. It is only active for dynamic +// LED modes. For all other plugins, it simply returns zero. +// +template +struct TransientLEDModeSize { + static constexpr size_t value = 0; +}; + +template +struct TransientLEDModeSize { + typedef typename remove_pointer::type Plugin__; + static constexpr size_t value = sizeof(typename Plugin__::TransientLEDMode); +}; + +// This helper class serves to determine the maximum memory footprint +// of any transient LED mode in the sketch. +// It implements a template type recursion +// that examins all specified LED modes. For this calculation +// it is no problem that also the type NoLEDMode is considered +// in this check as it is an empty class (size == 1 byte) and +// thus does not affect the maximum size computation. +// +template +struct TransientLEDModeMaxSize { + static constexpr size_t this_size + = TransientLEDModeSize::value; + + static constexpr size_t nested_size = TransientLEDModeMaxSize::value; + + static constexpr size_t value = (this_size > nested_size) ? this_size : nested_size; +}; + +// Specialization to end recursion +// +template +struct TransientLEDModeMaxSize { + static constexpr size_t value = TransientLEDModeSize::value; +}; + +} // end namespace led_mode_management + +class LEDModeManager { + public: + + // Everything in this class private on purpose. + // + // Only the class LEDControl is supposed to gain + // access to any inventory of this class to ensure that + // the handling of transient LED modes is safely handled + // by a well defined central instance. + // + friend class kaleidoscope::plugin::LEDControl; + + static uint8_t numLEDModes(); + +#if 0 + // This method could be used in rare cases + // where a stack allocation of a LED mode may not be avoided. + // + template + static auto + generateLEDModeTemporary(void *raw_buffer, + ParentPlugin__ *parent_plugin + ) -> typename ParentPlugin__::TransientLEDMode * { + auto led_mode = static_cast( + led_mode_management::generateLEDMode( + raw_buffer, + parent_plugin, + 0 /*dummy*/ + ) + ); + parent_plugin->registerLEDModeActivated(255 /* dymmy to disable access + to transient LED modes through LEDControl */ + ); + + return led_mode; + } +#endif + + private: + + + // For the sake of convenience make type LEDModeFactory + // available in class namespace + // + typedef led_mode_management::LEDModeFactory LEDModeFactory; + + static kaleidoscope::plugin::LEDMode *getLEDMode(uint8_t mode_id); + + static void retreiveLEDModeFactoryFromPROGMEM(uint8_t mode_id, + LEDModeFactory &fac); + + static void setupPersistentLEDModes(); + + // Persistent LED mode plugins are derived from kaleidoscope::plugin::LEDMode. + // The standard dictates that for them this more specialized overload + // of setupLEDMode is supposed to be called instead of the more + // general one below. + // + static void setupLEDMode(kaleidoscope::plugin::LEDMode *persistent_led_mode) { + persistent_led_mode->setup(); + } + + static void setupLEDMode(kaleidoscope::Plugin */*not_a_persistent_led_mode*/) {} + + static uint8_t led_mode_buffer_[]; +}; + +} // end namespace internal +} // end namespace kaleidoscope + +// Some auxiliary macros that are mapped to the list of +// plugins defined via KALEIDOSCOPE_INIT_PLUGINS follow. + +// Evaluates to a boolean value that signals whether +// a plugin is related to LED modes and thus needs to be considered +// during setup of the LED mode factory array. +// +#define _LED_MODE_MANAGEMENT__PLUGIN_CONTROLS_LED_MODE(PLUGIN) \ + pluginControlsLEDMode(ledModePluginType(&::PLUGIN)) + +// Generates a LEDModeFactory for each plugin. For all those +// plugins that do not export transient LED modes, a nullptr-initialized +// factory is created. This does not mean a performance penalty, as +// those empty factories are then directly discarded, still at compile time. +// +#define _LED_MODE_MANAGEMENT__GENERATE_LED_MODE_FACTORY(PLUGIN) \ + kaleidoscope::internal::led_mode_management __NL__ \ + ::GenerateLEDModeFactory::apply(&::PLUGIN) + +// Retreive the pointer type of the exported transient LED mode of a plugin. +// +#define _LED_MODE_MANAGEMENT__PLUGIN_PTR_TYPE(PLUGIN) \ + decltype(&::PLUGIN) + +#define _LED_MODE_MANAGEMENT__SETUP_STATIC_LED_MODE(PLUGIN) \ + LEDModeManager::setupLEDMode(&::PLUGIN); + +#define _INIT_LED_MODE_MANAGER(...) \ + namespace kaleidoscope { __NL__ \ + namespace internal { __NL__ \ + namespace led_mode_management { __NL__ \ + __NL__ \ + /* Setup the array-like data structure that stores __NL__ \ + * LEDModeFactories */ __NL__ \ + typedef kaleidoscope::internal::ArrayLikeStorage< __NL__ \ + LEDModeFactory, __NL__ \ + /* Map the list of global plugin __NL__ \ + * objects to a list of led mode __NL__ \ + * factories */ __NL__ \ + MAP_LIST( __NL__ \ + /* Generate a series of boolean values. True for __NL__ \ + * each plugin that is related to LED modes, __NL__ \ + * false otherwise. */ __NL__ \ + _LED_MODE_MANAGEMENT__PLUGIN_CONTROLS_LED_MODE, __NL__ \ + __VA_ARGS__ __NL__ \ + ) __NL__ \ + > ArrayType; __NL__ \ + __NL__ \ + /* Generate the actual instance of template class __NL__ \ + * TypedPluginArray and initialize it with a list of __NL__ \ + * LEDModeFactories. __NL__ \ + */ __NL__ \ + const PROGMEM ArrayType led_mode_factories( __NL__ \ + MAP_LIST( __NL__ \ + _LED_MODE_MANAGEMENT__GENERATE_LED_MODE_FACTORY, __NL__ \ + __VA_ARGS__ __NL__ \ + ) __NL__ \ + ); __NL__ \ + } /* end namespace led_mode_management */ __NL__ \ + __NL__ \ + /* Store the number of LEDModeFactories. __NL__ \ + */ __NL__ \ + const PROGMEM uint8_t num_led_modes __NL__ \ + = led_mode_management::ArrayType::n_entries; __NL__ \ + __NL__ \ + uint8_t LEDModeManager::numLEDModes() { __NL__ \ + return pgm_read_byte(&num_led_modes); __NL__ \ + } __NL__ \ + __NL__ \ + void LEDModeManager::retreiveLEDModeFactoryFromPROGMEM( __NL__ \ + uint8_t mode_id, __NL__ \ + LEDModeFactory &fac) { __NL__ \ + memcpy_P(&fac, __NL__ \ + &reinterpret_cast( __NL__ \ + &led_mode_management::led_mode_factories __NL__ \ + )[mode_id], __NL__ \ + sizeof(LEDModeFactory) __NL__ \ + ); __NL__ \ + } __NL__ \ + __NL__ \ + static constexpr size_t max_led_mode_size __NL__ \ + = led_mode_management::TransientLEDModeMaxSize< __NL__ \ + MAP_LIST( __NL__ \ + _LED_MODE_MANAGEMENT__PLUGIN_PTR_TYPE, __NL__ \ + __VA_ARGS__ __NL__ \ + ) __NL__ \ + >::value; __NL__ \ + __NL__ \ + /* This buffer is dimensioned in a way that it can hold __NL__ \ + * the largest of all transient LED modes defined in the sketch. __NL__ \ + */ __NL__ \ + uint8_t LEDModeManager::led_mode_buffer_[max_led_mode_size]; __NL__ \ + __NL__ \ + void LEDModeManager::setupPersistentLEDModes() { __NL__ \ + MAP( __NL__ \ + _LED_MODE_MANAGEMENT__SETUP_STATIC_LED_MODE, __NL__ \ + __VA_ARGS__ __NL__ \ + ) __NL__ \ + } __NL__ \ + } /* end namespace internal */ __NL__ \ + } /* end namespace kaleidoscope */ diff --git a/src/kaleidoscope_internal/array_like_storage.h b/src/kaleidoscope_internal/array_like_storage.h new file mode 100644 index 00000000..10ad9222 --- /dev/null +++ b/src/kaleidoscope_internal/array_like_storage.h @@ -0,0 +1,114 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2013-2018 Keyboard.io, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include + +namespace kaleidoscope { +namespace internal { + +// The ArrayLikeStorage class stores data of a specific +// type in a recursive fashion. It is used to generate array like +// data structures at compile time that are initialized from a +// list of entities only a subset of which is supposed to be +// stored in the container. +// +template +class ArrayLikeStorage { + + public: + + typedef ArrayLikeStorage NestedArray; + + template + constexpr ArrayLikeStorage(StoredType__ entry, MoreEntities__...more_entities) + : entry_(entry), + nested_array_(more_entities...) {} + + static constexpr uint8_t n_entries + = NestedArray::n_entries + 1; + + typedef StoredType__ ContentType; + + private: + StoredType__ entry_; + NestedArray nested_array_; +} __attribute__((packed)); // Make sure that there are no padding +// bytes added by the compiler. +// This is important to let the class +// have the same layout as a POD array. + + +template +class ArrayLikeStorage { + + public: + + typedef ArrayLikeStorage NestedArray; + + template + constexpr ArrayLikeStorage(AnyType__/* non-matching entity */, + MoreEntities__...more_entities) + : nested_array_(more_entities...) {} + + static constexpr uint8_t n_entries + = NestedArray::n_entries; + + typedef StoredType__ ContentType; + + private: + + NestedArray nested_array_; +} __attribute__((packed)); + +template +struct ArrayLikeStorage { + + public: + + constexpr ArrayLikeStorage(StoredType__ entry) + : entry_(entry) + {} + + static constexpr uint8_t n_entries = 1; + + typedef StoredType__ ContentType; + + private: + StoredType__ entry_; +} __attribute__((packed)); + +template +struct ArrayLikeStorage { + + public: + + template + constexpr ArrayLikeStorage(AnyType__/* non-matching entity */) {} + + static constexpr uint8_t n_entries = 0; + + typedef StoredType__ ContentType; +} __attribute__((packed)); + +} // end namespace internal +} // end namespace kaleidoscope diff --git a/src/kaleidoscope_internal/event_dispatch.h b/src/kaleidoscope_internal/event_dispatch.h index 84b271ae..a93a024f 100644 --- a/src/kaleidoscope_internal/event_dispatch.h +++ b/src/kaleidoscope_internal/event_dispatch.h @@ -198,4 +198,8 @@ __NL__ \ _PREPARE_EVENT_HANDLER_SIGNATURE_CHECK __NL__ \ __NL__ \ - _FOR_EACH_EVENT_HANDLER(_REGISTER_EVENT_HANDLER) + _FOR_EACH_EVENT_HANDLER(_REGISTER_EVENT_HANDLER) __NL__ \ + __NL__ \ + /* This generates a PROGMEM array-kind-of data structure that contains */ __NL__ \ + /* LEDModeFactory entries */ __NL__ \ + _INIT_LED_MODE_MANAGER(__VA_ARGS__)