diff --git a/CMakeLists.txt b/CMakeLists.txt index bfcedb53b5..c9cbc921b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,12 +268,15 @@ src/addons/gamepad_usb_host_listener.cpp src/addons/tg16_input.cpp src/animationstation/animation.cpp src/animationstation/animationstation.cpp +src/animationstation/effects/burstcolor.cpp src/animationstation/effects/chase.cpp -src/animationstation/effects/customtheme.cpp -src/animationstation/effects/customthemepressed.cpp +src/animationstation/effects/idletimeout.cpp +src/animationstation/effects/jigglestaticcolor.cpp +src/animationstation/effects/jiggletwostaticcolor.cpp +src/animationstation/effects/rain.cpp src/animationstation/effects/rainbow.cpp +src/animationstation/effects/randomcolor.cpp src/animationstation/effects/staticcolor.cpp -src/animationstation/effects/statictheme.cpp ${PROTO_OUTPUT_DIR}/enums.pb.c ${PROTO_OUTPUT_DIR}/config.pb.c ) diff --git a/configs/ASiLVLABC/BoardConfig.h b/configs/ASiLVLABC/BoardConfig.h index 9d632dc135..4ca1bd45d6 100644 --- a/configs/ASiLVLABC/BoardConfig.h +++ b/configs/ASiLVLABC/BoardConfig.h @@ -94,7 +94,6 @@ // buttons LED #define BOARD_LEDS_PIN 22 #define LED_BRIGHTNESS_MAXIMUM 50 -#define LED_BRIGHTNESS_STEPS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 diff --git a/configs/ASiLVLWASD/BoardConfig.h b/configs/ASiLVLWASD/BoardConfig.h index 28713e1df7..3582154024 100644 --- a/configs/ASiLVLWASD/BoardConfig.h +++ b/configs/ASiLVLWASD/BoardConfig.h @@ -94,7 +94,6 @@ // buttons LED #define BOARD_LEDS_PIN 22 #define LED_BRIGHTNESS_MAXIMUM 50 -#define LED_BRIGHTNESS_STEPS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 diff --git a/configs/BentoBox/BoardConfig.h b/configs/BentoBox/BoardConfig.h index 2afa7fceb6..e8fb2583ec 100644 --- a/configs/BentoBox/BoardConfig.h +++ b/configs/BentoBox/BoardConfig.h @@ -63,7 +63,6 @@ #define BOARD_LEDS_PIN 15 #define LED_BRIGHTNESS_MAXIMUM 255 -#define LED_BRIGHTNESS_STEPS 10 #define LEDS_PER_PIXEL 2 #define LEDS_DPAD_LEFT 0 diff --git a/configs/DuelPadZen/BoardConfig.h b/configs/DuelPadZen/BoardConfig.h index 5ea0d5aaaf..f767fe7df2 100644 --- a/configs/DuelPadZen/BoardConfig.h +++ b/configs/DuelPadZen/BoardConfig.h @@ -87,7 +87,6 @@ #define BOARD_LEDS_PIN 15 #define LED_BRIGHTNESS_MAXIMUM 100 - #define LED_BRIGHTNESS_STEPS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 2 #define LEDS_BASE_ANIMATION_INDEX 1 diff --git a/configs/FlatboxRev5RGB/BoardConfig.h b/configs/FlatboxRev5RGB/BoardConfig.h index fc08dd74a9..fa1e20bcaf 100644 --- a/configs/FlatboxRev5RGB/BoardConfig.h +++ b/configs/FlatboxRev5RGB/BoardConfig.h @@ -62,7 +62,6 @@ #define BOARD_LEDS_PIN 0 #define LED_BRIGHTNESS_MAXIMUM 50 -#define LED_BRIGHTNESS_STEPS 5 #define LEDS_BASE_ANIMATION_INDEX 1 #define LEDS_PER_PIXEL 2 diff --git a/configs/Haute42COSMOX/BoardConfig.h b/configs/Haute42COSMOX/BoardConfig.h index 83aed0d08a..a54d5aab09 100644 --- a/configs/Haute42COSMOX/BoardConfig.h +++ b/configs/Haute42COSMOX/BoardConfig.h @@ -78,7 +78,6 @@ #define BOARD_LEDS_PIN 28 #define LED_BRIGHTNESS_MAXIMUM 100 - #define LED_BRIGHTNESS_STEPS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 #define LEDS_BASE_ANIMATION_INDEX 1 @@ -292,13 +291,75 @@ {GP_ELEMENT_PIN_BUTTON, {39, 37, 4, 4, 1, 1, 9, GP_SHAPE_ELLIPSE}}\ } - // Keyboard Host enabled by default - #define KEYBOARD_HOST_ENABLED 1 +//Data format = {first led index, leds on this light, xcoord, ycoord, GPIO pin/case light Index, Type} +//Eg, your first light would be "first led index" = 0 and "leds on this light" = 2. +// your second light would be "first led index" = 2 (as 0 and 1 were just taken by the first light) +//T16 + +//LED order list on haute 42 t16 +//left +//down +//right +//up (thumb) +//b3 +//b4 +//r1 +//l1 +//b1 +//b2 +//r2 +//l2 +//m1 (extra button 1) +//l3 +//r3 +//m2 (extra button 2) + +//Defines for T16 +#define LIGHT_DATA_SIZE_DEFAULT 16 //number of sets in the below data +#define LIGHT_DATA_DEFAULT \ +0, 1, 0, 2, 5, LightType::LightType_ActionButton, \ +1, 1, 2, 2, 3, LightType::LightType_ActionButton, \ +2, 1, 4, 3, 4, LightType::LightType_ActionButton, \ +3, 1, 5, 7, 2, LightType::LightType_ActionButton, \ +4, 1, 6, 2, 10, LightType::LightType_ActionButton, \ +5, 1, 8, 1, 11, LightType::LightType_ActionButton, \ +6, 1, 10, 1, 12, LightType::LightType_ActionButton, \ +7, 1, 12, 1, 13, LightType::LightType_ActionButton, \ +8, 1, 6, 4, 6, LightType::LightType_ActionButton, \ +9, 1, 8, 3, 7, LightType::LightType_ActionButton, \ +10, 1, 10, 3, 8, LightType::LightType_ActionButton, \ +11, 1, 12, 3, 9, LightType::LightType_ActionButton, \ +12, 1, 3, 0, 27, LightType::LightType_ActionButton, \ +13, 1, 6, 0, 18, LightType::LightType_ActionButton, \ +14, 1, 8, 5, 19, LightType::LightType_ActionButton, \ +15, 1, 3, 6, 26, LightType::LightType_ActionButton +#define LIGHT_DATA_NAME_DEFAULT "Haute/Cosmox T16" + +//Defines for T12 +#define LIGHT_DATA_SIZE_1 12 //number of sets in the below data +#define LIGHT_DATA_1 \ +0, 1, 0, 2, 5, LightType::LightType_ActionButton, \ +1, 1, 2, 2, 3, LightType::LightType_ActionButton, \ +2, 1, 4, 3, 4, LightType::LightType_ActionButton, \ +3, 1, 5, 7, 2, LightType::LightType_ActionButton, \ +4, 1, 6, 2, 10, LightType::LightType_ActionButton, \ +5, 1, 8, 1, 11, LightType::LightType_ActionButton, \ +6, 1, 10, 1, 12, LightType::LightType_ActionButton, \ +7, 1, 12, 1, 13, LightType::LightType_ActionButton, \ +8, 1, 6, 4, 6, LightType::LightType_ActionButton, \ +9, 1, 8, 3, 7, LightType::LightType_ActionButton, \ +10, 1, 10, 3, 8, LightType::LightType_ActionButton, \ +11, 1, 12, 3, 9, LightType::LightType_ActionButton +#define LIGHT_DATA_NAME_1 "Haute/Cosmox T12" + + +// Keyboard Host enabled by default +#define KEYBOARD_HOST_ENABLED 1 - #define BOARD_LED_ENABLED 1 - #define BOARD_LED_TYPE ON_BOARD_LED_MODE_MODE_INDICATOR +#define BOARD_LED_ENABLED 1 +#define BOARD_LED_TYPE ON_BOARD_LED_MODE_MODE_INDICATOR - #define DEFAULT_SPLASH \ +#define DEFAULT_SPLASH \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ @@ -364,4 +425,4 @@ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - #endif \ No newline at end of file + #endif diff --git a/configs/Haute42COSMOXMLite/BoardConfig.h b/configs/Haute42COSMOXMLite/BoardConfig.h index 7a1b0c1b1c..0c75ad1a15 100644 --- a/configs/Haute42COSMOXMLite/BoardConfig.h +++ b/configs/Haute42COSMOXMLite/BoardConfig.h @@ -76,8 +76,7 @@ #define BOARD_LEDS_PIN 28 #define LED_BRIGHTNESS_MAXIMUM 200 -#define LEDS_BRIGHTNESS 200 -#define LED_BRIGHTNESS_STEPS 5 +#define LEDS_BRIGHTNESS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 #define LEDS_BASE_ANIMATION_INDEX 1 diff --git a/configs/Haute42COSMOXMUltra/BoardConfig.h b/configs/Haute42COSMOXMUltra/BoardConfig.h index 630ca6eb00..da148f282c 100644 --- a/configs/Haute42COSMOXMUltra/BoardConfig.h +++ b/configs/Haute42COSMOXMUltra/BoardConfig.h @@ -76,8 +76,7 @@ #define BOARD_LEDS_PIN 28 #define LED_BRIGHTNESS_MAXIMUM 200 -#define LEDS_BRIGHTNESS 200 -#define LED_BRIGHTNESS_STEPS 5 +#define LEDS_BRIGHTNESS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 #define LEDS_BASE_ANIMATION_INDEX 1 @@ -202,6 +201,56 @@ #define BOARD_LED_ENABLED 1 #define BOARD_LED_TYPE ON_BOARD_LED_MODE_MODE_INDICATOR +#define LIGHT_DATA_NAME_DEFAULT "Haute42|Cosmox M Ultra Gen 2" +#define LIGHT_DATA_SIZE_DEFAULT 46 //number of sets in the below data +#define LIGHT_DATA_DEFAULT \ +0, 1, 4, 4, 5, LightType::LightType_ActionButton, \ +1, 1, 6, 4, 3, LightType::LightType_ActionButton, \ +2, 1, 8, 5, 4, LightType::LightType_ActionButton, \ +3, 1, 9, 9, 2, LightType::LightType_ActionButton, \ +4, 1, 10, 4, 10, LightType::LightType_ActionButton, \ +5, 1, 12, 3, 11, LightType::LightType_ActionButton, \ +6, 1, 14, 3, 12, LightType::LightType_ActionButton, \ +7, 1, 16, 4, 13, LightType::LightType_ActionButton, \ +8, 1, 10, 6, 6, LightType::LightType_ActionButton, \ +9, 1, 12, 5, 7, LightType::LightType_ActionButton, \ +10, 1, 14, 5, 8, LightType::LightType_ActionButton, \ +11, 1, 16, 6, 9, LightType::LightType_ActionButton, \ +12, 1, 7, 2, 27, LightType::LightType_ActionButton, \ +13, 1, 7, 8, 18, LightType::LightType_ActionButton, \ +14, 1, 11, 8, 19, LightType::LightType_ActionButton, \ +15, 1, 2, 5, 26, LightType::LightType_ActionButton, \ +16, 1, 8, 0, 0, LightType::LightType_Case, \ +17, 1, 6, 0, 1, LightType::LightType_Case, \ +18, 1, 4, 0, 2, LightType::LightType_Case, \ +19, 1, 2, 0, 3, LightType::LightType_Case, \ +20, 1, 0, 0, 4, LightType::LightType_Case, \ +21, 1, 0, 2, 5, LightType::LightType_Case, \ +22, 1, 0, 4, 6, LightType::LightType_Case, \ +23, 1, 0, 6, 7, LightType::LightType_Case, \ +24, 1, 0, 8, 8, LightType::LightType_Case, \ +25, 1, 0, 10, 9, LightType::LightType_Case, \ +26, 1, 0, 12, 10, LightType::LightType_Case, \ +27, 1, 2, 12, 11, LightType::LightType_Case, \ +28, 1, 4, 12, 12, LightType::LightType_Case, \ +29, 1, 6, 12, 13, LightType::LightType_Case, \ +30, 1, 8, 12, 14, LightType::LightType_Case, \ +31, 1, 10, 12, 15, LightType::LightType_Case, \ +32, 1, 12, 12, 16, LightType::LightType_Case, \ +33, 1, 14, 12, 17, LightType::LightType_Case, \ +34, 1, 16, 12, 18, LightType::LightType_Case, \ +35, 1, 18, 12, 19, LightType::LightType_Case, \ +36, 1, 18, 10, 20, LightType::LightType_Case, \ +37, 1, 18, 8, 21, LightType::LightType_Case, \ +38, 1, 18, 6, 22, LightType::LightType_Case, \ +39, 1, 18, 4, 23, LightType::LightType_Case, \ +40, 1, 18, 2, 24, LightType::LightType_Case, \ +41, 1, 18, 0, 25, LightType::LightType_Case, \ +42, 1, 16, 0, 26, LightType::LightType_Case, \ +43, 1, 14, 0, 27, LightType::LightType_Case, \ +44, 1, 12, 0, 28, LightType::LightType_Case, \ +45, 1, 10, 0, 29, LightType::LightType_Case + #define DEFAULT_SPLASH \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ diff --git a/configs/Haute42COSMOXXAnalog/BoardConfig.h b/configs/Haute42COSMOXXAnalog/BoardConfig.h index c9909a9719..e3f5e319cd 100644 --- a/configs/Haute42COSMOXXAnalog/BoardConfig.h +++ b/configs/Haute42COSMOXXAnalog/BoardConfig.h @@ -79,7 +79,6 @@ #define GPIO_PIN_14 GpioAction::BUTTON_PRESS_TURBO #define LED_BRIGHTNESS_MAXIMUM 100 - #define LED_BRIGHTNESS_STEPS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 #define LEDS_BASE_ANIMATION_INDEX 1 diff --git a/configs/MiSTercadeV2/BoardConfig.h b/configs/MiSTercadeV2/BoardConfig.h index 4fcec02c24..849744f53b 100644 --- a/configs/MiSTercadeV2/BoardConfig.h +++ b/configs/MiSTercadeV2/BoardConfig.h @@ -64,7 +64,6 @@ #define BOARD_LEDS_PIN 28 // Addressible RGB LED for buttons #define LED_BRIGHTNESS_MAXIMUM 150 -#define LED_BRIGHTNESS_STEPS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 diff --git a/configs/Pico/BoardConfig.h b/configs/Pico/BoardConfig.h index ca2029f64d..3d6b7048b7 100644 --- a/configs/Pico/BoardConfig.h +++ b/configs/Pico/BoardConfig.h @@ -67,7 +67,6 @@ #define BOARD_LEDS_PIN 28 #define LED_BRIGHTNESS_MAXIMUM 100 -#define LED_BRIGHTNESS_STEPS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 diff --git a/configs/Pico2/BoardConfig.h b/configs/Pico2/BoardConfig.h index 348572961c..3171f8b4da 100644 --- a/configs/Pico2/BoardConfig.h +++ b/configs/Pico2/BoardConfig.h @@ -67,7 +67,6 @@ #define BOARD_LEDS_PIN 28 #define LED_BRIGHTNESS_MAXIMUM 100 -#define LED_BRIGHTNESS_STEPS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 diff --git a/configs/PicoAnn/BoardConfig.h b/configs/PicoAnn/BoardConfig.h index d88a4ecc07..6b2a70d394 100644 --- a/configs/PicoAnn/BoardConfig.h +++ b/configs/PicoAnn/BoardConfig.h @@ -70,7 +70,6 @@ #define BOARD_LEDS_PIN 15 #define LED_BRIGHTNESS_MAXIMUM 50 -#define LED_BRIGHTNESS_STEPS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 diff --git a/configs/RP2040AdvancedBreakoutBoardUSBPassthrough/BoardConfig.h b/configs/RP2040AdvancedBreakoutBoardUSBPassthrough/BoardConfig.h index 9636ff2c3a..fb021fc384 100644 --- a/configs/RP2040AdvancedBreakoutBoardUSBPassthrough/BoardConfig.h +++ b/configs/RP2040AdvancedBreakoutBoardUSBPassthrough/BoardConfig.h @@ -73,7 +73,6 @@ #define BOARD_LEDS_PIN 4 #define LED_BRIGHTNESS_MAXIMUM 100 -#define LED_BRIGHTNESS_STEPS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 #define LEDS_BASE_ANIMATION_INDEX 1 diff --git a/configs/SGFDevices/BoardConfig.h b/configs/SGFDevices/BoardConfig.h index ea4c20b7ce..f4213e2ce9 100644 --- a/configs/SGFDevices/BoardConfig.h +++ b/configs/SGFDevices/BoardConfig.h @@ -70,7 +70,6 @@ #define BOARD_LEDS_PIN 7 #define LED_BRIGHTNESS_MAXIMUM 255 -#define LED_BRIGHTNESS_STEPS 10 #define LEDS_PER_PIXEL 2 #define LEDS_DPAD_LEFT 0 diff --git a/configs/ZeroRhythm/BoardConfig.h b/configs/ZeroRhythm/BoardConfig.h index 6215433945..2cf6cb622d 100644 --- a/configs/ZeroRhythm/BoardConfig.h +++ b/configs/ZeroRhythm/BoardConfig.h @@ -63,7 +63,6 @@ #define BOARD_LEDS_PIN 0 #define LED_BRIGHTNESS_MAXIMUM 100 -#define LED_BRIGHTNESS_STEPS 5 #define LED_FORMAT LED_FORMAT_GRB #define LEDS_PER_PIXEL 1 #define LEDS_BASE_ANIMATION_INDEX 1 diff --git a/headers/addons/neopicoleds.h b/headers/addons/neopicoleds.h index 616fcefe5c..f2706c5aae 100644 --- a/headers/addons/neopicoleds.h +++ b/headers/addons/neopicoleds.h @@ -22,6 +22,8 @@ #include "animationstation.h" #include "NeoPico.h" +#include "playerleds.h" + #include "enums.pb.h" #ifndef BOARD_LEDS_PIN @@ -36,12 +38,85 @@ #define LED_FORMAT LED_FORMAT_GRB #endif +#ifndef LIGHT_DATA_SIZE_DEFAULT +#define LIGHT_DATA_SIZE_DEFAULT 0 +#endif +#ifndef LIGHT_DATA_DEFAULT +#define LIGHT_DATA_DEFAULT 0,0,0,0,0,LightType::LightType_ActionButton +#endif +#ifndef LIGHT_DATA_NAME_DEFAULT +#define LIGHT_DATA_NAME_DEFAULT "" +#endif +#ifndef LIGHT_DATA_SIZE_1 +#define LIGHT_DATA_SIZE_1 0 +#endif +#ifndef LIGHT_DATA_1 +#define LIGHT_DATA_1 0,0,0,0,0,LightType::LightType_ActionButton +#endif +#ifndef LIGHT_DATA_NAME_1 +#define LIGHT_DATA_NAME_1 "" +#endif +#ifndef LIGHT_DATA_SIZE_2 +#define LIGHT_DATA_SIZE_2 0 +#endif +#ifndef LIGHT_DATA_2 +#define LIGHT_DATA_2 0,0,0,0,0,LightType::LightType_ActionButton +#endif +#ifndef LIGHT_DATA_NAME_2 +#define LIGHT_DATA_NAME_2 "" +#endif +#ifndef LIGHT_DATA_SIZE_3 +#define LIGHT_DATA_SIZE_3 0 +#endif +#ifndef LIGHT_DATA_3 +#define LIGHT_DATA_3 0,0,0,0,0,LightType::LightType_ActionButton +#endif +#ifndef LIGHT_DATA_NAME_3 +#define LIGHT_DATA_NAME_3 "" +#endif +#ifndef LIGHT_DATA_SIZE_4 +#define LIGHT_DATA_SIZE_4 0 +#endif +#ifndef LIGHT_DATA_4 +#define LIGHT_DATA_4 0,0,0,0,0,LightType::LightType_ActionButton +#endif +#ifndef LIGHT_DATA_NAME_4 +#define LIGHT_DATA_NAME_4 "" +#endif +#ifndef LIGHT_DATA_SIZE_5 +#define LIGHT_DATA_SIZE_5 0 +#endif +#ifndef LIGHT_DATA_5 +#define LIGHT_DATA_5 0,0,0,0,0,LightType::LightType_ActionButton +#endif +#ifndef LIGHT_DATA_NAME_5 +#define LIGHT_DATA_NAME_5 "" +#endif +#ifndef LIGHT_DATA_SIZE_6 +#define LIGHT_DATA_SIZE_6 0 +#endif +#ifndef LIGHT_DATA_6 +#define LIGHT_DATA_6 0,0,0,0,0,LightType::LightType_ActionButton +#endif +#ifndef LIGHT_DATA_NAME_6 +#define LIGHT_DATA_NAME_6 "" +#endif +#ifndef LIGHT_DATA_SIZE_7 +#define LIGHT_DATA_SIZE_7 0 +#endif +#ifndef LIGHT_DATA_7 +#define LIGHT_DATA_7 0,0,0,0,0,LightType::LightType_ActionButton +#endif +#ifndef LIGHT_DATA_NAME_7 +#define LIGHT_DATA_NAME_7 "" +#endif + #ifndef LEDS_PER_PIXEL #define LEDS_PER_PIXEL 1 #endif #ifndef LEDS_BRIGHTNESS -#define LEDS_BRIGHTNESS 75 +#define LEDS_BRIGHTNESS -1 #endif #ifndef LEDS_BASE_ANIMATION_INDEX @@ -76,10 +151,6 @@ #define LED_BRIGHTNESS_MAXIMUM 128 #endif -#ifndef LED_BRIGHTNESS_STEPS -#define LED_BRIGHTNESS_STEPS 5 -#endif - #ifndef LEDS_DPAD_LEFT #define LEDS_DPAD_LEFT -1 #endif @@ -156,58 +227,6 @@ #define LEDS_TURN_OFF_WHEN_SUSPENDED 0 #endif -#ifndef CASE_RGB_TYPE -#define CASE_RGB_TYPE CASE_RGB_TYPE_NONE -#endif - -#ifndef CASE_RGB_INDEX -#define CASE_RGB_INDEX -1 -#endif - -#ifndef CASE_RGB_COUNT -#define CASE_RGB_COUNT 0 -#endif - -#ifndef AMBIENT_LIGHT_EFFECT -#define AMBIENT_LIGHT_EFFECT AL_CUSTOM_EFFECT_STATIC_COLOR -#endif - -#ifndef AMBIENT_STATIC_COLOR_BRIGHTNESS -#define AMBIENT_STATIC_COLOR_BRIGHTNESS 1.00f -#endif - -#ifndef AMBIENT_GRADIENT_COLOR_BRIGHTNESS -#define AMBIENT_GRADIENT_COLOR_BRIGHTNESS 1.00f -#endif - -#ifndef AMBIENT_CHASE_COLOR_BRIGHTNESS -#define AMBIENT_CHASE_COLOR_BRIGHTNESS 1.00f -#endif - -#ifndef AMBIENT_CUSTOM_THEME_BRIGHTNESS -#define AMBIENT_CUSTOM_THEME_BRIGHTNESS 1.00f -#endif - -#ifndef AMBIENT_GRADIENT_SPEED -#define AMBIENT_GRADIENT_SPEED 2 -#endif - -#ifndef AMBIENT_CHASE_SPEED -#define AMBIENT_CHASE_SPEED 100 -#endif - -#ifndef AMBIENT_BREATH_SPEED -#define AMBIENT_BREATH_SPEED 0.01f -#endif - -#ifndef AMBIENT_CUSTOM_THEME -#define AMBIENT_CUSTOM_THEME 0 -#endif - -#ifndef AMBIENT_STATIC_COLOR -#define AMBIENT_STATIC_COLOR ANIMATION_COLOR_PURPLE -#endif - // Neo Pixel needs to tie into PlayerLEDS led Levels class NeoPicoPlayerLEDs : public PlayerLEDs { @@ -222,56 +241,65 @@ class NeoPicoPlayerLEDs : public PlayerLEDs // NeoPico LED Addon class NeoPicoLEDAddon : public GPAddon { public: - virtual bool available(); - virtual void setup(); - virtual void preprocess() {} - virtual void process(); - virtual void postprocess(bool sent) {} + + //GP Addon functions + virtual bool available(); + virtual void setup(); + virtual void preprocess() {} + virtual void process(); + virtual void postprocess(bool sent) {} virtual void reinit() {} - virtual std::string name() { return NeoPicoLEDName; } - void ambientLightLinkage(); - + virtual std::string name() { return NeoPicoLEDName; } + + static void AssignLedPreset(const unsigned char* data, int32_t dataSize); + static void RestartLedSystem() { bRestartLeds = true; } + + uint32_t frame[FRAME_MAX]; private: - std::vector * getLEDPositions(std::string button, std::vector> *positions); - std::vector> generatedLEDButtons(std::vector> *positions); - std::vector> generatedLEDStickless(std::vector> *positions); - std::vector> generatedLEDWasd(std::vector> *positions); - std::vector> generatedLEDWasdFBM(std::vector> *positions); - std::vector> createLEDLayout(ButtonLayout layout, uint8_t ledsPerPixel, uint8_t ledButtonCount); - uint8_t setupButtonPositions(); - GamepadHotkey animationHotkeys(Gamepad *gamepad); - void ambientHotkeys(Gamepad *gamepad); - void ambientLightCustom(); - const uint32_t intervalMS = 10; - absolute_time_t nextRunTime; - int ledCount; - int buttonLedCount; - PixelMatrix matrix; - NeoPico neopico; - PLEDAnimationState animationState; // NeoPico can control the player LEDs - NeoPicoPlayerLEDs * neoPLEDs = nullptr; - AnimationStation as; - std::map buttonPositions; - PLEDType ledType; - GamepadHotkey lastAmbientAction; - uint32_t frame[100]; - - // Ambient neopico leds - float alBrightnessBreathX; - uint8_t breathLedEffectCycle; - bool alReverse; - int alCurrentFrame; - int alFrameToRGB; - int alFrameSpeed; - RGB ambientLight; - absolute_time_t nextRunTimeAmbientLight; - uint8_t chaseLightIndex; - uint8_t chaseLightMaxIndexPos; - - uint8_t multipleOfButtonLedsCount; - uint8_t remainderOfButtonLedsCount; - - uint8_t alLinkageStartIndex; + + void decompressSettings(); + + void configureLEDs(); + + GamepadHotkey ProcessAnimationHotkeys(Gamepad *gamepad); + + //Legacy setup functions + void generateLegacyIndividualLight(int firstLedIndex, int xCoord, int yCoord, uint8_t ledsPerPixel, GpioAction actionButton); + void generatedLEDButtons(std::vector> *positions, uint8_t ledsPerPixel); + void generatedLEDStickless(std::vector> *positions, uint8_t ledsPerPixel); + void generatedLEDWasd(std::vector> *positions, uint8_t ledsPerPixel); + void generatedLEDWasdFBM(std::vector> *positions, uint8_t ledsPerPixel); + void createLEDLayout(ButtonLayout layout, uint8_t ledsPerPixel, uint8_t ledButtonCount); + uint8_t setupButtonPositions(); + + //New co-ordinated setup + void GenerateLights(); + + //Controls the actual lights on the board. Writes out state each frame + NeoPico neopico; + + //Classes to control the player LEDS + PLEDAnimationState animationState; // NeoPico can control the player LEDs + NeoPicoPlayerLEDs * neoPLEDs = nullptr; + + //Data representation of the lights + Lights RGBLights; + + //Animation class. Handles idle animations, special move animations and pressed button effects + class AnimationStation AnimStation; + + const uint32_t intervalMS = 25; + absolute_time_t nextRunTime; + absolute_time_t lastRunTime; + uint8_t ledCount; + InputMode inputMode; // HACK + std::map buttonPositions; + bool turnOffWhenSuspended; + + bool bHasSetupNeoPico = false; + + //Webconfig/testing + static bool bRestartLeds; }; #endif diff --git a/headers/animationstation/animation.h b/headers/animationstation/animation.h index e8ad551908..31dc1bd12e 100644 --- a/headers/animationstation/animation.h +++ b/headers/animationstation/animation.h @@ -10,6 +10,8 @@ #include "NeoPico.h" #include +#include "enums.pb.h" + struct RGB { // defaults allows trivial constructor, avoiding compiler complaints and avoiding unnessecary initialization // animation always memsets the frame before use, to this is safe. @@ -83,7 +85,6 @@ struct RGB { } }; -// Also defined in Enums.proto constexpr RGB ColorBlack(0, 0, 0); constexpr RGB ColorWhite(255, 255, 255); constexpr RGB ColorRed(255, 0, 0); @@ -98,55 +99,91 @@ constexpr RGB ColorBlue(0, 0, 255); constexpr RGB ColorPurple(128, 0, 255); constexpr RGB ColorPink(255, 0, 255); constexpr RGB ColorMagenta(255, 0, 128); -constexpr RGB ColorIndigo(75, 0, 130); -constexpr RGB ColorViolet(238, 130, 238); inline const std::vector colors { ColorBlack, ColorWhite, ColorRed, ColorOrange, ColorYellow, ColorLimeGreen, ColorGreen, ColorSeafoam, ColorAqua, ColorSkyBlue, - ColorBlue, ColorPurple, ColorPink, ColorMagenta, ColorIndigo, - ColorViolet -}; + ColorBlue, ColorPurple, ColorPink, ColorMagenta }; + +#define MAX_CUSTOM_COLORS 16 +inline std::vector customColors { }; + +typedef enum +{ + BUTTONCASELIGHTTYPE_BUTTON_ONLY, + BUTTONCASELIGHTTYPE_CASE_ONLY, + BUTTONCASELIGHTTYPE_BUTTON_AND_CASE, +} EButtonCaseEffectType; class Animation { public: - Animation(PixelMatrix &matrix); - virtual void UpdatePixels(std::vector pixels); - void ClearPixels(); + Animation(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType); virtual ~Animation(){}; + //Which buttons are held at the moment + virtual void UpdatePressed(std::vector InPressedPins); + void ClearPressed(); + static LEDFormat format; - bool notInFilter(Pixel pixel); - virtual bool Animate(RGB (&frame)[100]) = 0; - void UpdateTime(); - void UpdatePresses(RGB (&frame)[100]); - void DecrementFadeCounter(int32_t index); + virtual void Animate(RGB (&frame)[FRAME_MAX]) = 0; + + //param adjustment + virtual void ParameterUp() {}; + virtual void ParameterDown() {}; - virtual void ParameterUp() = 0; - virtual void ParameterDown() = 0; + virtual void PressParameterUp() {}; + virtual void PressParameterDown() {}; - virtual void FadeTimeUp(); - virtual void FadeTimeDown(); + virtual void SetOptionalParams(uint32_t OptionalParams) {}; - RGB BlendColor(RGB start, RGB end, uint32_t frame); + virtual bool IsFinished() { return false; } //ready for delete? Only applicable to special move anims really protected: -/* We track both the full matrix as well as individual pixels here to support -button press changes. Rather than adjusting the matrix to represent a subset of pixels, -we provide a subset of pixels to use as a filter. */ - PixelMatrix *matrix; - std::vector pixels; - bool filtered = false; + + //gets current frame time + void UpdateTime(); + + //Update timers for pressed buttons this frame + void UpdatePresses(); + void DecrementFadeCounters(); + + //notifies + virtual void NewPressForPin(int lightIndex) {}; + + RGB BlendColor(RGB start, RGB end, float alpha); + RGB FadeColor(RGB start, RGB end, uint32_t TimeLeft); + + virtual int32_t GetFadeTime(); + + //Type Helpers + bool LightTypeIsForAnimation(LightType Type); + + //Get color helpers + virtual RGB GetNonPressedColorForLight(uint32_t LightIndex); + virtual RGB GetPressedColorForLight(uint32_t LightIndex); + virtual RGB GetColorForIndex(uint32_t ColorIndex); + + //Light data + Lights* RGBLights; + + //Is this running as a button pressed animation + bool isButtonAnimation = false; + + //Pins currently pressed + std::vector pressedPins; // Color fade - RGB defaultColor = ColorBlack; - static std::map times; - static std::map hitColor; + std::vector fadeTimes; + absolute_time_t lastUpdateTime = nil_time; - uint32_t coolDownTimeInMs = 1000; + + uint32_t holdTimeInMs = 1000; + uint32_t fadeoutTimeInMs = 1000; + int64_t updateTimeInMs = 20; + EButtonCaseEffectType ButtonCaseEffectType = EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_AND_CASE; }; #endif diff --git a/headers/animationstation/animationstation.h b/headers/animationstation/animationstation.h index 4eb7d69a18..e552b15716 100644 --- a/headers/animationstation/animationstation.h +++ b/headers/animationstation/animationstation.h @@ -6,72 +6,151 @@ #include #include #include +#include #include "hardware/clocks.h" #include "NeoPico.h" #include "animation.h" -#include "effects/chase.h" -#include "effects/customtheme.h" -#include "effects/customthemepressed.h" -#include "effects/rainbow.h" -#include "effects/staticcolor.h" -#include "effects/statictheme.h" #include "config.pb.h" -#include "enums.pb.h" -const int TOTAL_EFFECTS = 4; // Exclude custom theme until verified present +#define MAX_ANIMATION_PROFILES 4 +#define MAX_ANIMATION_PROFILES_INCLUDING_TEST (MAX_ANIMATION_PROFILES+1) +#define MAX_CASE_LIGHTS 40 //this should be divisible by 4 as we pack 4 indexes into one config int32 + +typedef enum +{ + AnimationStation_TestModeInvalid, + AnimationStation_TestModeOff, + AnimationStation_TestModeButtons, + AnimationStation_TestModeLayout, + AnimationStation_TestModeProfilePreview, +} AnimationStationTestMode; + +struct __attribute__ ((__packed__)) AnimationProfile_Unpacked +{ + bool bEnabled = false; + + AnimationNonPressedEffects baseNonPressedEffect; + AnimationPressedEffects basePressedEffect; + AnimationNonPressedEffects baseCaseEffect; + + int16_t baseCycleTime; + int16_t basePressedCycleTime; + + uint32_t notPressedStaticColors[NUM_BANK0_GPIOS + 3]; //since we pack 4 into each. Adding 3 ensures we have space for extra pading + uint32_t pressedStaticColors[NUM_BANK0_GPIOS + 3]; //since we pack 4 into each. Adding 3 ensures we have space for extra pading + + uint32_t caseStaticColors[MAX_CASE_LIGHTS]; + + uint32_t buttonPressHoldTimeInMs; + uint32_t buttonPressFadeOutTimeInMs; + + uint32_t nonPressedSpecialColor; + uint32_t pressedSpecialColor; + + bool bUseCaseLightsInPressedAnimations; +}; + +struct __attribute__ ((__packed__)) AnimationOptions_Unpacked +{ + uint32_t checksum; + uint8_t NumValidProfiles; + AnimationProfile_Unpacked profiles[MAX_ANIMATION_PROFILES_INCLUDING_TEST]; + uint8_t brightness; + int8_t baseProfileIndex; + uint32_t autoDisableTime; +}; class AnimationStation { public: - AnimationStation(); - void Animate(); - void HandleEvent(GamepadHotkey action); - void Clear(); - void ChangeAnimation(int changeSize); - void ApplyBrightness(uint32_t *frameValue); - uint16_t AdjustIndex(int changeSize); - void HandlePressed(std::vector pressed); - void ClearPressed(); - void SetMode(uint8_t mode); - void SetMatrix(PixelMatrix matrix); - void ConfigureBrightness(uint8_t max, uint8_t steps); - float GetBrightnessX(); - float GetLinkageModeOfBrightnessX(); - uint8_t GetBrightness(); - void SetBrightness(uint8_t brightness); - void DecreaseBrightness(); - void IncreaseBrightness(); - void DimBrightnessTo0(); - uint8_t GetBrightnessSteps(){ return this->brightnessSteps; }; - uint8_t GetCustomBrightnessStepsSize(){ return (brightnessMax / brightnessSteps); }; - RGB linkageFrame[100]; // copy baseAnimation frame exclude buttonAnimation frame - -private: - Animation* baseAnimation; - Animation* buttonAnimation; - std::vector lastPressed; - absolute_time_t nextChange; - uint8_t effectCount; - RGB frame[100]; - bool ambientLightEffectsChangeFlag = false; - bool ambientLightOnOffFlag = false; - bool ambientLightLinkageOnOffFlag = false; - bool aleLedsBrightnessCustomXupFlag = false; - bool aleLedsBrightnessCustomXDownFlag = false; - bool aleLedsParameterCustomUpFlag = false; - bool aleLedsParameterCustomDownFlag = false; - bool alGradientChaseBreathSpeedUpFlag = false; - bool alGradientChaseBreathSpeedDownFlag = false; - bool alCustomLinkageModeFlag = false; - uint8_t getBrightnessStepSize() { return (brightnessMax / brightnessSteps); } - uint8_t getLinkageModeOfBrightnessStepSize() { return (255 / brightnessSteps); } - float linkageModeOfBrightnessX; - uint8_t brightnessMax; - uint8_t brightnessSteps; - float brightnessX; - PixelMatrix matrix; + AnimationStation(); + + void Animate(); + void HandleEvent(GamepadHotkey action); + void Clear(); + void ApplyBrightness(uint32_t *frameValue); + + //Change profiles + void ChangeProfile(int changeSize); + uint16_t AdjustIndex(int changeSize); + + //What buttons (physical gpio pins) are pressed this frame + void HandlePressedPins(std::vector pressedPins); + + //What buttons (logical ones) are pressed this frame + void HandlePressedButtons(uint32_t pressedButtons); + + int8_t GetMode(); + void SetMode(int8_t mode); + void SetLights(Lights InRGBLights); + + //Brightness settings + static void SetMaxBrightness(uint8_t max); + static float GetNormalisedBrightness(); + static uint8_t GetBrightnessStepValue(); + static void SetBrightnessStepValue(uint8_t brightness); + static void DecreaseBrightnessByStep(); + static void IncreaseBrightnessByStep(); + static void DimBrightnessTo0(); + + static void DecompressProfile(int ProfileIndex, const AnimationProfile* ProfileToDecompress); + void DecompressSettings(); + void CheckForOptionsUpdate(); + + //Testing/webconfig + static void SetTestMode(AnimationStationTestMode TestType, const AnimationProfile* TestProfile); + static void SetTestPinState(int PinOrCaseIndex, bool IsCaseLight); + + //Running non-pressed animation + Animation* baseAnimation; + + //Running case animation + Animation* caseAnimation; + + //Running pressed animation + Animation* buttonAnimation; + + //Buttons pressed (physical gipo pins) last frame, used when changing button theme so starts initialised + std::vector lastPressed; + + static AnimationOptions_Unpacked options; + + static absolute_time_t nextChange; + + //Color of all lights this frame + RGB frame[FRAME_MAX]; + + static uint8_t brightnessSteps; +protected: + inline static uint8_t getBrightnessStepSize() { return (brightnessMax / brightnessSteps); } + static uint8_t brightnessMax; //0-255 + static float normalisedBrightness; //0-1 + + Animation* GetNonPressedEffectForEffectType(AnimationNonPressedEffects EffectType, EButtonCaseEffectType InButtonCaseEffectType); + + //webconfig test mode + void UpdateTestMode(); + + void UpdateTimeout(); + + //Light data + Lights RGBLights; + + //options/save + absolute_time_t timeAnimationSaveSet; + bool bAnimConfigSaveNeeded = false; + + //idletimeout + absolute_time_t timeLastButtonPressed; + bool bIsInIdleTimeout = false; + + //Testing/webconfig + static AnimationStationTestMode TestMode; + static bool bTestModeChangeRequested; + static int TestModePinOrCaseIndex; + static bool TestModeLightIsCase; }; #endif diff --git a/headers/animationstation/animationstorage.h b/headers/animationstation/animationstorage.h deleted file mode 100644 index 0ada770400..0000000000 --- a/headers/animationstation/animationstorage.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _ANIMATION_STORAGE_H_ -#define _ANIMATION_STORAGE_H_ -/* -#include "animationstation.h" - -class AnimationStorage -{ - public: - void save(); - AnimationOptions getAnimationOptions(); -}; - -static AnimationStorage AnimationStore; -*/ -#endif diff --git a/headers/animationstation/effects/burstcolor.h b/headers/animationstation/effects/burstcolor.h new file mode 100644 index 0000000000..944d453e7f --- /dev/null +++ b/headers/animationstation/effects/burstcolor.h @@ -0,0 +1,64 @@ +#ifndef _BURST_COLOR_H_ +#define _BURST_COLOR_H_ + +#include +#include +#include +#include "../animation.h" +#include "../animationstation.h" + +struct FBurstData +{ + int XPos; + int YPos; + RGB StartColor; + float RunningTime = -1.0f; +}; + +struct FGridEntry +{ + RGB Color; + float Strength = 0.0f; + + FGridEntry() + { + Color = RGB(0,0,0); + }; +}; + +#define MAX_BURSTS 16 +#define BURST_DISTANCE 6 +#define MIN_TIME_BETWEEN_BURSTS_ON_BUTTON_IN_MS 100 +#define BURST_DISTANCE_PER_SEC 10.0f + +class BurstColor : public Animation { + +public: + BurstColor(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType); + BurstColor(Lights& InRGBLights, bool bInRandomColor, bool bInSmallBurst, std::vector &InPressedPins, EButtonCaseEffectType InButtonCaseEffectType); + ~BurstColor() { }; + + virtual void Animate(RGB (&frame)[FRAME_MAX]) override; + + virtual void PressParameterUp() override; + virtual void PressParameterDown() override; + +protected: + + virtual int32_t GetFadeTime() { return MIN_TIME_BETWEEN_BURSTS_ON_BUTTON_IN_MS; } + + virtual void NewPressForPin(int lightIndex) override; + + FBurstData RunningBursts[MAX_BURSTS]; + + bool bRandomColor; + + bool bSmallBurst; + + int MinXCoord = 0; + int MinYCoord = 0; + int MaxXCoord = 0; + int MaxYCoord = 0; +}; + +#endif diff --git a/headers/animationstation/effects/chase.h b/headers/animationstation/effects/chase.h index 0b9ad1df5a..f4f5a98687 100644 --- a/headers/animationstation/effects/chase.h +++ b/headers/animationstation/effects/chase.h @@ -8,22 +8,67 @@ #include #include "animationstation.h" +//Chase lights the lights up in a specific order. 2 lights can be on at once as they overlaps on off times. There are multiple directions that can be used. + +//List of chase types +typedef enum +{ + CHASETYPES_SEQUENTIAL, + CHASETYPES_SEQUENTIAL_PINGPONG, + CHASETYPES_LEFT_TO_RIGHT, + CHASETYPES_RIGHT_TO_LEFT, + CHASETYPES_TOP_TO_BOTTOM, + CHASETYPES_BOTTOM_TO_TOP, + CHASETYPES_HORIZONTAL_PINGPONG, + CHASETYPES_VERTICAL_PINGPONG, + CHASETYPES_RANDOM, + CHASETYPES_MAX, + CHASETYPES_TESTLAYOUT, +} ChaseTypes; + +typedef enum +{ + SINGLECHASETYPES_SEQUENTIAL, + SINGLECHASETYPES_LEFT_TO_RIGHT, + SINGLECHASETYPES_RIGHT_TO_LEFT, + SINGLECHASETYPES_TOP_TO_BOTTOM, + SINGLECHASETYPES_BOTTOM_TO_TOP, + SINGLECHASETYPES_MAX, +} SingleChaseTypes; + class Chase : public Animation { public: - Chase(PixelMatrix &matrix); + Chase(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType, ChaseTypes InChaseType); ~Chase() {}; - bool Animate(RGB (&frame)[100]); - void ParameterUp(); - void ParameterDown(); + virtual void Animate(RGB (&frame)[FRAME_MAX]) override; + + virtual void ParameterUp() override; + virtual void ParameterDown() override; + + ChaseTypes ChaseTypeInUse = CHASETYPES_SEQUENTIAL; protected: - bool IsChasePixel(int i); - int WheelFrame(int i); - int currentFrame = 0; - int currentPixel = 0; - bool reverse = false; - absolute_time_t nextRunTime = nil_time; + + void CheckForEndOfSequence(); + + float ChaseTimes[2]; + std::vector OrderedLights; + + int MinXCoord = 0; + int MinYCoord = 0; + int MaxXCoord = 0; + int MaxYCoord = 0; + + int CurrentLight = 0; + + bool Reversed = false; + + SingleChaseTypes RandomChaseType; + + absolute_time_t NextRunTime = nil_time; + + bool TestLayoutFlipFlop = false; }; #endif diff --git a/headers/animationstation/effects/customtheme.h b/headers/animationstation/effects/customtheme.h deleted file mode 100644 index a25ad6a255..0000000000 --- a/headers/animationstation/effects/customtheme.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef CUSTOM_THEME_H_ -#define CUSTOM_THEME_H_ - -#include -#include "animation.h" -#include "animationstation.h" - -class CustomTheme : public Animation { -public: - CustomTheme(PixelMatrix &matrix); - ~CustomTheme() { }; - - bool HasTheme(); - bool Animate(RGB (&frame)[100]); - void ParameterUp(); - void ParameterDown(); -protected: - std::map theme; -}; - -#endif diff --git a/headers/animationstation/effects/customthemepressed.h b/headers/animationstation/effects/customthemepressed.h deleted file mode 100644 index 344e3b852b..0000000000 --- a/headers/animationstation/effects/customthemepressed.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef _CUSTOM_THEME_PRESSED_H_ -#define _CUSTOM_THEME_PRESSED_H_ - -#include -#include -#include "animation.h" -#include "animationstation.h" - -class CustomThemePressed : public Animation { -public: - CustomThemePressed(PixelMatrix &matrix); - CustomThemePressed(PixelMatrix &matrix, std::vector &pixels); - ~CustomThemePressed() { pixels = nullptr; }; - bool HasTheme(); - bool Animate(RGB (&frame)[100]); - void ParameterUp() { } - void ParameterDown() { } -protected: - std::vector *pixels; - RGB defaultColor = ColorBlack; - std::map theme; -}; - -#endif diff --git a/headers/animationstation/effects/idletimeout.h b/headers/animationstation/effects/idletimeout.h new file mode 100644 index 0000000000..e27fbebba6 --- /dev/null +++ b/headers/animationstation/effects/idletimeout.h @@ -0,0 +1,23 @@ +#ifndef _IDLE_TIMEOUT_H_ +#define _IDLE_TIMEOUT_H_ + +#include +#include +#include +#include "animation.h" +#include "animationstation.h" +#include "staticcolor.h" + +class IdleTimeout : public StaticColor { +public: + IdleTimeout(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType); + IdleTimeout(Lights& InRGBLights, std::vector &InPressedPins); + ~IdleTimeout() { }; + + +protected: + + virtual RGB GetNonPressedColorForLight(uint32_t LightIndex) override; +}; + +#endif diff --git a/headers/animationstation/effects/jigglestaticcolor.h b/headers/animationstation/effects/jigglestaticcolor.h new file mode 100644 index 0000000000..5507285bcd --- /dev/null +++ b/headers/animationstation/effects/jigglestaticcolor.h @@ -0,0 +1,22 @@ +#ifndef _JIGGLE_STATIC_COLOR_H_ +#define _JIGGLE_STATIC_COLOR_H_ + +#include +#include +#include +#include "../animation.h" +#include "../animationstation.h" +#include "staticcolor.h" + +class JiggleStaticColor : public StaticColor { +public: + JiggleStaticColor(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType); + JiggleStaticColor(Lights& InRGBLights, std::vector &InPressedPins); + ~JiggleStaticColor() { }; + + virtual RGB AdjustColor(RGB InColor) override; + +protected: +}; + +#endif diff --git a/headers/animationstation/effects/jiggletwostaticcolor.h b/headers/animationstation/effects/jiggletwostaticcolor.h new file mode 100644 index 0000000000..691ca8c195 --- /dev/null +++ b/headers/animationstation/effects/jiggletwostaticcolor.h @@ -0,0 +1,22 @@ +#ifndef _JIGGLE_TWO_STATIC_COLOR_H_ +#define _JIGGLE_TWO_STATIC_COLOR_H_ + +#include +#include +#include +#include "../animation.h" +#include "../animationstation.h" +#include "staticcolor.h" + +class JiggleTwoStaticColor : public StaticColor { +public: + JiggleTwoStaticColor(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType); + JiggleTwoStaticColor(Lights& InRGBLights, std::vector &InPressedPins); + ~JiggleTwoStaticColor() { }; + + virtual RGB AdjustColor(RGB InColor) override; + +protected: +}; + +#endif diff --git a/headers/animationstation/effects/rain.h b/headers/animationstation/effects/rain.h new file mode 100644 index 0000000000..11aa516764 --- /dev/null +++ b/headers/animationstation/effects/rain.h @@ -0,0 +1,58 @@ +#ifndef _RAIN_H_ +#define _RAIN_H_ + +#include "../animation.h" +#include "hardware/clocks.h" +#include +#include +#include +#include "../animationstation.h" + +//drips light down the grid. Matrix/rain + +typedef enum +{ + RAIN_LOW, + RAIN_MEDIUM, + RAIN_HIGH, +} ERainFrequency; + +#define MAX_RAIN_DROPS 20 +#define RAIN_DROP_NO_REPEAT_X_NUM 3 + +class Rain : public Animation { +public: + Rain(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType, ERainFrequency InRainFrequency = ERainFrequency::RAIN_MEDIUM); + ~Rain() {}; + + virtual void Animate(RGB (&frame)[FRAME_MAX]) override; + + virtual void ParameterUp() override; + virtual void ParameterDown() override; + +protected: + float GetTimeTillNextDrop(); + bool RainHistoryContains(int newXCoord); + int FindLightForCoord(int xCoord, int yCoord); + + float RainYCoords[MAX_RAIN_DROPS]; + int RainXCoords[MAX_RAIN_DROPS]; + + int previousRainDropXCoords[RAIN_DROP_NO_REPEAT_X_NUM]; + + float TimeTillNextRain = 0.0f; + float DefaultRainSpeed = 1.5f; + + ERainFrequency RainFrequency = ERainFrequency::RAIN_MEDIUM; + + std::vector OrderedLights; + + int MinXCoord = 0; + int MinYCoord = 0; + int MaxXCoord = 0; + int MaxYCoord = 0; + + absolute_time_t NextRunTime = nil_time; +}; + +#endif diff --git a/headers/animationstation/effects/rainbow.h b/headers/animationstation/effects/rainbow.h index 91ded8ea0e..36b5e5442b 100644 --- a/headers/animationstation/effects/rainbow.h +++ b/headers/animationstation/effects/rainbow.h @@ -8,14 +8,17 @@ #include #include "animationstation.h" -class Rainbow : public Animation { +class RainbowSynced : public Animation +{ public: - Rainbow(PixelMatrix &matrix); - ~Rainbow() {}; + RainbowSynced(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType); + ~RainbowSynced() {}; - bool Animate(RGB (&frame)[100]); - void ParameterUp(); - void ParameterDown(); + virtual void Animate(RGB (&frame)[FRAME_MAX]) override; + + //These change the speed of the rainbow changing color + virtual void ParameterUp() override; + virtual void ParameterDown() override; protected: int currentFrame = 0; @@ -23,4 +26,21 @@ class Rainbow : public Animation { absolute_time_t nextRunTime = nil_time; }; +class RainbowRotate : public Animation +{ +public: + RainbowRotate(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType); + ~RainbowRotate() {}; + + virtual void Animate(RGB (&frame)[FRAME_MAX]) override; + + //These change the speed of the rainbow changing color + virtual void ParameterUp() override; + virtual void ParameterDown() override; + +protected: + int currentFrame = 0; + absolute_time_t nextRunTime = nil_time; +}; + #endif diff --git a/headers/animationstation/effects/randomcolor.h b/headers/animationstation/effects/randomcolor.h new file mode 100644 index 0000000000..4d3055c9d5 --- /dev/null +++ b/headers/animationstation/effects/randomcolor.h @@ -0,0 +1,25 @@ +#ifndef _RANDOM_COLOR_H_ +#define _RANDOM_COLOR_H_ + +#include +#include +#include +#include "../animation.h" +#include "../animationstation.h" + +class RandomColor : public Animation { +public: + RandomColor(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType); + RandomColor(Lights& InRGBLights, std::vector &InPressedPins); + ~RandomColor() { }; + + virtual void Animate(RGB (&frame)[FRAME_MAX]) override; + +protected: + + virtual void NewPressForPin(int lightIndex) override; + + std::vector savedPressedColor; +}; + +#endif diff --git a/headers/animationstation/effects/staticcolor.h b/headers/animationstation/effects/staticcolor.h index eeb963f863..22109c38d7 100644 --- a/headers/animationstation/effects/staticcolor.h +++ b/headers/animationstation/effects/staticcolor.h @@ -9,16 +9,14 @@ class StaticColor : public Animation { public: - StaticColor(PixelMatrix &matrix); - StaticColor(PixelMatrix &matrix, std::vector &pixels); + StaticColor(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType); + StaticColor(Lights& InRGBLights, std::vector &InPressedPins); ~StaticColor() { }; - bool Animate(RGB (&frame)[100]); - void SaveIndexOptions(uint8_t colorIndex); - uint8_t GetColor(); - void ParameterUp(); - void ParameterDown(); + virtual void Animate(RGB (&frame)[FRAME_MAX]) override; protected: + + virtual RGB AdjustColor(RGB InColor) { return InColor; } }; #endif diff --git a/headers/animationstation/effects/statictheme.h b/headers/animationstation/effects/statictheme.h deleted file mode 100644 index 4f417f2f0b..0000000000 --- a/headers/animationstation/effects/statictheme.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef STATIC_THEME_H_ -#define STATIC_THEME_H_ - -#include -#include -#include -#include -#include -#include -#include "animation.h" -#include "animationstation.h" - -class StaticTheme : public Animation { -public: - StaticTheme(PixelMatrix &matrix); - ~StaticTheme() {}; - - void AddTheme(const std::map& theme) { themes.push_back(theme); } - void ClearThemes() { themes.clear(); } - bool Animate(RGB (&frame)[100]); - void ParameterUp(); - void ParameterDown(); -protected: - RGB defaultColor = ColorBlack; - std::vector> themes; -}; - -#endif diff --git a/headers/animationstation/pixel.h b/headers/animationstation/pixel.h index 17af8a1751..98555ff24a 100644 --- a/headers/animationstation/pixel.h +++ b/headers/animationstation/pixel.h @@ -6,6 +6,8 @@ #include #include +#include "enums.pb.h" + struct Pixel { Pixel(int index, uint32_t mask = 0) : index(index), mask(mask) { } Pixel(int index, std::vector positions) : index(index), positions(positions) { } @@ -18,40 +20,98 @@ struct Pixel { inline const Pixel NO_PIXEL(-1); -struct PixelMatrix { - PixelMatrix() { } +inline bool operator==(const Pixel &lhs, const Pixel &rhs) { + return lhs.index == rhs.index; +} + +// Enums ////////////////////////////////////////////////////////////////////////// + +// Structs ////////////////////////////////////////////////////////////////////////// - std::vector> pixels; - uint8_t ledsPerPixel; - void setup(std::vector> pixels, int ledsPerPixel = -1) { - this->pixels = pixels; - this->ledsPerPixel = ledsPerPixel; +//Grid position of a single RGB Light +struct LightPosition +{ + LightPosition() {} + + LightPosition(uint32_t xCoord, uint32_t yCoord) + { + XPosition = xCoord; + YPosition = yCoord; + } + + int XPosition = 0; + int YPosition = 0; +}; + +//A single RGB light on the device. Replaced Pixel +struct Light +{ + Light(uint8_t InFirstLedIndex, uint8_t InNumLedsPerLight, LightPosition InPosition, uint8_t InGIPOPin, LightType InType) + { + FirstLedIndex = InFirstLedIndex; + Position = InPosition; + Type = InType; + LedsPerLight = InNumLedsPerLight; + //GamePadMask = GamePadMask; + if(InType != LightType::LightType_Case) + GIPOPin = InGIPOPin; + else + CaseLightIndex = InGIPOPin; } - inline int getLedCount() { - int count = 0; - for (auto &col : pixels) - for (auto &pixel : col) - if (pixel.index == NO_PIXEL.index) - continue; - else - count += pixel.positions.size(); + // index of first LED + uint32_t FirstLedIndex; + + // Approximate grid position of Light on the device + LightPosition Position; + + // Type of light, used in animations to allow users to seperate off lights for different anims + LightType Type; - return count; + //How many leds make up this light. + uint8_t LedsPerLight; + + //Game pad mask (if applicaple) (Needed to do SOCD on Lights) + // uint32_t GamePadMask; + + //GIPO pin this action (if applicaple) is on + int32_t GIPOPin = -1; + + //Sequential index of this case light + int32_t CaseLightIndex = -1; +}; + +//All RGB lights on the device. Replaced PixelMatrix +struct Lights +{ +public: + Lights() {} + + void Setup(std::vector InLights) + { + AllLights.clear(); + AllLights = InLights; } - inline uint16_t getPixelCount() const { - uint16_t count = 0; - for (auto &col : pixels) - count += col.size(); + inline uint8_t GetLedCount() const + { + int highestLedSoFar = 0; + for(const Light& thisLight : AllLights ) + { + int ledIndexValue = (int)thisLight.FirstLedIndex + (int)thisLight.LedsPerLight; + if(ledIndexValue > highestLedSoFar) + highestLedSoFar = ledIndexValue; + } + return highestLedSoFar; + } - return count; + inline uint16_t GetLightsCount() const + { + return AllLights.size(); } + //Array of all the lights + std::vector AllLights; }; -inline bool operator==(const Pixel &lhs, const Pixel &rhs) { - return lhs.index == rhs.index; -} - #endif diff --git a/headers/display/ui/screens/ButtonLayoutScreen.h b/headers/display/ui/screens/ButtonLayoutScreen.h index 939121df88..b3ed4ea10d 100644 --- a/headers/display/ui/screens/ButtonLayoutScreen.h +++ b/headers/display/ui/screens/ButtonLayoutScreen.h @@ -127,6 +127,9 @@ class ButtonLayoutScreen : public GPScreen { GPWidget* pushElement(GPButtonLayout element); void generateHeader(); + void updateCustomHeaders(); + void addCustomHeader(std::string newStr, std::string identifier); + const std::map displayModeLookup = { {INPUT_MODE_PS3, 0}, {INPUT_MODE_SWITCH, 1}, @@ -159,15 +162,21 @@ class ButtonLayoutScreen : public GPScreen { std::deque inputHistory; std::array lastInput; - bool bannerDisplay; uint8_t bannerDelay = 2; + float inbetweenBannerDelay = 0.3f; int bannerDelayStart = 0; - std::string bannerMessage; + bool inbetweenBanners = false; + std::deque bannerString; + std::deque bannerIdentifier; + + int8_t gamePadProfileNumber = -2; + int8_t prevGamepadProfileNumber = -2; + + int8_t prevLEDAnimationProfileNumber = -2; + uint16_t prevButtonState = 0; uint8_t prevLayoutLeft = 0; uint8_t prevLayoutRight = 0; - uint8_t profileNumber = 0; - uint8_t prevProfileNumber = 0; ButtonLayoutParamsLeft prevLeftOptions; ButtonLayoutParamsRight prevRightOptions; ButtonLayoutOrientation prevOrientation; diff --git a/lib/NeoPico/src/NeoPico.h b/lib/NeoPico/src/NeoPico.h index 3fa623a2db..8bc00d0c6d 100644 --- a/lib/NeoPico/src/NeoPico.h +++ b/lib/NeoPico/src/NeoPico.h @@ -12,6 +12,8 @@ typedef enum LED_FORMAT_RGBW = 3, } LEDFormat; +#define FRAME_MAX 100 + class NeoPico { public: @@ -22,13 +24,14 @@ class NeoPico void Off(); LEDFormat GetFormat(); void SetFrame(uint32_t * newFrame); + void ChangeNumPixels(int inNumPixels) {numPixels = inNumPixels;} private: void PutPixel(uint32_t pixel_grb); LEDFormat format; PIO pio = pio1; int stateMachine = 0; int numPixels = 0; - uint32_t frame[100]; + uint32_t frame[FRAME_MAX]; }; #endif diff --git a/lib/tinyusb b/lib/tinyusb index 9865cba11e..f77793517e 160000 --- a/lib/tinyusb +++ b/lib/tinyusb @@ -1 +1 @@ -Subproject commit 9865cba11ecbcdd25237ba9cf4ccbe3fd1fd821d +Subproject commit f77793517e3b016a89f75f3a5ad0bee57f0ad554 diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake index 1b2c74bada..d493cc23a5 100644 --- a/pico_sdk_import.cmake +++ b/pico_sdk_import.cmake @@ -63,9 +63,9 @@ if (NOT PICO_SDK_PATH) get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") endif () FetchContent_Declare( - pico_sdk - GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} - GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} ) if (NOT pico_sdk) @@ -95,6 +95,7 @@ if (NOT PICO_SDK_PATH) SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild ) endif () + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) endif () set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) diff --git a/proto/config.proto b/proto/config.proto index 1a539288a9..cb6955d30c 100644 --- a/proto/config.proto +++ b/proto/config.proto @@ -271,6 +271,12 @@ message DisplayOptions optional uint32 inputHistoryRow = 30; } +message LightCluster +{ + optional uint32 lightLocationData = 1; // [ledIndex, ledCount, xCoord, yCoord] + optional uint32 lightTypeData = 2; // [gpioPin, type, unused, unused] +}; + message LEDOptions { optional int32 dataPin = 1; @@ -278,7 +284,7 @@ message LEDOptions optional ButtonLayout ledLayout = 3; optional uint32 ledsPerButton = 4; optional uint32 brightnessMaximum = 5; - optional uint32 brightnessSteps = 6; + optional uint32 brightnessSteps = 6 [deprecated = true]; optional int32 indexUp = 7; optional int32 indexDown = 8; @@ -313,72 +319,57 @@ message LEDOptions optional int32 pledIndex3 = 34; optional int32 pledIndex4 = 35; - optional CaseRGBType caseRGBType = 36; - optional int32 caseRGBIndex = 37; + optional CaseRGBType caseRGBType = 36 [deprecated = true]; + optional int32 caseRGBIndex = 37 [deprecated = true]; optional uint32 caseRGBColor = 38 [deprecated = true]; - optional uint32 caseRGBCount = 39; + optional uint32 caseRGBCount = 39 [deprecated = true]; + + repeated LightCluster lightClusterData = 40 [(nanopb).max_count = 100]; //FRAME_MAX (100) + optional bool lightClusterDataInitialised = 41; +}; + +// This has to be kept in sync with AnimationProfile_Unpacked in AnimationStation.hpp +message AnimationProfile +{ + optional bool bEnabled = 1; + + optional AnimationNonPressedEffects baseNonPressedEffect = 2; + optional AnimationPressedEffects basePressedEffect = 3; + + optional int32 baseCycleTime = 4; + + repeated uint32 notPressedStaticColors = 5 [(nanopb).max_count = 8]; //NUM_BANK0_GPIOS/4 from platform_defs.h + repeated uint32 pressedStaticColors = 6 [(nanopb).max_count = 8]; //NUM_BANK0_GPIOS/4 from platform_defs.h + + optional uint32 buttonPressHoldTimeInMs = 7; + optional uint32 buttonPressFadeOutTimeInMs = 8; + + optional uint32 nonPressedSpecialColor = 9; + + optional AnimationNonPressedEffects baseCaseEffect = 10; + + repeated uint32 caseStaticColors = 11 [(nanopb).max_count = 10]; //(MAX_CASE_LIGHTS/4) from AnimationStation.hpp + + optional uint32 pressedSpecialColor = 12; + + optional int32 basePressedCycleTime = 13; + + optional bool bUseCaseLightsInPressedAnimations = 14; }; -// This has to be kept in sync with AnimationOptions in animationstation.hpp +// This has to be kept in sync with AnimationOptions_Unpacked in animationstation.hpp message AnimationOptions { - optional uint32 baseAnimationIndex = 1; + reserved 1, 3 to 56; + optional uint32 brightness = 2; - optional uint32 staticColorIndex = 3; - optional uint32 buttonColorIndex = 4; - optional int32 chaseCycleTime = 5; - optional int32 rainbowCycleTime = 6; - optional uint32 themeIndex = 7; - - optional bool hasCustomTheme = 8; - optional uint32 customThemeUp = 9; - optional uint32 customThemeDown = 10; - optional uint32 customThemeLeft = 11; - optional uint32 customThemeRight = 12; - optional uint32 customThemeB1 = 13; - optional uint32 customThemeB2 = 14; - optional uint32 customThemeB3 = 15; - optional uint32 customThemeB4 = 16; - optional uint32 customThemeL1 = 17; - optional uint32 customThemeR1 = 18; - optional uint32 customThemeL2 = 19; - optional uint32 customThemeR2 = 20; - optional uint32 customThemeS1 = 21; - optional uint32 customThemeS2 = 22; - optional uint32 customThemeL3 = 23; - optional uint32 customThemeR3 = 24; - optional uint32 customThemeA1 = 25; - optional uint32 customThemeA2 = 26; - optional uint32 customThemeUpPressed = 27; - optional uint32 customThemeDownPressed = 28; - optional uint32 customThemeLeftPressed = 29; - optional uint32 customThemeRightPressed = 30; - optional uint32 customThemeB1Pressed = 31; - optional uint32 customThemeB2Pressed = 32; - optional uint32 customThemeB3Pressed = 33; - optional uint32 customThemeB4Pressed = 34; - optional uint32 customThemeL1Pressed = 35; - optional uint32 customThemeR1Pressed = 36; - optional uint32 customThemeL2Pressed = 37; - optional uint32 customThemeR2Pressed = 38; - optional uint32 customThemeS1Pressed = 39; - optional uint32 customThemeS2Pressed = 40; - optional uint32 customThemeL3Pressed = 41; - optional uint32 customThemeR3Pressed = 42; - optional uint32 customThemeA1Pressed = 43; - optional uint32 customThemeA2Pressed = 44; - optional uint32 buttonPressColorCooldownTimeInMs = 45; - optional uint32 ambientLightEffectsCountIndex = 46; // ambient count - optional bool ambientLightCustomLinkageModeFlag = 47 [deprecated = true]; - optional uint32 ambientLightGradientSpeed = 48; // gradient speed - optional int32 ambientLightChaseSpeed = 49; // chase speed - optional float ambientLightBreathSpeed = 50; // breath speed - optional float alGradientBrightnessCustomX = 51; // gradient brightness - optional float alChaseBrightnessCustomX = 52; // chase brightness - optional float alStaticColorBrightnessCustomX = 53; // static brightness - optional float alStaticBrightnessCustomThemeX = 54; // static custom theme brightness - optional uint32 alCustomStaticThemeIndex = 55; // custom theme index - optional uint32 alCustomStaticColorIndex = 56; // static color index + + repeated AnimationProfile profiles = 57 [(nanopb).max_count = 4]; //MAX_ANIMATION_PROFILES from AnimationStation.hpp + optional int32 baseProfileIndex = 58; + + repeated uint32 customColors = 59 [(nanopb).max_count = 16]; //MAX_CUSTOM_COLORS from Animation.hpp + + optional uint32 autoDisableTime = 60; } message BootselButtonOptions diff --git a/proto/enums.proto b/proto/enums.proto index 7b69984fac..4d4819df50 100644 --- a/proto/enums.proto +++ b/proto/enums.proto @@ -422,25 +422,16 @@ enum GamepadHotkey HOTKEY_MENU_NAV_BACK = 49; HOTKEY_MENU_NAV_TOGGLE = 50; HOTKEY_LEDS_NONE = 51; - HOTKEY_LEDS_ANIMATION_UP = 52; - HOTKEY_LEDS_ANIMATION_DOWN = 53; - HOTKEY_LEDS_PARAMETER_UP = 54; - HOTKEY_LEDS_PRESS_PARAMETER_UP = 55; - HOTKEY_LEDS_PRESS_PARAMETER_DOWN = 56; - HOTKEY_LEDS_PARAMETER_DOWN = 57; - HOTKEY_LEDS_BRIGHTNESS_UP = 58; - HOTKEY_LEDS_BRIGHTNESS_DOWN = 59; - HOTKEY_LEDS_FADETIME_UP = 60; - HOTKEY_LEDS_FADETIME_DOWN = 61; - HOTKEY_AMBIENT_LIGHT_EFFECTS_CHANGE = 62; - HOTKEY_AMBIENT_LIGHT_EFFECTS_ON_OFF = 63; - HOTKEY_AMBIENT_LIGHT_EFFECTS_BRIGHTNESS_UP = 64; - HOTKEY_AMBIENT_LIGHT_EFFECTS_BRIGHTNESS_DOWN = 65; - HOTKEY_AMBIENT_LIGHT_EFFECTS_PARAMETER_UP = 66; - HOTKEY_AMBIENT_LIGHT_EFFECTS_PARAMETER_DOWN = 67; - HOTKEY_AMBIENT_LIGHT_EFFECTS_FRAME_SPEED_UP = 68; - HOTKEY_AMBIENT_LIGHT_EFFECTS_FRAME_SPEED_DOWN = 69; - HOTKEY_AMBIENT_LIGHT_EFFECTS_CUSTOM_LINKAGE = 70; + HOTKEY_LEDS_PROFILE_UP = 52; + HOTKEY_LEDS_PROFILE_DOWN = 53; + HOTKEY_LEDS_PRESS_PARAMETER_UP = 54; + HOTKEY_LEDS_PRESS_PARAMETER_DOWN = 55; + HOTKEY_LEDS_PARAMETER_UP = 56; + HOTKEY_LEDS_PARAMETER_DOWN = 57; + HOTKEY_LEDS_BRIGHTNESS_UP = 58; + HOTKEY_LEDS_BRIGHTNESS_DOWN = 59; + // 60 - 70 have previously been LED hotkeys + // They can be reused as they where never saved in storage HOTKEY_LOAD_PROFILE_5 = 71; HOTKEY_LOAD_PROFILE_6 = 72; HOTKEY_ENABLE_4_WAY_MODE = 73; @@ -450,17 +441,6 @@ enum GamepadHotkey HOTKEY_FOCUS_MODE_TOGGLE = 77; } -enum AnimationEffects -{ - option (nanopb_enumopt).long_names = false; - - EFFECT_STATIC_COLOR = 0; - EFFECT_RAINBOW = 1; - EFFECT_CHASE = 2; - EFFECT_STATIC_THEME = 3; - EFFECT_CUSTOM_THEME = 4; -} - // This has to be kept in sync with LEDFormat in NeoPico.h enum LEDFormat_Proto { @@ -470,6 +450,50 @@ enum LEDFormat_Proto LED_FORMAT_RGBW = 3; } +/* This has to be kept in sync with AnimationNonPressedEffects in AnimationStation.hpp */ +enum AnimationNonPressedEffects +{ + EFFECT_STATIC_COLOR = 0; + EFFECT_RAINBOW_SYNCED = 1; + EFFECT_RAINBOW_ROTATE = 2; + EFFECT_CHASE_SEQUENTIAL = 3; + EFFECT_CHASE_LEFT_TO_RIGHT = 4; + EFFECT_CHASE_RIGHT_TO_LEFT = 5; + EFFECT_CHASE_TOP_TO_BOTTOM = 6; + EFFECT_CHASE_BOTTOM_TO_TOP = 7; + EFFECT_CHASE_SEQUENTIAL_PINGPONG = 8; + EFFECT_CHASE_HORIZONTAL_PINGPONG = 9; + EFFECT_CHASE_VERTICAL_PINGPONG = 10; + EFFECT_CHASE_RANDOM = 11; + EFFECT_JIGGLESTATIC = 12; + EFFECT_JIGGLETWOSTATICS = 13; + EFFECT_RAIN_LOW = 14; + EFFECT_RAIN_MEDIUM = 15; + EFFECT_RAIN_HIGH = 16; +} + +/* This has to be kept in sync with AnimationPressedEffects in AnimationStation.hpp */ +enum AnimationPressedEffects +{ + PRESSEDEFFECT_STATIC_COLOR = 0; + PRESSEDEFFECT_RANDOM = 1; + PRESSEDEFFECT_JIGGLESTATIC = 2; + PRESSEDEFFECT_JIGGLETWOSTATICS = 3; + PRESSEDEFFECT_BURST = 4; + PRESSEDEFFECT_BURST_RANDOM = 5; + PRESSEDEFFECT_BURST_SMALL = 6; + PRESSEDEFFECT_BURST_SMALL_RANDOM = 7; +} + +/* This has to be kept in sync with LightType in Pixel.hpp */ +enum LightType +{ + ActionButton = 0; + Case = 1; + Turbo = 2; + PlayerLight = 3; +} + enum ShmupMixMode { option (nanopb_enumopt).long_names = false; diff --git a/src/addons/neopicoleds.cpp b/src/addons/neopicoleds.cpp index 9ae105cd39..52df11bdf2 100644 --- a/src/addons/neopicoleds.cpp +++ b/src/addons/neopicoleds.cpp @@ -4,7 +4,6 @@ */ #include "animationstation.h" -#include "animationstorage.h" #include "storagemanager.h" #include "NeoPico.h" #include "pixel.h" @@ -16,25 +15,6 @@ #include "enums.h" #include "helper.h" -#define FRAME_MAX 100 -#define AL_ROW 5 -#define AL_COL 8 -#define AL_STATIC_COLOR_COUNT 14 -#define AL_EFFECT_MODE_MAX 5 -#define CHASE_LIGHTS_TURN_ON 4 - -const RGB alCustomStaticTheme[AL_ROW][AL_COL] = - {{ColorRed, ColorOrange, ColorYellow, ColorGreen, ColorBlue, ColorIndigo, ColorViolet, ColorWhite}, - {ColorOrange, ColorRed, ColorGreen, ColorYellow, ColorIndigo, ColorBlue, ColorWhite, ColorViolet}, - {ColorYellow, ColorOrange, ColorRed, ColorIndigo, ColorBlue, ColorGreen, ColorViolet, ColorWhite}, - {ColorGreen, ColorOrange, ColorYellow, ColorRed, ColorWhite, ColorIndigo, ColorViolet, ColorBlue}, - {ColorWhite, ColorIndigo, ColorViolet, ColorOrange, ColorBlue, ColorGreen, ColorYellow, ColorRed},}; - -const RGB alCustomStaticColors[AL_STATIC_COLOR_COUNT] { - ColorBlack, ColorWhite, ColorRed, ColorOrange, ColorYellow, - ColorLimeGreen, ColorGreen, ColorSeafoam, ColorAqua, ColorSkyBlue, - ColorBlue, ColorPurple, ColorPink, ColorMagenta }; - const std::string BUTTON_LABEL_UP = "Up"; const std::string BUTTON_LABEL_DOWN = "Down"; const std::string BUTTON_LABEL_LEFT = "Left"; @@ -56,6 +36,12 @@ const std::string BUTTON_LABEL_A2 = "A2"; static std::vector EMPTY_VECTOR; +bool NeoPicoLEDAddon::bRestartLeds = false; + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Player LEDs //////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////// + uint32_t rgbPLEDValues[4]; // Move to Proto Enums @@ -143,7 +129,7 @@ PLEDAnimationState getXBoneAnimationNEOPICO(Gamepad * gamepad) .animation = PLED_ANIM_OFF }; - if ( gamepad->auxState.playerID.ledValue == 1 ) { + if ( gamepad->auxState.playerID.ledValue == 1 ) { animationState.animation = PLED_ANIM_SOLID; } @@ -239,6 +225,18 @@ PLEDAnimationState getSwitchProAnimationNEOPICO(uint16_t ledState) return animationState; } +////////////////////////////////////////////////////////////////////////////////////////////////////////// +//END Player LEDs //////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +//RBG LEDs /////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////// +//GP Addon functions +/////////////////////////////////// + bool NeoPicoLEDAddon::available() { const LEDOptions& ledOptions = Storage::getInstance().getLedOptions(); return isValidPin(ledOptions.dataPin); @@ -247,6 +245,7 @@ bool NeoPicoLEDAddon::available() { void NeoPicoLEDAddon::setup() { // Set Default LED Options const LEDOptions& ledOptions = Storage::getInstance().getLedOptions(); + turnOffWhenSuspended = ledOptions.turnOffWhenSuspended; // Setup our aux state player ID sensors Gamepad * gamepad = Storage::getInstance().GetProcessedGamepad(); @@ -257,262 +256,34 @@ void NeoPicoLEDAddon::setup() { neoPLEDs = new NeoPicoPlayerLEDs(); } - // Setup our LED matrix - uint8_t buttonCount = setupButtonPositions(); - vector> pixels = createLEDLayout(static_cast(ledOptions.ledLayout), ledOptions.ledsPerButton, buttonCount); - matrix.setup(pixels, ledOptions.ledsPerButton); - ledCount = matrix.getLedCount(); - buttonLedCount = ledCount; // used in linkage - - // Add Player LEDs to LED count - if (ledOptions.pledType == PLED_TYPE_RGB && PLED_COUNT > 0) - ledCount += PLED_COUNT; + decompressSettings(); - // Add Turbo LED to LED Count - const TurboOptions& turboOptions = Storage::getInstance().getAddonOptions().turboOptions; - if (turboOptions.turboLedType == PLED_TYPE_RGB) - ledCount += 1; - - // Add Case RGB LEDs to LED Count - if (ledOptions.caseRGBType != CASE_RGB_TYPE_NONE ) { - ledCount += (int)ledOptions.caseRGBCount; - } - - // Setup NeoPico ws2812 PIO - neopico.Setup(ledOptions.dataPin, ledCount, static_cast(ledOptions.ledFormat), pio0, 0); - neopico.Off(); // turn off everything - - // Rewrite this - Animation::format = static_cast(ledOptions.ledFormat); - - // Configure Animation Station - const AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - as.ConfigureBrightness(ledOptions.brightnessMaximum, ledOptions.brightnessSteps); - as.SetMatrix(matrix); - as.SetMode(animationOptions.baseAnimationIndex); - as.SetBrightness(animationOptions.brightness); + configureLEDs(); // Next Run nextRunTime = make_timeout_time_ms(0); // Reset timeout - - // Last Hot-key Action - lastAmbientAction = HOTKEY_LEDS_NONE; - - // Ambient lights - alBrightnessBreathX = 1.00f; - breathLedEffectCycle = 0; - alReverse = false; - alCurrentFrame = 0; - alFrameToRGB = 0; - alFrameSpeed = 2; - ambientLight.r = 0x00; - ambientLight.g = 0x00; - ambientLight.b = 0x00; - nextRunTimeAmbientLight = make_timeout_time_ms(0); - - // Start of chase light index is case rgb index - chaseLightIndex = ledOptions.caseRGBIndex; - chaseLightMaxIndexPos = ledCount; - - multipleOfButtonLedsCount = (ledOptions.caseRGBCount) / (buttonLedCount); - remainderOfButtonLedsCount = (ledOptions.caseRGBCount) % (buttonLedCount); - - alLinkageStartIndex = ledOptions.caseRGBIndex; } -void NeoPicoLEDAddon::ambientLightCustom() { - const AnimationOptions& options = Storage::getInstance().getAnimationOptions(); - const LEDOptions& ledOptions = Storage::getInstance().getLedOptions(); - - uint8_t alStartIndex = ledOptions.caseRGBIndex; - uint8_t multipleOfCustomStaticThemeCount; - uint8_t remainderOfCustomStaticThemeCount; - int maxFrame = (int)ledOptions.caseRGBCount; - if ( maxFrame > FRAME_MAX - alStartIndex ) - maxFrame = FRAME_MAX - alStartIndex; // make sure we don't go over 100 and overflow frame[] - - // Start-up Animations in Haute were here - switch(options.ambientLightEffectsCountIndex) { - case AL_CUSTOM_EFFECT_STATIC_COLOR: - for(int i = 0; i < maxFrame; i++) { - frame[alStartIndex + i] = alCustomStaticColors[options.alCustomStaticColorIndex].value(Animation::format, options.alStaticColorBrightnessCustomX); - } - break; - - case AL_CUSTOM_EFFECT_GRADIENT: - alFrameToRGB = 255 - alCurrentFrame; // From 0 -> 255 to 255 -> 0 - if(alFrameToRGB < 85) { // Less than 85, transitions from red to yellow. The red component starts at 255 and gradually decreases, the green component always reaches 0, and the blue component starts at 0 and increases gradually. - ambientLight.r = 255 - alFrameToRGB * 3; - ambientLight.g = 0; - ambientLight.b = alFrameToRGB * 3; - } else if(alFrameToRGB < 170) { // Between 85 and 170, there is a transition from yellow to cyan. The red component is always 0, the green component starts at 0 and gradually increases, and the blue component starts at 255 and gradually decreases. - alFrameToRGB -= 85; - ambientLight.r = 0; - ambientLight.g = alFrameToRGB * 3; - ambientLight.b = 255 - alFrameToRGB * 3; - } else { // When greater than or equal to 170, it transitions from cyan to blue. The red component is always 0, the green component starts at 255 and gradually decreases, and the blue component starts at 0 and gradually increases. - alFrameToRGB -= 170; - ambientLight.r = alFrameToRGB * 3; - ambientLight.g = 255 - alFrameToRGB * 3; // Power-on animation chase - ambientLight.b = 0; - } - // Reverse color cycle if we hit the end of our cycle change - if (alReverse) { - alCurrentFrame -= options.ambientLightGradientSpeed; - if(alCurrentFrame < 0) { - alCurrentFrame = 1; - alReverse = false; - } - } else { - alCurrentFrame += options.ambientLightGradientSpeed; - if(alCurrentFrame > 255) { - alCurrentFrame = 254; - alReverse = true; - } - } - // Fill Frame - for(int i = 0; i < maxFrame; i++){ - frame[alStartIndex + i] = ambientLight.value(Animation::format, options.alGradientBrightnessCustomX); - } - break; - case AL_CUSTOM_EFFECT_CHASE: - if(time_reached(nextRunTimeAmbientLight)){ - alFrameToRGB = 255 - alCurrentFrame; // 从 0 -> 255 变为 255 -> 0 - if(alFrameToRGB < 85) { // Less than 85, transitions from red to yellow. The red component starts at 255 and gradually decreases, the green component always reaches 0, and the blue component starts at 0 and increases gradually. - ambientLight.r = 255 - alFrameToRGB * 3; - ambientLight.g = 0; - ambientLight.b = alFrameToRGB * 3; - } else if (alFrameToRGB < 170) { // Between 85 and 170, there is a transition from yellow to cyan. The red component is always 0, the green component starts at 0 and gradually increases, and the blue component starts at 255 and gradually decreases. - alFrameToRGB -= 85; - ambientLight.r = 0; - ambientLight.g = alFrameToRGB * 3; - ambientLight.b = 255 - alFrameToRGB * 3; - } else { // When greater than or equal to 170, it transitions from cyan to blue. The red component is always 0, the green component starts at 255 and gradually decreases, and the blue component starts at 0 and gradually increases. - alFrameToRGB -= 170; - ambientLight.r = alFrameToRGB * 3; - ambientLight.g = 255 - alFrameToRGB * 3; - ambientLight.b = 0; - } - if(alReverse) { - alCurrentFrame -= alFrameSpeed; - if(alCurrentFrame < 0) { - alCurrentFrame = 1; - alReverse = false; - } - } else { - alCurrentFrame += alFrameSpeed; - if(alCurrentFrame > 255) { - alCurrentFrame = 254; - alReverse = true; - } - } - chaseLightIndex++; - if(chaseLightIndex >= chaseLightMaxIndexPos) { - chaseLightIndex = alStartIndex; - } - nextRunTimeAmbientLight = make_timeout_time_ms(options.ambientLightChaseSpeed); - } - // Blank out our caseRGBs - for(int j = 0; j < maxFrame; j++){ - frame[alStartIndex + j] = 0x0; - } - // Fill up to four pixels forward - for(int i = 0; i < CHASE_LIGHTS_TURN_ON && chaseLightIndex + i < chaseLightMaxIndexPos; i++) { - frame[chaseLightIndex + i] = ambientLight.value(Animation::format, options.alChaseBrightnessCustomX); - } - // Fill up to 3 pixels in the beginning of our casergb (wrap-around) - if ( chaseLightIndex + CHASE_LIGHTS_TURN_ON > chaseLightMaxIndexPos ) { - for(int i = 0; i < (chaseLightIndex + CHASE_LIGHTS_TURN_ON) - chaseLightMaxIndexPos; i++) { - frame[alStartIndex + i] = ambientLight.value(Animation::format, options.alChaseBrightnessCustomX); - } - } - break; - case AL_CUSTOM_EFFECT_BREATH: - if(alReverse) { - alBrightnessBreathX += options.ambientLightBreathSpeed; - if(alBrightnessBreathX > 1.00f){ - alBrightnessBreathX = 1.00f; - alReverse = false; - } - } else { - alBrightnessBreathX -= options.ambientLightBreathSpeed; - if(alBrightnessBreathX < 0.00f){ - alBrightnessBreathX = 0.00f; - alReverse = true; - breathLedEffectCycle++; - } - } - - if(breathLedEffectCycle <= 1) { - ambientLight.r = 255; - ambientLight.g = 0; - ambientLight.b = 255; - } else if ((breathLedEffectCycle > 1) && (breathLedEffectCycle <= 3)) { - ambientLight.r = 255; - ambientLight.g = 0; - ambientLight.b = 0; - } else if ((breathLedEffectCycle > 3) && (breathLedEffectCycle <= 5)) { - ambientLight.r = 0; - ambientLight.g = 255; - ambientLight.b = 0; - } else if ((breathLedEffectCycle > 5) && (breathLedEffectCycle <= 7)) { - ambientLight.r= 0; - ambientLight.g = 0; - ambientLight.b = 255; - } - else { - breathLedEffectCycle = 0; - } - // Fill Frame - for(int i = 0; i < maxFrame; i++) { - frame[alStartIndex + i] = ambientLight.value(Animation::format, alBrightnessBreathX); - } - break; - case AL_CUSTOM_EFFECT_STATIC_THEME: - multipleOfCustomStaticThemeCount = maxFrame / AL_COL; - remainderOfCustomStaticThemeCount = maxFrame % AL_COL; - // Fill frame with extras on remainder - for(int i = 0; i < multipleOfCustomStaticThemeCount; i++){ - for(int j = 0; j < AL_COL; j++){ - frame[alStartIndex + i*AL_COL + j] = alCustomStaticTheme[options.alCustomStaticThemeIndex][j].value(Animation::format, options.alStaticBrightnessCustomThemeX); - } - } - if(remainderOfCustomStaticThemeCount != 0){ - for(int k = 0; k < remainderOfCustomStaticThemeCount; k++){ - frame[alStartIndex + multipleOfCustomStaticThemeCount * AL_COL + k] = alCustomStaticTheme[options.alCustomStaticThemeIndex][k].value(Animation::format, options.alStaticBrightnessCustomThemeX); - } - } - break; - default: - break; - } -} - -void NeoPicoLEDAddon::ambientLightLinkage() { - float preLinkageBrightnessX = as.GetLinkageModeOfBrightnessX(); - for(int i = 0; i < multipleOfButtonLedsCount; i++){ // Repeat buttons - for(int j = 0; j < buttonLedCount; j++){ - frame[alLinkageStartIndex + i*buttonLedCount + j] = as.linkageFrame[j].value(Animation::format, preLinkageBrightnessX); - } - } - - if(remainderOfButtonLedsCount != 0){ // Remainder - for(int k = 0; k < remainderOfButtonLedsCount; k++){ - frame[alLinkageStartIndex + multipleOfButtonLedsCount * buttonLedCount + k] = as.linkageFrame[k].value(Animation::format, preLinkageBrightnessX); - } +void NeoPicoLEDAddon::process() +{ + if(bRestartLeds) + { + bRestartLeds = false; + AnimStation.Clear(); + neopico.Clear(); + neopico.Show(); + decompressSettings(); + configureLEDs(); } -} -void NeoPicoLEDAddon::process() { - const LEDOptions& ledOptions = Storage::getInstance().getLedOptions(); - if (!isValidPin(ledOptions.dataPin) || !time_reached(this->nextRunTime)) - return; + //Check we have LEDs enabled and is it time to update + const LEDOptions& ledOptions = Storage::getInstance().getLedOptions(); + if (!isValidPin(ledOptions.dataPin) || !time_reached(this->nextRunTime)) + return; - // Get turbo options (turbo RGB led) - const TurboOptions& turboOptions = Storage::getInstance().getAddonOptions().turboOptions; + //Handle player leds (player id lights) Gamepad * gamepad = Storage::getInstance().GetProcessedGamepad(); - GamepadHotkey action = animationHotkeys(gamepad); - if (ledOptions.pledType == PLED_TYPE_RGB) { + if (ledOptions.pledType == PLED_TYPE_RGB) { if (gamepad->auxState.playerID.enabled && gamepad->auxState.playerID.active) { switch (gamepad->getOptions().inputMode) { case INPUT_MODE_XINPUT: @@ -541,36 +312,38 @@ void NeoPicoLEDAddon::process() { } } - if ( action != HOTKEY_LEDS_NONE ) { - as.HandleEvent(action); - } + //Check for button combos that change animation settings + GamepadHotkey action = ProcessAnimationHotkeys(gamepad); + AnimStation.HandleEvent(action); + + //New check for buttons being pressed. this is a direct check to see if a pin is held + Mask_t values = Storage::getInstance().GetGamepad()->debouncedGpio; + vector pressedPins; + for(auto thisLight : RGBLights.AllLights) + { + if(values & (1 << thisLight.GIPOPin)) + { + pressedPins.push_back(thisLight.GIPOPin); + } + } + AnimStation.HandlePressedPins(pressedPins); - uint32_t buttonState = gamepad->state.dpad << 16 | gamepad->state.buttons; - vector pressed; - for (auto row : matrix.pixels) - { - for (auto pixel : row) - { - if (buttonState & pixel.mask) - pressed.push_back(pixel); - } - } - if (pressed.size() > 0) - as.HandlePressed(pressed); - else - as.ClearPressed(); + //Still need to check logical buttons so that we can trigger special moves (coming later) + uint32_t buttonState = gamepad->state.dpad << 16 | gamepad->state.buttons; + AnimStation.HandlePressedButtons(buttonState); - // Animation Station calls Effect - as.Animate(); + //Update idle and button animations + AnimStation.Animate(); - if (ledOptions.turnOffWhenSuspended && get_usb_suspended()) { - as.DimBrightnessTo0(); - } else { - as.SetBrightness(as.GetBrightness()); - } + //check if need to turn off due to usb suspension + if (turnOffWhenSuspended && get_usb_suspended()) { + AnimStation.DimBrightnessTo0(); + } else { + AnimStation.SetBrightnessStepValue(AnimationStation::GetBrightnessStepValue()); + } - // Copy Animation Station to NeoPico w/ Brightness Modification - as.ApplyBrightness(&frame[0]); + //Grab led values this frame + AnimStation.ApplyBrightness(frame); // Apply the player LEDs to our first 4 leds if we're in NEOPIXEL mode if (ledOptions.pledType == PLED_TYPE_RGB) { @@ -580,7 +353,7 @@ void NeoPicoLEDAddon::process() { continue; float level = (static_cast(PLED_MAX_LEVEL - neoPLEDs->getLedLevels()[i]) / static_cast(PLED_MAX_LEVEL)); - float brightness = as.GetBrightnessX() * level; + float brightness = as.GetNormalisedBrightness() * level; if (gamepad->auxState.sensors.statusLight.enabled && gamepad->auxState.sensors.statusLight.active) { rgbPLEDValues[i] = (RGB(gamepad->auxState.sensors.statusLight.color.red, gamepad->auxState.sensors.statusLight.color.green, gamepad->auxState.sensors.statusLight.color.blue)).value(neopico.GetFormat(), brightness); } else { @@ -590,242 +363,195 @@ void NeoPicoLEDAddon::process() { } } + // Get turbo options (turbo RGB led) + const TurboOptions& turboOptions = Storage::getInstance().getAddonOptions().turboOptions; // Turbo LED is a separate RGB that is on if turbo is on, and off if its off if ( turboOptions.turboLedType == PLED_TYPE_RGB ) { // RGB or PWM? if ( gamepad->auxState.turbo.activity == 1) { // Turbo is on (active sensor) - if (turboOptions.turboLedIndex >= 0 && turboOptions.turboLedIndex < 100) { // Double check index value - float brightness = as.GetBrightnessX(); + if (turboOptions.turboLedIndex >= 0 && turboOptions.turboLedIndex < FRAME_MAX) { // Double check index value + float brightness = as.GetNormalisedBrightness(); frame[turboOptions.turboLedIndex] = ((RGB)turboOptions.turboLedColor).value(neopico.GetFormat(), brightness); } } } - // Case RGB LEDs for a single static color go here - if ( ledOptions.caseRGBIndex >= 0 && - ledOptions.caseRGBCount > 0 ) { - ambientHotkeys(gamepad); - if ( ledOptions.caseRGBType == CASE_RGB_TYPE_AMBIENT ) { - this->ambientLightCustom(); - } else if ( ledOptions.caseRGBType == CASE_RGB_TYPE_LINKED ) { - this->ambientLightLinkage(); //Custom mode - } - } + //Set led values out to the actual leds + neopico.SetFrame(frame); + neopico.Show(); - neopico.SetFrame(frame); - neopico.Show(); - this->nextRunTime = make_timeout_time_ms(intervalMS); + //queue up next frame time + this->lastRunTime = get_absolute_time(); + this->nextRunTime = make_timeout_time_ms(NeoPicoLEDAddon::intervalMS); } -std::vector * NeoPicoLEDAddon::getLEDPositions(string button, std::vector> *positions) +/////////////////////////////////// +// Old Pixel Setup functions +// Left here for legacy setup until all configs are converted +/////////////////////////////////// + +void NeoPicoLEDAddon::generateLegacyIndividualLight(int firstLedIndex, int xCoord, int yCoord, uint8_t ledsPerPixel, GpioAction actionButton) { - int buttonPosition = buttonPositions[button]; - if (buttonPosition < 0) - return &EMPTY_VECTOR; - else - return &positions->at(buttonPosition); -} + //If button doesnt have a light then return + if(firstLedIndex < 0) + return; + + firstLedIndex = firstLedIndex * ledsPerPixel; + + LEDOptions& options = Storage::getInstance().getLedOptions(); + if(options.lightClusterData_count >= FRAME_MAX) //Max data array size (defined in config proto) + return; + + const GpioMappings& pinMappings = Storage::getInstance().getGpioMappings(); + + //NOTE : I dont like this but I'm not sure theres a better way. Since sticks often have multiple buttons bound to the same action I'm hoping the first one found is the "master" + int gpioPin = -1; + for(int configIndex = 0; configIndex < pinMappings.pins_count; ++configIndex) + { + if(actionButton == pinMappings.pins[configIndex].action) + { + gpioPin = configIndex; + break; + } + } -// Macro for Pixel() declarations -#define PIXEL(BUTTON, MASK) \ - Pixel(buttonPositions[BUTTON], MASK, *getLEDPositions(BUTTON, positions)) + int thisEntryIndex = options.lightClusterData_count; + options.lightClusterData[thisEntryIndex].lightLocationData = firstLedIndex; + options.lightClusterData[thisEntryIndex].lightLocationData += ledsPerPixel << 8; + options.lightClusterData[thisEntryIndex].lightLocationData += xCoord << 16; + options.lightClusterData[thisEntryIndex].lightLocationData += yCoord << 24; + options.lightClusterData[thisEntryIndex].lightTypeData = gpioPin; + options.lightClusterData[thisEntryIndex].lightTypeData += LightType::LightType_ActionButton << 8; + options.lightClusterData_count++; +} /** * @brief Create an LED layout using a 2x4 matrix. */ -std::vector> NeoPicoLEDAddon::generatedLEDButtons(std::vector> *positions) +void NeoPicoLEDAddon::generatedLEDButtons(std::vector> *positions, uint8_t ledsPerPixel) { - std::vector> pixels = - { - { - PIXEL(BUTTON_LABEL_B3, GAMEPAD_MASK_B3), - PIXEL(BUTTON_LABEL_B1, GAMEPAD_MASK_B1), - }, - { - PIXEL(BUTTON_LABEL_B4, GAMEPAD_MASK_B4), - PIXEL(BUTTON_LABEL_B2, GAMEPAD_MASK_B2), - }, - { - PIXEL(BUTTON_LABEL_R1, GAMEPAD_MASK_R1), - PIXEL(BUTTON_LABEL_R2, GAMEPAD_MASK_R2), - }, - { - PIXEL(BUTTON_LABEL_L1, GAMEPAD_MASK_L1), - PIXEL(BUTTON_LABEL_L2, GAMEPAD_MASK_L2), - }, - { - PIXEL(BUTTON_LABEL_LEFT, GAMEPAD_MASK_DL), - PIXEL(BUTTON_LABEL_DOWN, GAMEPAD_MASK_DD), - PIXEL(BUTTON_LABEL_RIGHT, GAMEPAD_MASK_DR), - PIXEL(BUTTON_LABEL_UP, GAMEPAD_MASK_DU), - PIXEL(BUTTON_LABEL_S1, GAMEPAD_MASK_S1), - PIXEL(BUTTON_LABEL_S2, GAMEPAD_MASK_S2), - PIXEL(BUTTON_LABEL_L3, GAMEPAD_MASK_L3), - PIXEL(BUTTON_LABEL_R3, GAMEPAD_MASK_R3), - PIXEL(BUTTON_LABEL_A1, GAMEPAD_MASK_A1), - PIXEL(BUTTON_LABEL_A2, GAMEPAD_MASK_A2), - }, - }; + //8 action buttons in 2x4 array + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B3], 4, 0, ledsPerPixel, BUTTON_PRESS_B3); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B1], 4, 1, ledsPerPixel, BUTTON_PRESS_B1); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B4], 5, 0, ledsPerPixel, BUTTON_PRESS_B4); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B2], 5, 1, ledsPerPixel, BUTTON_PRESS_B2); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_R1], 6, 0, ledsPerPixel, BUTTON_PRESS_R1); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_R2], 6, 1, ledsPerPixel, BUTTON_PRESS_R2); - return pixels; + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_L1], 7, 0, ledsPerPixel, BUTTON_PRESS_L1); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_L2], 7, 1, ledsPerPixel, BUTTON_PRESS_L2); + + //extras + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_LEFT], 0, 1, ledsPerPixel, BUTTON_PRESS_LEFT); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_DOWN], 1, 2, ledsPerPixel, BUTTON_PRESS_DOWN); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_RIGHT], 2, 1, ledsPerPixel, BUTTON_PRESS_RIGHT); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_UP], 1, 0, ledsPerPixel, BUTTON_PRESS_UP); } /** * @brief Create an LED layout using a 3x8 matrix. */ -std::vector> NeoPicoLEDAddon::generatedLEDStickless(vector> *positions) +void NeoPicoLEDAddon::generatedLEDStickless(vector> *positions, uint8_t ledsPerPixel) { - std::vector> pixels = - { - { - PIXEL(BUTTON_LABEL_LEFT, GAMEPAD_MASK_DL), - NO_PIXEL, - NO_PIXEL, - }, - { - PIXEL(BUTTON_LABEL_DOWN, GAMEPAD_MASK_DD), - NO_PIXEL, - NO_PIXEL, - }, - { - PIXEL(BUTTON_LABEL_RIGHT, GAMEPAD_MASK_DR), - NO_PIXEL, - NO_PIXEL, - }, - { - PIXEL(BUTTON_LABEL_UP, GAMEPAD_MASK_DU), - NO_PIXEL, - NO_PIXEL, - }, - { - PIXEL(BUTTON_LABEL_B3, GAMEPAD_MASK_B3), - PIXEL(BUTTON_LABEL_B1, GAMEPAD_MASK_B1), - NO_PIXEL, - }, - { - PIXEL(BUTTON_LABEL_B4, GAMEPAD_MASK_B4), - PIXEL(BUTTON_LABEL_B2, GAMEPAD_MASK_B2), - NO_PIXEL, - }, - { - PIXEL(BUTTON_LABEL_R1, GAMEPAD_MASK_R1), - PIXEL(BUTTON_LABEL_R2, GAMEPAD_MASK_R2), - NO_PIXEL, - }, - { - PIXEL(BUTTON_LABEL_L1, GAMEPAD_MASK_L1), - PIXEL(BUTTON_LABEL_L2, GAMEPAD_MASK_L2), - NO_PIXEL, - }, - { - PIXEL(BUTTON_LABEL_S1, GAMEPAD_MASK_S1), - PIXEL(BUTTON_LABEL_S2, GAMEPAD_MASK_S2), - PIXEL(BUTTON_LABEL_L3, GAMEPAD_MASK_L3), - PIXEL(BUTTON_LABEL_R3, GAMEPAD_MASK_R3), - PIXEL(BUTTON_LABEL_A1, GAMEPAD_MASK_A1), - PIXEL(BUTTON_LABEL_A2, GAMEPAD_MASK_A2), - }, - }; + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_LEFT], 0, 2, ledsPerPixel, BUTTON_PRESS_LEFT); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_DOWN], 2, 2, ledsPerPixel, BUTTON_PRESS_DOWN); - return pixels; + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_RIGHT], 4, 3, ledsPerPixel, BUTTON_PRESS_RIGHT); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_UP], 5, 6, ledsPerPixel, BUTTON_PRESS_UP); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B3], 6, 2, ledsPerPixel, BUTTON_PRESS_B3); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B1], 6, 4, ledsPerPixel, BUTTON_PRESS_B1); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B4], 8, 1, ledsPerPixel, BUTTON_PRESS_B4); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B2], 8, 3, ledsPerPixel, BUTTON_PRESS_B2); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_R1], 10, 1, ledsPerPixel, BUTTON_PRESS_R1); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_R2], 10, 3, ledsPerPixel, BUTTON_PRESS_R2); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_L1], 12, 1, ledsPerPixel, BUTTON_PRESS_L1); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_L2], 12, 3, ledsPerPixel, BUTTON_PRESS_L2); + + //extras + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_S1], 13, 0, ledsPerPixel, BUTTON_PRESS_S1); + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_S2], 14, 0, ledsPerPixel, BUTTON_PRESS_S2); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_L3], 7, 0, ledsPerPixel, BUTTON_PRESS_L3); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_R3], 7, 5, ledsPerPixel, BUTTON_PRESS_R3); + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_A1], 12, 0, ledsPerPixel, BUTTON_PRESS_A1); + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_A2], 11, 0, ledsPerPixel, BUTTON_PRESS_A2); } /** * @brief Create an LED layout using a 2x7 matrix. */ -std::vector> NeoPicoLEDAddon::generatedLEDWasd(std::vector> *positions) +void NeoPicoLEDAddon::generatedLEDWasd(std::vector> *positions, uint8_t ledsPerPixel) { - std::vector> pixels = - { - { - NO_PIXEL, - PIXEL(BUTTON_LABEL_LEFT, GAMEPAD_MASK_DL), - }, - { - PIXEL(BUTTON_LABEL_UP, GAMEPAD_MASK_DU), - PIXEL(BUTTON_LABEL_DOWN, GAMEPAD_MASK_DD), - }, - { - NO_PIXEL, - PIXEL(BUTTON_LABEL_RIGHT, GAMEPAD_MASK_DR), - }, - { - PIXEL(BUTTON_LABEL_B3, GAMEPAD_MASK_B3), - PIXEL(BUTTON_LABEL_B1, GAMEPAD_MASK_B1), - }, - { - PIXEL(BUTTON_LABEL_B4, GAMEPAD_MASK_B4), - PIXEL(BUTTON_LABEL_B2, GAMEPAD_MASK_B2), - }, - { - PIXEL(BUTTON_LABEL_R1, GAMEPAD_MASK_R1), - PIXEL(BUTTON_LABEL_R2, GAMEPAD_MASK_R2), - }, - { - PIXEL(BUTTON_LABEL_L1, GAMEPAD_MASK_L1), - PIXEL(BUTTON_LABEL_L2, GAMEPAD_MASK_L2), - }, - { - PIXEL(BUTTON_LABEL_S1, GAMEPAD_MASK_S1), - PIXEL(BUTTON_LABEL_S2, GAMEPAD_MASK_S2), - PIXEL(BUTTON_LABEL_L3, GAMEPAD_MASK_L3), - PIXEL(BUTTON_LABEL_R3, GAMEPAD_MASK_R3), - PIXEL(BUTTON_LABEL_A1, GAMEPAD_MASK_A1), - PIXEL(BUTTON_LABEL_A2, GAMEPAD_MASK_A2), - }, - }; + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_LEFT], 0, 2, ledsPerPixel, BUTTON_PRESS_LEFT); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_DOWN], 2, 2, ledsPerPixel, BUTTON_PRESS_DOWN); - return pixels; + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_RIGHT], 4, 3, ledsPerPixel, BUTTON_PRESS_RIGHT); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_UP], 2, 1, ledsPerPixel, BUTTON_PRESS_UP); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B3], 6, 2, ledsPerPixel, BUTTON_PRESS_B3); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B1], 6, 4, ledsPerPixel, BUTTON_PRESS_B1); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B4], 8, 1, ledsPerPixel, BUTTON_PRESS_B4); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B2], 8, 3, ledsPerPixel, BUTTON_PRESS_B2); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_R1], 10, 1, ledsPerPixel, BUTTON_PRESS_R1); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_R2], 10, 3, ledsPerPixel, BUTTON_PRESS_R2); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_L1], 12, 1, ledsPerPixel, BUTTON_PRESS_L1); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_L2], 12, 3, ledsPerPixel, BUTTON_PRESS_L2); + + //extras + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_S1], 13, 0, ledsPerPixel, BUTTON_PRESS_S1); + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_S2], 14, 0, ledsPerPixel, BUTTON_PRESS_S2); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_L3], 7, 0, ledsPerPixel, BUTTON_PRESS_L3); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_R3], 7, 5, ledsPerPixel, BUTTON_PRESS_R3); + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_A1], 12, 0, ledsPerPixel, BUTTON_PRESS_A1); + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_A2], 11, 0, ledsPerPixel, BUTTON_PRESS_A2); } /** * @brief Create an LED layout using a 2x7 matrix for the mirrored Fightboard. */ -std::vector> NeoPicoLEDAddon::generatedLEDWasdFBM(std::vector> *positions) +void NeoPicoLEDAddon::generatedLEDWasdFBM(std::vector> *positions, uint8_t ledsPerPixel) { - std::vector> pixels = - { - { - PIXEL(BUTTON_LABEL_L1, GAMEPAD_MASK_L1), - PIXEL(BUTTON_LABEL_L2, GAMEPAD_MASK_L2), - }, - { - PIXEL(BUTTON_LABEL_R1, GAMEPAD_MASK_R1), - PIXEL(BUTTON_LABEL_R2, GAMEPAD_MASK_R2), - }, - { - PIXEL(BUTTON_LABEL_B4, GAMEPAD_MASK_B4), - PIXEL(BUTTON_LABEL_B2, GAMEPAD_MASK_B2), - }, - { - PIXEL(BUTTON_LABEL_B3, GAMEPAD_MASK_B3), - PIXEL(BUTTON_LABEL_B1, GAMEPAD_MASK_B1), - }, - { - NO_PIXEL, - PIXEL(BUTTON_LABEL_LEFT, GAMEPAD_MASK_DL), - }, - { - PIXEL(BUTTON_LABEL_UP, GAMEPAD_MASK_DU), - PIXEL(BUTTON_LABEL_DOWN, GAMEPAD_MASK_DD), - }, - { - NO_PIXEL, - PIXEL(BUTTON_LABEL_RIGHT, GAMEPAD_MASK_DR), - }, - { - PIXEL(BUTTON_LABEL_S1, GAMEPAD_MASK_S1), - PIXEL(BUTTON_LABEL_S2, GAMEPAD_MASK_S2), - PIXEL(BUTTON_LABEL_L3, GAMEPAD_MASK_L3), - PIXEL(BUTTON_LABEL_R3, GAMEPAD_MASK_R3), - PIXEL(BUTTON_LABEL_A1, GAMEPAD_MASK_A1), - PIXEL(BUTTON_LABEL_A2, GAMEPAD_MASK_A2), - }, - }; + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_LEFT], 8, 2, ledsPerPixel, BUTTON_PRESS_LEFT); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_DOWN], 10, 2, ledsPerPixel, BUTTON_PRESS_DOWN); - return pixels; + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_RIGHT], 12, 3, ledsPerPixel, BUTTON_PRESS_RIGHT); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_UP], 10, 1, ledsPerPixel, BUTTON_PRESS_UP); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B3], 0, 2, ledsPerPixel, BUTTON_PRESS_B3); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B1], 0, 4, ledsPerPixel, BUTTON_PRESS_B1); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B4], 2, 1, ledsPerPixel, BUTTON_PRESS_B4); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_B2], 2, 3, ledsPerPixel, BUTTON_PRESS_B2); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_R1], 4, 1, ledsPerPixel, BUTTON_PRESS_R1); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_R2], 4, 3, ledsPerPixel, BUTTON_PRESS_R2); + + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_L1], 6, 1, ledsPerPixel, BUTTON_PRESS_L1); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_L2], 6, 3, ledsPerPixel, BUTTON_PRESS_L2); + + //extras + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_S1], 13, 0, ledsPerPixel, BUTTON_PRESS_S1); + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_S2], 14, 0, ledsPerPixel, BUTTON_PRESS_S2); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_L3], 1, 0, ledsPerPixel, BUTTON_PRESS_L3); + generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_R3], 1, 5, ledsPerPixel, BUTTON_PRESS_R3); + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_A1], 12, 0, ledsPerPixel, BUTTON_PRESS_A1); + //generateLegacyIndividualLight(buttonPositions[BUTTON_LABEL_A2], 11, 0, ledsPerPixel, BUTTON_PRESS_A2); } -std::vector> NeoPicoLEDAddon::createLEDLayout(ButtonLayout layout, uint8_t ledsPerPixel, uint8_t ledButtonCount) +void NeoPicoLEDAddon::createLEDLayout(ButtonLayout layout, uint8_t ledsPerPixel, uint8_t ledButtonCount) { vector> positions(ledButtonCount); for (int i = 0; i != ledButtonCount; i++) @@ -835,35 +561,36 @@ std::vector> NeoPicoLEDAddon::createLEDLayout(ButtonLayout la positions[i][l] = (i * ledsPerPixel) + l; } - switch (static_cast(layout)) - { - case BUTTON_LAYOUT_STICKLESS: - case BUTTON_LAYOUT_OPENCORE0WASDA: - case BUTTON_LAYOUT_STICKLESS_13: - case BUTTON_LAYOUT_STICKLESS_14: - case BUTTON_LAYOUT_STICKLESS_16: - case BUTTON_LAYOUT_STICKLESS_R16: - case BUTTON_LAYOUT_BOARD_DEFINED_A: - return generatedLEDStickless(&positions); - case BUTTON_LAYOUT_FIGHTBOARD_MIRRORED: - return generatedLEDWasdFBM(&positions); - case BUTTON_LAYOUT_BUTTONS_ANGLED: - case BUTTON_LAYOUT_FIGHTBOARD_STICK: - return generatedLEDWasd(&positions); - case BUTTON_LAYOUT_BLANKA: - case BUTTON_LAYOUT_BUTTONS_BASIC: - case BUTTON_LAYOUT_KEYBOARD_ANGLED: - case BUTTON_LAYOUT_KEYBOARDA: - case BUTTON_LAYOUT_DANCEPADA: - case BUTTON_LAYOUT_TWINSTICKA: - case BUTTON_LAYOUT_ARCADE: - case BUTTON_LAYOUT_VLXA: - default: - return generatedLEDButtons(&positions); - } - - assert(false); - return std::vector>(); + switch (static_cast(layout)) + { + case BUTTON_LAYOUT_STICKLESS: + case BUTTON_LAYOUT_OPENCORE0WASDA: + case BUTTON_LAYOUT_STICKLESS_13: + case BUTTON_LAYOUT_STICKLESS_14: + case BUTTON_LAYOUT_STICKLESS_16: + case BUTTON_LAYOUT_STICKLESS_R16: + case BUTTON_LAYOUT_BOARD_DEFINED_A: + generatedLEDStickless(&positions, ledsPerPixel); + break; + case BUTTON_LAYOUT_FIGHTBOARD_MIRRORED: + generatedLEDWasdFBM(&positions, ledsPerPixel); + break; + case BUTTON_LAYOUT_BUTTONS_ANGLED: + case BUTTON_LAYOUT_FIGHTBOARD_STICK: + generatedLEDWasd(&positions, ledsPerPixel); + break; + case BUTTON_LAYOUT_BLANKA: + case BUTTON_LAYOUT_BUTTONS_BASIC: + case BUTTON_LAYOUT_KEYBOARD_ANGLED: + case BUTTON_LAYOUT_KEYBOARDA: + case BUTTON_LAYOUT_DANCEPADA: + case BUTTON_LAYOUT_TWINSTICKA: + case BUTTON_LAYOUT_ARCADE: + case BUTTON_LAYOUT_VLXA: + default: + generatedLEDButtons(&positions, ledsPerPixel); + break; + } } uint8_t NeoPicoLEDAddon::setupButtonPositions() @@ -898,233 +625,196 @@ uint8_t NeoPicoLEDAddon::setupButtonPositions() return buttonCount; } -GamepadHotkey NeoPicoLEDAddon::animationHotkeys(Gamepad *gamepad) +void NeoPicoLEDAddon::AssignLedPreset(const unsigned char* data, int32_t dataSize) { - GamepadHotkey action = HOTKEY_LEDS_NONE; - if (gamepad->pressedS1() && gamepad->pressedS2()) - { - if (gamepad->pressedB3()) - { - action = HOTKEY_LEDS_ANIMATION_UP; - gamepad->state.buttons &= ~(GAMEPAD_MASK_B3 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); - } - else if (gamepad->pressedB1()) - { - action = HOTKEY_LEDS_ANIMATION_DOWN; - gamepad->state.buttons &= ~(GAMEPAD_MASK_B1 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); - } - else if (gamepad->pressedB4()) - { - action = HOTKEY_LEDS_BRIGHTNESS_UP; - gamepad->state.buttons &= ~(GAMEPAD_MASK_B4 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); - } - else if (gamepad->pressedB2()) - { - action = HOTKEY_LEDS_BRIGHTNESS_DOWN; - gamepad->state.buttons &= ~(GAMEPAD_MASK_B2 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); - } - else if (gamepad->pressedR1()) - { - action = HOTKEY_LEDS_PARAMETER_UP; - gamepad->state.buttons &= ~(GAMEPAD_MASK_R1 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); - } - else if (gamepad->pressedR2()) - { - action = HOTKEY_LEDS_PARAMETER_DOWN; - gamepad->state.buttons &= ~(GAMEPAD_MASK_R2 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); - } - else if (gamepad->pressedL1()) - { - action = HOTKEY_LEDS_PRESS_PARAMETER_UP; - gamepad->state.buttons &= ~(GAMEPAD_MASK_L1 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); - } - else if (gamepad->pressedL2()) - { - action = HOTKEY_LEDS_PRESS_PARAMETER_DOWN; - gamepad->state.buttons &= ~(GAMEPAD_MASK_L2 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); - } - else if (gamepad->pressedL3()) - { - action = HOTKEY_LEDS_FADETIME_DOWN; - gamepad->state.buttons &= ~(GAMEPAD_MASK_L3 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); - } - else if (gamepad->pressedR3()) - { - action = HOTKEY_LEDS_FADETIME_UP; - gamepad->state.buttons &= ~(GAMEPAD_MASK_R3 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); - } - } - return action; + LEDOptions& options = Storage::getInstance().getLedOptions(); + options.lightClusterData_count = 0; + options.lightClusterDataInitialised = true; + for (int thisEntryIndex = 0; (thisEntryIndex * 6) + 5 < dataSize; ++thisEntryIndex) //each data entry has 6 elements + { + int thisDataIndex = thisEntryIndex * 6; + options.lightClusterData[thisEntryIndex].lightLocationData = data[thisDataIndex]; + options.lightClusterData[thisEntryIndex].lightLocationData += ((int)data[thisDataIndex+1]) << 8; + options.lightClusterData[thisEntryIndex].lightLocationData += ((int)data[thisDataIndex+2]) << 16; + options.lightClusterData[thisEntryIndex].lightLocationData += ((int)data[thisDataIndex+3]) << 24; + options.lightClusterData[thisEntryIndex].lightTypeData = ((int)data[thisDataIndex+4]); + options.lightClusterData[thisEntryIndex].lightTypeData += ((int)data[thisDataIndex+5]) << 8; + + options.lightClusterData_count = thisEntryIndex + 1; + + if(options.lightClusterData_count >= FRAME_MAX) //100 entries total + return; + } } -void NeoPicoLEDAddon::ambientHotkeys(Gamepad *gamepad) +void NeoPicoLEDAddon::configureLEDs() { - bool reqSave = false; - GamepadHotkey action = HOTKEY_LEDS_NONE; - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - LEDOptions & ledOptions = Storage::getInstance().getLedOptions(); - - // Only allow Start + Key Ambient Changes if Case RGB is enabled - if(gamepad->pressedS2() && (gamepad->pressedS1() == false)) // start (not start+back) - { - if(gamepad->pressedL1()) { // LB - action = HOTKEY_AMBIENT_LIGHT_EFFECTS_CHANGE; - gamepad->state.buttons &= ~(GAMEPAD_MASK_L1 | GAMEPAD_MASK_S2); - if (lastAmbientAction != action ) { - // Ambient changes are all done inside of neopico instead of animation station? - animationOptions.ambientLightEffectsCountIndex++; - if(animationOptions.ambientLightEffectsCountIndex > AL_EFFECT_MODE_MAX - 1){ - animationOptions.ambientLightEffectsCountIndex = 0; - } - // Reset our ambient light RGB - ambientLight.r = 0x00; - ambientLight.g = 0x00; - ambientLight.b = 0x00; - alCurrentFrame = 0; - alFrameToRGB = 0; - alReverse = false; - chaseLightIndex = ledOptions.caseRGBIndex; - alBrightnessBreathX = 1.00f; - breathLedEffectCycle = 0; - reqSave = true; - } - } else if (gamepad->pressedL2()) { // LT (Different from COSMOX, we just cycle instead of temporary disable) - action = HOTKEY_AMBIENT_LIGHT_EFFECTS_ON_OFF; - if (lastAmbientAction != action ) { - // Move the other way - if(animationOptions.ambientLightEffectsCountIndex == 0 ) - animationOptions.ambientLightEffectsCountIndex = AL_EFFECT_MODE_MAX - 1; - else - animationOptions.ambientLightEffectsCountIndex--; - // Reset our ambient light RGB - ambientLight.r = 0x00; - ambientLight.g = 0x00; - ambientLight.b = 0x00; - alCurrentFrame = 0; - alFrameToRGB = 0; - alReverse = false; - chaseLightIndex = ledOptions.caseRGBIndex; - alBrightnessBreathX = 1.00f; - breathLedEffectCycle = 0; - reqSave = true; - } - // turn off brightness for ambient lights - gamepad->state.buttons &= ~(GAMEPAD_MASK_L2 | GAMEPAD_MASK_S2); - } else if (gamepad->pressedB4() && (gamepad->pressedB3() == false)) { // Y not X+Y - action = HOTKEY_AMBIENT_LIGHT_EFFECTS_BRIGHTNESS_UP; - if ( lastAmbientAction != action ) { - float customBrightnessStep = 1.0f / as.GetBrightnessSteps(); - if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_CHASE ) { - animationOptions.alChaseBrightnessCustomX = min(animationOptions.alChaseBrightnessCustomX+customBrightnessStep, 1.00f); - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_GRADIENT ) { - animationOptions.alGradientBrightnessCustomX = min(animationOptions.alGradientBrightnessCustomX+customBrightnessStep, 1.00f); - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_STATIC_COLOR ) { - animationOptions.alStaticColorBrightnessCustomX = min(animationOptions.alStaticColorBrightnessCustomX+customBrightnessStep, 1.00f); - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_STATIC_THEME ) { - animationOptions.alStaticBrightnessCustomThemeX = min(animationOptions.alStaticBrightnessCustomThemeX+customBrightnessStep, 1.00f); - } // do nothing for breath - - reqSave = true; - } - gamepad->state.buttons &= ~(GAMEPAD_MASK_B4 | GAMEPAD_MASK_S2); - } else if (gamepad->pressedB2()) { // B - action = HOTKEY_AMBIENT_LIGHT_EFFECTS_BRIGHTNESS_DOWN; - if ( lastAmbientAction != action ) { - float customBrightnessStep = 1.0f / as.GetBrightnessSteps(); - if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_CHASE ) { - animationOptions.alChaseBrightnessCustomX = max(animationOptions.alChaseBrightnessCustomX-customBrightnessStep, 0.00f); - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_GRADIENT ) { - animationOptions.alGradientBrightnessCustomX = max(animationOptions.alGradientBrightnessCustomX-customBrightnessStep, 0.00f); - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_STATIC_COLOR ) { - animationOptions.alStaticColorBrightnessCustomX = max(animationOptions.alStaticColorBrightnessCustomX-customBrightnessStep, 0.00f); - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_STATIC_THEME ) { - animationOptions.alStaticBrightnessCustomThemeX = max(animationOptions.alStaticBrightnessCustomThemeX-customBrightnessStep, 0.00f); - } // do nothing for breath - reqSave = true; - } - gamepad->state.buttons &= ~(GAMEPAD_MASK_B2 | GAMEPAD_MASK_S2); - } else if (gamepad->pressedR1()) { // RB - action = HOTKEY_AMBIENT_LIGHT_EFFECTS_PARAMETER_UP; - if ( lastAmbientAction != action ) { - if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_STATIC_COLOR ) { - animationOptions.alCustomStaticColorIndex++; - if (animationOptions.alCustomStaticColorIndex > AL_STATIC_COLOR_COUNT - 1){ - animationOptions.alCustomStaticColorIndex = 0; // loop - } - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_STATIC_THEME ) { - animationOptions.alCustomStaticThemeIndex++; - if (animationOptions.alCustomStaticThemeIndex > AL_ROW - 1){ - animationOptions.alCustomStaticThemeIndex = 0; // loop - } - } - reqSave = true; - } - gamepad->state.buttons &= ~(GAMEPAD_MASK_R1 | GAMEPAD_MASK_S2); - } else if (gamepad->pressedR2()) { // RT - action = HOTKEY_AMBIENT_LIGHT_EFFECTS_PARAMETER_DOWN; - if ( lastAmbientAction != action ) { - if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_STATIC_COLOR ) { - if (animationOptions.alCustomStaticColorIndex == 0 ) { - animationOptions.alCustomStaticColorIndex = AL_STATIC_COLOR_COUNT - 1; - } else { - animationOptions.alCustomStaticColorIndex--; - } - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_STATIC_THEME ) { - if (animationOptions.alCustomStaticThemeIndex == 0 ) { - animationOptions.alCustomStaticThemeIndex = AL_ROW - 1; - } else { - animationOptions.alCustomStaticThemeIndex--; - } - } - reqSave = true; - } - gamepad->state.buttons &= ~(GAMEPAD_MASK_R2 | GAMEPAD_MASK_S2); - } else if (gamepad->pressedL3()) { // LS - action = HOTKEY_AMBIENT_LIGHT_EFFECTS_FRAME_SPEED_UP; - if ( lastAmbientAction != action ) { - if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_GRADIENT ) { - animationOptions.ambientLightGradientSpeed = min(animationOptions.ambientLightGradientSpeed+1,(uint32_t)6); - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_CHASE ) { - animationOptions.ambientLightChaseSpeed = max(animationOptions.ambientLightChaseSpeed-20,(int32_t)0); - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_BREATH ) { - animationOptions.ambientLightBreathSpeed = min(animationOptions.ambientLightBreathSpeed+0.01f,0.05f); - } - reqSave = true; - } - gamepad->state.buttons &= ~(GAMEPAD_MASK_L3 | GAMEPAD_MASK_S2); - } else if (gamepad->pressedR3()) { // RS - action = HOTKEY_AMBIENT_LIGHT_EFFECTS_FRAME_SPEED_DOWN; - if ( lastAmbientAction != action ) { - if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_GRADIENT ) { - animationOptions.ambientLightGradientSpeed = max(animationOptions.ambientLightGradientSpeed-1,(uint32_t)1); - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_CHASE ) { - animationOptions.ambientLightChaseSpeed = min(animationOptions.ambientLightChaseSpeed+20,(int32_t)100); - } else if ( animationOptions.ambientLightEffectsCountIndex == AL_CUSTOM_EFFECT_BREATH ) { - animationOptions.ambientLightBreathSpeed = max(animationOptions.ambientLightBreathSpeed-0.01f,0.01f); - } - reqSave = true; - } - gamepad->state.buttons &= ~(GAMEPAD_MASK_R3 | GAMEPAD_MASK_S2); - } else if (gamepad->pressedB1() && gamepad->pressedB3()) { // A + X - // Cycle through off, ambient, and linked - action = HOTKEY_AMBIENT_LIGHT_EFFECTS_CUSTOM_LINKAGE; - if ( lastAmbientAction != action ) { - if ( ledOptions.caseRGBType == CASE_RGB_TYPE_AMBIENT ) { - ledOptions.caseRGBType = CASE_RGB_TYPE_LINKED; - } else if ( ledOptions.caseRGBType == CASE_RGB_TYPE_LINKED ) { - ledOptions.caseRGBType = CASE_RGB_TYPE_NONE; - } else if ( ledOptions.caseRGBType == CASE_RGB_TYPE_NONE ) { - ledOptions.caseRGBType = CASE_RGB_TYPE_AMBIENT; - } - reqSave = true; - } - gamepad->state.buttons &= ~(GAMEPAD_MASK_B1 | GAMEPAD_MASK_B3 | GAMEPAD_MASK_S2); + LEDOptions& ledOptions = Storage::getInstance().getLedOptions(); + + //New grid based setup + if(ledOptions.lightClusterDataInitialised == false) + { + ledOptions.lightClusterDataInitialised = true; + + //do we have any presets defined? + if(strcmp("", LIGHT_DATA_NAME_DEFAULT) != 0) + { + const unsigned char lightData[] = { LIGHT_DATA_DEFAULT }; + AssignLedPreset(lightData, sizeof(lightData)); + } + else + { + //fall back to old matrix setup which will now approximate a grid and return the same data struct ready for light creation + uint8_t buttonCount = setupButtonPositions(); + createLEDLayout(static_cast(ledOptions.ledLayout), ledOptions.ledsPerButton, buttonCount); } + + EventManager::getInstance().triggerEvent(new GPStorageSaveEvent(true)); + } + + GenerateLights(); + ledCount = RGBLights.GetLedCount(); + + if (ledOptions.pledType == PLED_TYPE_RGB && PLED_COUNT > 0) + { + int32_t pledIndexes[] = { ledOptions.pledIndex1, ledOptions.pledIndex2, ledOptions.pledIndex3, ledOptions.pledIndex4 }; + for (int i = 0; i < PLED_COUNT; i++) + { + if(pledIndexes[i] >= 0 && pledIndexes[i] <= 99 && pledIndexes[i] > ledCount) + ledCount = pledIndexes[i]; + } + } + + // Setup neo pico (once only) + if(!bHasSetupNeoPico) + { + bHasSetupNeoPico = true; + neopico.Setup(ledOptions.dataPin, ledCount, static_cast(ledOptions.ledFormat), pio0,0); + neopico.Off(); + } + else + { + neopico.ChangeNumPixels(ledCount); + } + + Animation::format = static_cast(ledOptions.ledFormat); + AnimStation.SetMaxBrightness(ledOptions.brightnessMaximum); + AnimStation.SetBrightnessStepValue(AnimStation.options.brightness); + AnimStation.SetLights(RGBLights); + AnimStation.SetMode(as.options.baseProfileIndex); +} + +void NeoPicoLEDAddon::decompressSettings() +{ + AnimStation.DecompressSettings(); +} + +//////////////////////////////////////////// +//New RGBLight setups +//////////////////////////////////////////// + +void NeoPicoLEDAddon::GenerateLights() +{ + int minX = -1; + int minY = -1; + + LEDOptions& ledOptions = Storage::getInstance().getLedOptions(); + + std::vector generatedLights; + for(int index = 0; index < (int)ledOptions.lightClusterData_count; ++index) + { + int ledIndex = (ledOptions.lightClusterData[index].lightLocationData) & 0xFF; + int ledCount = (ledOptions.lightClusterData[index].lightLocationData >> 8) & 0xFF; + int posX = (ledOptions.lightClusterData[index].lightLocationData >> 16) & 0xFF; + int posY = (ledOptions.lightClusterData[index].lightLocationData >> 24) & 0xFF; + int gpioPin = (ledOptions.lightClusterData[index].lightTypeData) & 0xFF; + int ledType = (ledOptions.lightClusterData[index].lightTypeData >> 8) & 0xFF; + //Data format = {first led index, leds on this light, xcoord, ycoord, GPIO pin, Type} + LightPosition newLightPos (posX, posY); + Light newLight (ledIndex, + ledCount, + newLightPos, + gpioPin, + (LightType)ledType); + + //Update mins + if(minX == -1 || newLight.Position.XPosition < minX) + minX = newLight.Position.XPosition; + if(minY == -1 || newLight.Position.YPosition < minY) + minY = newLight.Position.YPosition; + + generatedLights.push_back(newLight); + } + + //check for critical error + if(minX < 0 || minY < 0) + return; + + //Strip Empty rows and coloums on left and top side + for(int index = 0; index < (int)generatedLights.size(); ++index) + { + generatedLights[index].Position.XPosition -= minX; + generatedLights[index].Position.YPosition -= minY; } - if (reqSave) { - EventManager::getInstance().triggerEvent(new GPStorageSaveEvent(false)); + RGBLights.Setup(generatedLights); +} + +//////////////////////////////////////////// +//Helper functions +//////////////////////////////////////////// + +GamepadHotkey NeoPicoLEDAddon::ProcessAnimationHotkeys(Gamepad *gamepad) +{ + GamepadHotkey action = HOTKEY_LEDS_NONE; + + if (gamepad->pressedS1() && gamepad->pressedS2()) + { + if (gamepad->pressedB3()) + { + action = HOTKEY_LEDS_PROFILE_UP; + gamepad->state.buttons &= ~(GAMEPAD_MASK_B3 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); + } + else if (gamepad->pressedB1()) + { + action = HOTKEY_LEDS_PROFILE_DOWN; + gamepad->state.buttons &= ~(GAMEPAD_MASK_B1 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); + } + else if (gamepad->pressedB4()) + { + action = HOTKEY_LEDS_BRIGHTNESS_UP; + gamepad->state.buttons &= ~(GAMEPAD_MASK_B4 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); + } + else if (gamepad->pressedB2()) + { + action = HOTKEY_LEDS_BRIGHTNESS_DOWN; + gamepad->state.buttons &= ~(GAMEPAD_MASK_B2 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); + } + else if (gamepad->pressedR1()) + { + action = HOTKEY_LEDS_PARAMETER_UP; + gamepad->state.buttons &= ~(GAMEPAD_MASK_R1 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); + } + else if (gamepad->pressedR2()) + { + action = HOTKEY_LEDS_PARAMETER_DOWN; + gamepad->state.buttons &= ~(GAMEPAD_MASK_R2 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); + } + else if (gamepad->pressedL1()) + { + action = HOTKEY_LEDS_PRESS_PARAMETER_UP; + gamepad->state.buttons &= ~(GAMEPAD_MASK_L1 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); + } + else if (gamepad->pressedL2()) + { + action = HOTKEY_LEDS_PRESS_PARAMETER_DOWN; + gamepad->state.buttons &= ~(GAMEPAD_MASK_L2 | GAMEPAD_MASK_S1 | GAMEPAD_MASK_S2); + } } - lastAmbientAction = action; + return action; } + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +//END RBG LEDs /////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/animationstation/animation.cpp b/src/animationstation/animation.cpp index 87ced6d9fe..ff8fa8b7dc 100644 --- a/src/animationstation/animation.cpp +++ b/src/animationstation/animation.cpp @@ -7,103 +7,204 @@ #define PRESS_COOLDOWN_MIN 0 LEDFormat Animation::format; -std::map Animation::times = {}; -std::map Animation::hitColor = {}; - -Animation::Animation(PixelMatrix &matrix) : matrix(&matrix) { - for (size_t r = 0; r != matrix.pixels.size(); r++) { - for (size_t c = 0; c != matrix.pixels[r].size(); c++) { - if (matrix.pixels[r][c].index == NO_PIXEL.index) - continue; - times.insert_or_assign(matrix.pixels[r][c].index, 0); - hitColor.insert_or_assign(matrix.pixels[r][c].index, defaultColor); - } + +Animation::Animation(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType) : RGBLights(&InRGBLights) +{ + ButtonCaseEffectType = InButtonCaseEffectType; + + fadeTimes.clear(); + for(int ledIndex = 0; ledIndex < RGBLights->GetLedCount(); ++ledIndex) + { + fadeTimes.push_back(0); } + + holdTimeInMs = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].buttonPressHoldTimeInMs; + fadeoutTimeInMs = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].buttonPressFadeOutTimeInMs; + + //Since we use the fadeTimes array to know if a light is held. make sure that we hold for at least 1 ms. + if(holdTimeInMs <= 0) + holdTimeInMs = 1; } -void Animation::UpdatePixels(std::vector inpixels) { - this->pixels = inpixels; +void Animation::UpdatePressed(std::vector InPressedPins) +{ + this->pressedPins = InPressedPins; } -void Animation::UpdateTime() { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - coolDownTimeInMs = animationOptions.buttonPressColorCooldownTimeInMs; +void Animation::ClearPressed() +{ + this->pressedPins.clear(); +} +void Animation::UpdateTime() +{ absolute_time_t currentTime = get_absolute_time(); updateTimeInMs = absolute_time_diff_us(lastUpdateTime, currentTime) / 1000; lastUpdateTime = currentTime; } -void Animation::UpdatePresses(RGB (&frame)[100]) { - // Queue up blend on hit - for (size_t p = 0; p < pixels.size(); p++) { - if (pixels[p].index != NO_PIXEL.index) { - times[pixels[p].index] = coolDownTimeInMs; - hitColor[pixels[p].index] = frame[pixels[p].positions[0]]; +void Animation::UpdatePresses() +{ + if(!isButtonAnimation) + return; + + //Set hold/fade time for all pressed buttons + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + if(RGBLights->AllLights[lightIndex].Type != LightType_ActionButton) + continue; + + for(unsigned int pressedPinIndex = 0; pressedPinIndex < pressedPins.size(); ++pressedPinIndex) + { + if(pressedPins[pressedPinIndex] == RGBLights->AllLights[lightIndex].GIPOPin) + { + uint8_t firstLightIndex = RGBLights->AllLights[lightIndex].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[lightIndex].LedsPerLight; + bool wasPressedOrFading = false; + for(int ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + if(fadeTimes[ledIndex] > 0) + wasPressedOrFading = true; + fadeTimes[ledIndex] = GetFadeTime(); + } + + //if this is a new press, let effects know + if(!wasPressedOrFading) + { + NewPressForPin(lightIndex); + } + } } } } -void Animation::DecrementFadeCounter(int32_t index) { - times[index] -= updateTimeInMs; - if (times[index] < 0) { - times[index] = 0; - }; -} - -void Animation::ClearPixels() { - this->pixels.clear(); +int32_t Animation::GetFadeTime() +{ + return holdTimeInMs + fadeoutTimeInMs; } -/* Some of these animations are filtered to specific pixels, such as button press animations. -This somewhat backwards named method determines if a specific pixel is _not_ included in the filter */ -bool Animation::notInFilter(Pixel pixel) { - if (!this->filtered) { - return false; - } +void Animation::DecrementFadeCounters() +{ + if(!isButtonAnimation) + return; + + //Set hold/fade time for all pressed buttons + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + //early out, save frames! + if(RGBLights->AllLights[lightIndex].Type != LightType_ActionButton || fadeTimes[RGBLights->AllLights[lightIndex].FirstLedIndex] <= 0) + continue; + + //is this button still pressed ? + bool wasPressed = false; + for(unsigned int pressedPinIndex = 0; pressedPinIndex < pressedPins.size(); ++pressedPinIndex) + { + if(pressedPins[pressedPinIndex] == RGBLights->AllLights[lightIndex].GIPOPin) + { + wasPressed = true; + } + } - for (size_t i = 0; i < this->pixels.size(); i++) { - if (pixel == this->pixels.at(i)) { - return false; + //if not then we can safely decrement the timer + if(!wasPressed) + { + uint8_t firstLightIndex = RGBLights->AllLights[lightIndex].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[lightIndex].LedsPerLight; + for(int ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + fadeTimes[ledIndex] -= updateTimeInMs; + if (fadeTimes[ledIndex] < 0) + fadeTimes[ledIndex] = 0; + } } } +} + +RGB Animation::BlendColor(RGB start, RGB end, float alpha) +{ + RGB result = ColorBlack; - return true; + if (alpha < 0.0f) alpha = 0.0f; + if (alpha > 1.0f) alpha = 1.0f; + + result.r = static_cast(static_cast(start.r) + ((static_cast(end.r) - static_cast(start.r)) * alpha)); + result.g = static_cast(static_cast(start.g) + ((static_cast(end.g) - static_cast(start.g)) * alpha)); + result.b = static_cast(static_cast(start.b) + ((static_cast(end.b) - static_cast(start.b)) * alpha)); + + return result; } -RGB Animation::BlendColor(RGB start, RGB end, uint32_t timeRemainingInMs) { +RGB Animation::FadeColor(RGB start, RGB end, uint32_t timeRemainingInMs) +{ RGB result = ColorBlack; - if (timeRemainingInMs <= 0) { + //fade over? + if (timeRemainingInMs <= 0) return end; - } - float progress = 1.0f - (static_cast(timeRemainingInMs) / static_cast(coolDownTimeInMs)); + //still in hold time? + if(timeRemainingInMs > fadeoutTimeInMs) + return start; + + float progress = 1.0f - (static_cast(timeRemainingInMs) / static_cast(fadeoutTimeInMs)); if (progress < 0.0f) progress = 0.0f; if (progress > 1.0f) progress = 1.0f; - result.r = static_cast(static_cast(start.r + (end.r - start.r) * progress)); - result.g = static_cast(static_cast(start.g + (end.g - start.g) * progress)); - result.b = static_cast(static_cast(start.b + (end.b - start.b) * progress)); + result.r = static_cast(static_cast(start.r) + ((static_cast(end.r) - static_cast(start.r)) * progress)); + result.g = static_cast(static_cast(start.g) + ((static_cast(end.g) - static_cast(start.g)) * progress)); + result.b = static_cast(static_cast(start.b) + ((static_cast(end.b) - static_cast(start.b)) * progress)); return result; } +//Type helpers +bool Animation::LightTypeIsForAnimation(LightType Type) +{ + if((Type == LightType::LightType_ActionButton && (ButtonCaseEffectType == EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_ONLY || ButtonCaseEffectType == EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_AND_CASE) ) || + (Type == LightType::LightType_Case && (ButtonCaseEffectType == EButtonCaseEffectType::BUTTONCASELIGHTTYPE_CASE_ONLY || ButtonCaseEffectType == EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_AND_CASE) ) ) + return true; -void Animation::FadeTimeUp() { - AnimationOptions & anmationOptions = Storage::getInstance().getAnimationOptions(); - anmationOptions.buttonPressColorCooldownTimeInMs = anmationOptions.buttonPressColorCooldownTimeInMs + PRESS_COOLDOWN_INCREMENT; + return false; +} - if (anmationOptions.buttonPressColorCooldownTimeInMs > PRESS_COOLDOWN_MAX) { - anmationOptions.buttonPressColorCooldownTimeInMs = PRESS_COOLDOWN_MAX; +//Get correct color for light index +RGB Animation::GetNonPressedColorForLight(uint32_t LightIndex) +{ + int colIndex = 0; + Light* thisLight = &(RGBLights->AllLights[LightIndex]); + if(thisLight->Type == LightType::LightType_ActionButton) + { + //button + colIndex = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].notPressedStaticColors[thisLight->GIPOPin]; + } + else + { + //case light + colIndex = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].caseStaticColors[thisLight->CaseLightIndex]; } -} -void Animation::FadeTimeDown() { - AnimationOptions & anmationOptions = Storage::getInstance().getAnimationOptions(); - anmationOptions.buttonPressColorCooldownTimeInMs = anmationOptions.buttonPressColorCooldownTimeInMs - PRESS_COOLDOWN_INCREMENT; + return GetColorForIndex(colIndex); +} - if (anmationOptions.buttonPressColorCooldownTimeInMs > PRESS_COOLDOWN_MAX) { - anmationOptions.buttonPressColorCooldownTimeInMs = PRESS_COOLDOWN_MIN; - } +RGB Animation::GetPressedColorForLight(uint32_t LightIndex) +{ + Light* thisLight = &(RGBLights->AllLights[LightIndex]); + int colIndex = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].pressedStaticColors[thisLight->GIPOPin]; + return GetColorForIndex(colIndex); } + +RGB Animation::GetColorForIndex(uint32_t ColorIndex) +{ + //pre defined color? + if(ColorIndex < (uint32_t)colors.size()) + return colors[ColorIndex]; + + //must be custom color + ColorIndex -= colors.size(); + if(ColorIndex > customColors.size()) + { + //error, no such color + return colors[0]; + } + return customColors[ColorIndex]; +} \ No newline at end of file diff --git a/src/animationstation/animationstation.cpp b/src/animationstation/animationstation.cpp index c87cf2d868..e201e7a137 100644 --- a/src/animationstation/animationstation.cpp +++ b/src/animationstation/animationstation.cpp @@ -5,246 +5,722 @@ * SPDX-License-Identifier: BSD-3-Clause */ +#include "effects/chase.h" +#include "effects/rain.h" +#include "effects/rainbow.h" +#include "effects/staticcolor.h" +#include "effects/jigglestaticcolor.h" +#include "effects/burstcolor.h" +#include "effects/idletimeout.h" +#include "effects/jiggletwostaticcolor.h" +#include "effects/randomcolor.h" + #include "animationstation.h" -#include "animationstorage.h" #include "storagemanager.h" -AnimationStation::AnimationStation() { - brightnessMax = 100; - brightnessSteps = 5; - brightnessX = 0; - linkageModeOfBrightnessX = 0; - nextChange = nil_time; - effectCount = TOTAL_EFFECTS; - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.hasCustomTheme) { - effectCount++; // increase our effect count - } +#include "enums.pb.h" +#include "config.pb.h" + +uint8_t AnimationStation::brightnessMax = 100; +uint8_t AnimationStation::brightnessSteps = 10; +float AnimationStation::normalisedBrightness = 0; +absolute_time_t AnimationStation::nextChange = nil_time; +AnimationOptions_Unpacked AnimationStation::options = {}; +AnimationStationTestMode AnimationStation::TestMode = AnimationStationTestMode::AnimationStation_TestModeInvalid; +bool AnimationStation::bTestModeChangeRequested = false; +int AnimationStation::TestModePinOrCaseIndex = -1; +bool AnimationStation::TestModeLightIsCase = false; + +AnimationStation::AnimationStation() +{ + AnimationStation::SetBrightnessStepValue(1); + + timeLastButtonPressed = get_absolute_time(); + + //ensure valid profile set + ChangeProfile(0); +} + +void AnimationStation::SetLights(Lights InRGBLights) +{ + RGBLights = InRGBLights; } -void AnimationStation::ConfigureBrightness(uint8_t max, uint8_t steps) { +void AnimationStation::SetMaxBrightness(uint8_t max) +{ brightnessMax = max; - brightnessSteps = steps; + if(brightnessMax < brightnessSteps) + brightnessMax = brightnessSteps; } -void AnimationStation::HandleEvent(GamepadHotkey action) { - if (action == HOTKEY_LEDS_NONE || !time_reached(nextChange) ) { +void AnimationStation::HandleEvent(GamepadHotkey action) +{ + if (action == HOTKEY_LEDS_NONE) + { + AnimationStation::nextChange = nil_time; return; } - nextChange = make_timeout_time_ms(250); - bool reqSave = false; - - if (action == HOTKEY_LEDS_BRIGHTNESS_UP) { - IncreaseBrightness(); - reqSave = true; + else if(!time_reached(AnimationStation::nextChange)) + { + return; } - if (action == HOTKEY_LEDS_BRIGHTNESS_DOWN) { - DecreaseBrightness(); - reqSave = true; - } + AnimationStation::nextChange = make_timeout_time_ms(250); - if (action == HOTKEY_LEDS_ANIMATION_UP) { - ChangeAnimation(1); - reqSave = true; + //Adjust brigness + if (action == HOTKEY_LEDS_BRIGHTNESS_UP) + { + AnimationStation::IncreaseBrightnessByStep(); } - - if (action == HOTKEY_LEDS_ANIMATION_DOWN) { - ChangeAnimation(-1); - reqSave = true; + if (action == HOTKEY_LEDS_BRIGHTNESS_DOWN) + { + AnimationStation::DecreaseBrightnessByStep(); } - if (this->baseAnimation == nullptr || this->buttonAnimation == nullptr) { - return; + //Switch to new profile + if (action == HOTKEY_LEDS_PROFILE_UP) + { + ChangeProfile(1); } - - if (action == HOTKEY_LEDS_PARAMETER_UP) { - this->baseAnimation->ParameterUp(); - reqSave = true; + if (action == HOTKEY_LEDS_PROFILE_DOWN) + { + ChangeProfile(-1); } - if (action == HOTKEY_LEDS_PARAMETER_DOWN) { - this->baseAnimation->ParameterDown(); - reqSave = true; + //Adjust existing profile hotkeys + if (this->baseAnimation == nullptr || this->buttonAnimation == nullptr) + { + return; } - if (action == HOTKEY_LEDS_PRESS_PARAMETER_UP) { - this->buttonAnimation->ParameterUp(); - reqSave = true; + if (action == HOTKEY_LEDS_PARAMETER_UP) + { + this->baseAnimation->ParameterUp(); } - - if (action == HOTKEY_LEDS_PRESS_PARAMETER_DOWN) { - this->buttonAnimation->ParameterDown(); - reqSave = true; + if (action == HOTKEY_LEDS_PARAMETER_DOWN) + { + this->baseAnimation->ParameterDown(); } - - if (action == HOTKEY_LEDS_FADETIME_UP) { - this->baseAnimation->FadeTimeUp(); - reqSave = true; + if (action == HOTKEY_LEDS_PRESS_PARAMETER_UP) + { + this->buttonAnimation->PressParameterUp(); } - - if (action == HOTKEY_LEDS_FADETIME_DOWN) { - this->baseAnimation->FadeTimeDown(); - reqSave = true; + if (action == HOTKEY_LEDS_PRESS_PARAMETER_DOWN) + { + this->buttonAnimation->PressParameterDown(); } - - if (reqSave) { - EventManager::getInstance().triggerEvent(new GPStorageSaveEvent(false)); - } } -void AnimationStation::ChangeAnimation(int changeSize) { +void AnimationStation::ChangeProfile(int changeSize) +{ + timeLastButtonPressed = get_absolute_time(); + this->SetMode(this->AdjustIndex(changeSize)); } -uint16_t AnimationStation::AdjustIndex(int changeSize) { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); +uint16_t AnimationStation::AdjustIndex(int changeSize) +{ + std::vector validIndexes; - int newIndex = animationOptions.baseAnimationIndex + changeSize; + //when initalising then changesize will be 0. If current profile is -1 then we saved in the off state. So just resume in off state + if(changeSize == 0 && this->options.baseProfileIndex == -1) + return -1; - if (newIndex >= effectCount) { - return 0; + //if no profiles defined then return -1 to turn everything off + if(options.NumValidProfiles == 0) + return -1; + + //Check to see which ones are enabled. If none then return -1 to turn everything off + for(int index = 0; index < MAX_ANIMATION_PROFILES; ++index) + { + if(options.profiles[index].bEnabled) + validIndexes.push_back(index); + } + if(validIndexes.size() == 0) + return -1; + + //find index of current profile + int indexOfCurrentProfile = -1; + for(unsigned int index = 0; index < validIndexes.size(); ++index) + { + if(validIndexes[index] == (int)this->options.baseProfileIndex) + { + indexOfCurrentProfile = index; + break; + } } - if (newIndex < 0) { - return (effectCount - 1); + //if we cant find it then either we're in the off state or this is probably the first call and the first profile isnt valid. + if(indexOfCurrentProfile == -1) + { + if(changeSize >= 0) + return validIndexes[0]; + else + return validIndexes[validIndexes.size() - 1]; } - return (uint16_t)newIndex; -} + int newProfileIndex = indexOfCurrentProfile + changeSize; + + //if we're going to wrap around then move to "OFF" profile + if (newProfileIndex >= (int)validIndexes.size()) + { + return -1; + } + else if (newProfileIndex < 0) + { + return -1; + } -void AnimationStation::HandlePressed(std::vector pressed) { - this->lastPressed = pressed; - this->baseAnimation->UpdatePixels(pressed); - this->buttonAnimation->UpdatePixels(pressed); + return (uint16_t)validIndexes[newProfileIndex]; } -void AnimationStation::ClearPressed() { - if (this->buttonAnimation != nullptr) { - this->buttonAnimation->ClearPixels(); +void AnimationStation::HandlePressedPins(std::vector pressedPins) +{ + if(pressedPins.size()) + { + timeLastButtonPressed = get_absolute_time(); + this->lastPressed = pressedPins; + if(this->buttonAnimation) + this->buttonAnimation->UpdatePressed(pressedPins); } - if (this->baseAnimation != nullptr) { - this->baseAnimation->ClearPixels(); + else + { + this->lastPressed.clear(); + if(this->buttonAnimation) + this->buttonAnimation->ClearPressed(); } +} - this->lastPressed.clear(); +void AnimationStation::HandlePressedButtons(uint32_t pressedButtons) +{ } -void AnimationStation::Animate() { - if (baseAnimation == nullptr || buttonAnimation == nullptr) { - this->Clear(); +void AnimationStation::UpdateTestMode() +{ + if(!bTestModeChangeRequested) return; + bTestModeChangeRequested = false; + + switch(TestMode) + { + case AnimationStationTestMode::AnimationStation_TestModeOff: + { + SetMode(-1); + } break; + case AnimationStationTestMode::AnimationStation_TestModeButtons: + case AnimationStationTestMode::AnimationStation_TestModeLayout: + case AnimationStationTestMode::AnimationStation_TestModeProfilePreview: + { + SetMode(MAX_ANIMATION_PROFILES_INCLUDING_TEST - 1); + } break; + + default: + break; } +} - // Only copy our frame to linkage frame if the animation effect updated our frame[] - if ( baseAnimation->Animate(this->frame) == true ) { - // Copy frame to linkage frame before button press - for(int i = 0; i < 100; i++){ - linkageFrame[i] = this->frame[i]; - } +void AnimationStation::Animate() +{ + //Test mode checks + UpdateTestMode(); + + //timeout checks + UpdateTimeout(); + + //Check for options changing and need saving + CheckForOptionsUpdate(); + + //If no profiles running + if (baseAnimation == nullptr || buttonAnimation == nullptr) + { + this->Clear(); + return; } + baseAnimation->Animate(this->frame); + if(caseAnimation != nullptr) + caseAnimation->Animate(this->frame); buttonAnimation->Animate(this->frame); } -void AnimationStation::Clear() { +void AnimationStation::Clear() +{ + //sets all lights to black (off) memset(frame, 0, sizeof(frame)); } -float AnimationStation::GetBrightnessX() { - return brightnessX; +void AnimationStation::UpdateTimeout() +{ + if(TestMode == AnimationStationTestMode::AnimationStation_TestModeInvalid && options.autoDisableTime > 0) + { + if(bIsInIdleTimeout == false) + { + if((absolute_time_diff_us(timeLastButtonPressed, get_absolute_time()) / 1000) > options.autoDisableTime) + { + //turn off all lights (but leave pressed effects running so the first press on restart isnt lost) + bIsInIdleTimeout = true; + delete this->baseAnimation; + this->baseAnimation = new IdleTimeout(RGBLights, EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_AND_CASE); + } + } + else if((absolute_time_diff_us(timeLastButtonPressed, get_absolute_time()) / 1000) < options.autoDisableTime) + { + bIsInIdleTimeout = false; + delete this->baseAnimation; + bool bCaseLightsUsingButtonNonPressedAnim = this->options.profiles[this->options.baseProfileIndex].baseNonPressedEffect == this->options.profiles[this->options.baseProfileIndex].baseCaseEffect; + + //set new profile nonpressed animation + this->baseAnimation = GetNonPressedEffectForEffectType(this->options.profiles[this->options.baseProfileIndex].baseNonPressedEffect, bCaseLightsUsingButtonNonPressedAnim ? EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_AND_CASE : EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_ONLY); + //Set case animation if required + if(!bCaseLightsUsingButtonNonPressedAnim) + { + this->caseAnimation = GetNonPressedEffectForEffectType(this->options.profiles[this->options.baseProfileIndex].baseCaseEffect, EButtonCaseEffectType::BUTTONCASELIGHTTYPE_CASE_ONLY); + } + } + } } -float AnimationStation::GetLinkageModeOfBrightnessX() { - return linkageModeOfBrightnessX; +int8_t AnimationStation::GetMode() +{ + return this->options.baseProfileIndex; } -uint8_t AnimationStation::GetBrightness() { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - return animationOptions.brightness; +Animation* AnimationStation::GetNonPressedEffectForEffectType(AnimationNonPressedEffects EffectType, EButtonCaseEffectType InButtonCaseEffectType) +{ + Animation* newEffect = nullptr; + + switch (EffectType) + { + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_RAINBOW_SYNCED: + newEffect = new RainbowSynced(RGBLights, InButtonCaseEffectType); + break; + + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_RAINBOW_ROTATE: + newEffect = new RainbowRotate(RGBLights, InButtonCaseEffectType); + break; + + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_CHASE_SEQUENTIAL: + newEffect = new Chase(RGBLights, InButtonCaseEffectType, ChaseTypes::CHASETYPES_SEQUENTIAL); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_CHASE_LEFT_TO_RIGHT: + if(TestMode == AnimationStationTestMode::AnimationStation_TestModeLayout) + newEffect = new Chase(RGBLights, InButtonCaseEffectType, ChaseTypes::CHASETYPES_TESTLAYOUT); + else + newEffect = new Chase(RGBLights, InButtonCaseEffectType, ChaseTypes::CHASETYPES_LEFT_TO_RIGHT); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_CHASE_RIGHT_TO_LEFT: + newEffect = new Chase(RGBLights, InButtonCaseEffectType, ChaseTypes::CHASETYPES_RIGHT_TO_LEFT); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_CHASE_TOP_TO_BOTTOM: + newEffect = new Chase(RGBLights, InButtonCaseEffectType, ChaseTypes::CHASETYPES_TOP_TO_BOTTOM); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_CHASE_BOTTOM_TO_TOP: + newEffect = new Chase(RGBLights, InButtonCaseEffectType, ChaseTypes::CHASETYPES_BOTTOM_TO_TOP); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_CHASE_SEQUENTIAL_PINGPONG: + newEffect = new Chase(RGBLights, InButtonCaseEffectType, ChaseTypes::CHASETYPES_SEQUENTIAL_PINGPONG); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_CHASE_HORIZONTAL_PINGPONG: + newEffect = new Chase(RGBLights, InButtonCaseEffectType, ChaseTypes::CHASETYPES_HORIZONTAL_PINGPONG); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_CHASE_VERTICAL_PINGPONG: + newEffect = new Chase(RGBLights, InButtonCaseEffectType, ChaseTypes::CHASETYPES_VERTICAL_PINGPONG); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_CHASE_RANDOM: + newEffect = new Chase(RGBLights, InButtonCaseEffectType, ChaseTypes::CHASETYPES_RANDOM); + break; + + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_STATIC_COLOR: + newEffect = new StaticColor(RGBLights, InButtonCaseEffectType); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_JIGGLESTATIC: + newEffect = new JiggleStaticColor(RGBLights, InButtonCaseEffectType); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_JIGGLETWOSTATICS: + newEffect = new JiggleTwoStaticColor(RGBLights, InButtonCaseEffectType); + break; + + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_RAIN_LOW: + newEffect = new Rain(RGBLights, InButtonCaseEffectType, ERainFrequency::RAIN_LOW); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_RAIN_MEDIUM: + newEffect = new Rain(RGBLights, InButtonCaseEffectType, ERainFrequency::RAIN_MEDIUM); + break; + case AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_RAIN_HIGH: + newEffect = new Rain(RGBLights, InButtonCaseEffectType, ERainFrequency::RAIN_HIGH); + break; + + default: + break; + } + + return newEffect; } -void AnimationStation::SetMode(uint8_t mode) { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - animationOptions.baseAnimationIndex = mode; - AnimationEffects newEffect = - static_cast(animationOptions.baseAnimationIndex); +void AnimationStation::SetMode(int8_t mode) +{ + this->options.baseProfileIndex = mode; - if (this->baseAnimation != nullptr) { + //remove old animations + if (this->baseAnimation != nullptr) + { delete this->baseAnimation; + this->baseAnimation = nullptr; + } + if (this->caseAnimation != nullptr) + { + delete this->caseAnimation; + this->caseAnimation = nullptr; } - if (this->buttonAnimation != nullptr) { + if (this->buttonAnimation != nullptr) + { delete this->buttonAnimation; + this->buttonAnimation = nullptr; } + //turn off all lights this->Clear(); - switch (newEffect) { - case AnimationEffects::EFFECT_RAINBOW: - this->baseAnimation = new Rainbow(matrix); - this->buttonAnimation = new StaticColor(matrix, lastPressed); + //no profiles + if(mode == -1) + return; + + bool bCaseLightsUsingButtonNonPressedAnim = this->options.profiles[this->options.baseProfileIndex].baseNonPressedEffect == this->options.profiles[this->options.baseProfileIndex].baseCaseEffect; + + //set new profile nonpressed animation + this->baseAnimation = GetNonPressedEffectForEffectType(this->options.profiles[this->options.baseProfileIndex].baseNonPressedEffect, bCaseLightsUsingButtonNonPressedAnim ? EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_AND_CASE : EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_ONLY); + + //Set case animation if required + if(!bCaseLightsUsingButtonNonPressedAnim) + { + this->caseAnimation = GetNonPressedEffectForEffectType(this->options.profiles[this->options.baseProfileIndex].baseCaseEffect, EButtonCaseEffectType::BUTTONCASELIGHTTYPE_CASE_ONLY); + } + + //set new profile pressed animation + //for effects that can alter multiple lights, tell them if they should also effect case lights + EButtonCaseEffectType buttonCaseEffectType = this->options.profiles[this->options.baseProfileIndex].bUseCaseLightsInPressedAnimations ? EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_AND_CASE : EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_ONLY; + switch (this->options.profiles[this->options.baseProfileIndex].basePressedEffect) + { + case AnimationPressedEffects::AnimationPressedEffects_PRESSEDEFFECT_RANDOM: + this->buttonAnimation = new RandomColor(RGBLights, lastPressed); break; - case AnimationEffects::EFFECT_CHASE: - this->baseAnimation = new Chase(matrix); - this->buttonAnimation = new StaticColor(matrix, lastPressed); + + case AnimationPressedEffects::AnimationPressedEffects_PRESSEDEFFECT_STATIC_COLOR: + this->buttonAnimation = new StaticColor(RGBLights, lastPressed); + break; + case AnimationPressedEffects::AnimationPressedEffects_PRESSEDEFFECT_JIGGLESTATIC: + this->buttonAnimation = new JiggleStaticColor(RGBLights, lastPressed); + break; + case AnimationPressedEffects::AnimationPressedEffects_PRESSEDEFFECT_JIGGLETWOSTATICS: + this->buttonAnimation = new JiggleTwoStaticColor(RGBLights, lastPressed); + break; + + case AnimationPressedEffects::AnimationPressedEffects_PRESSEDEFFECT_BURST: + this->buttonAnimation = new BurstColor(RGBLights, false, false, lastPressed, buttonCaseEffectType); + break; + case AnimationPressedEffects::AnimationPressedEffects_PRESSEDEFFECT_BURST_RANDOM: + this->buttonAnimation = new BurstColor(RGBLights, true, false, lastPressed, buttonCaseEffectType); break; - case AnimationEffects::EFFECT_STATIC_THEME: - this->baseAnimation = new StaticTheme(matrix); - this->buttonAnimation = new StaticColor(matrix, lastPressed); + case AnimationPressedEffects::AnimationPressedEffects_PRESSEDEFFECT_BURST_SMALL: + this->buttonAnimation = new BurstColor(RGBLights, false, true, lastPressed, buttonCaseEffectType); break; - case AnimationEffects::EFFECT_CUSTOM_THEME: - this->baseAnimation = new CustomTheme(matrix); - this->buttonAnimation = new CustomThemePressed(matrix, lastPressed); + case AnimationPressedEffects::AnimationPressedEffects_PRESSEDEFFECT_BURST_SMALL_RANDOM: + this->buttonAnimation = new BurstColor(RGBLights, true, true, lastPressed, buttonCaseEffectType); break; + default: - this->baseAnimation = new StaticColor(matrix); - this->buttonAnimation = new StaticColor(matrix, lastPressed); break; } } -void AnimationStation::SetMatrix(PixelMatrix matrix) { - this->matrix = matrix; +/////////////////////////////////// +// Brightness functions +/////////////////////////////////// + +void AnimationStation::ApplyBrightness(uint32_t *frameValue) +{ + for (int i = 0; i < FRAME_MAX; i++) + frameValue[i] = this->frame[i].value(Animation::format, normalisedBrightness); +} + +void AnimationStation::SetBrightnessStepValue(uint8_t brightness) +{ + AnimationStation::options.brightness = std::clamp(options.brightness, 0, brightnessSteps); + + AnimationStation::normalisedBrightness = (AnimationStation::options.brightness * getBrightnessStepSize()) / 255.0F; + AnimationStation::normalisedBrightness = std::clamp(AnimationStation::normalisedBrightness, 0.0f, 1.0f); +} + +void AnimationStation::DecreaseBrightnessByStep() +{ + AnimationStation::options.brightness = std::clamp(((int32_t)options.brightness)-1, 0, brightnessSteps); +} + +void AnimationStation::IncreaseBrightnessByStep() +{ + AnimationStation::options.brightness = std::clamp(options.brightness+1, 0, brightnessSteps); } -void AnimationStation::ApplyBrightness(uint32_t *frameValue) { - for (int i = 0; i < 100; i++) - frameValue[i] = this->frame[i].value(Animation::format, brightnessX); +void AnimationStation::DimBrightnessTo0() +{ + AnimationStation::normalisedBrightness = 0; } -void AnimationStation::SetBrightness(uint8_t brightness) { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - animationOptions.brightness = - (brightness > brightnessSteps) ? brightnessSteps : brightness; - brightnessX = - (animationOptions.brightness * getBrightnessStepSize()) / 255.0F; - linkageModeOfBrightnessX = - (animationOptions.brightness * getLinkageModeOfBrightnessStepSize()) / 255.0F; - if (linkageModeOfBrightnessX > 1) - linkageModeOfBrightnessX = 1; - else if (linkageModeOfBrightnessX < 0) - linkageModeOfBrightnessX = 0; - if (brightnessX > 1.0f) - brightnessX = 1.0f; - else if (brightnessX < 0.0f) - brightnessX = 0.0f; +float AnimationStation::GetNormalisedBrightness() +{ + return AnimationStation::normalisedBrightness; } -void AnimationStation::DecreaseBrightness() { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.brightness > 0) - SetBrightness(animationOptions.brightness-1); +uint8_t AnimationStation::GetBrightnessStepValue() +{ + return AnimationStation::options.brightness; } -void AnimationStation::IncreaseBrightness() { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.brightness < getBrightnessStepSize()) - SetBrightness(animationOptions.brightness+1); - else if (animationOptions.brightness > getBrightnessStepSize()) - SetBrightness(brightnessSteps); +void AnimationStation::DecompressProfile(int ProfileIndex, const AnimationProfile* ProfileToDecompress) +{ + options.profiles[ProfileIndex].bEnabled = ProfileToDecompress->bEnabled; + options.profiles[ProfileIndex].baseNonPressedEffect = (AnimationNonPressedEffects)((int)ProfileToDecompress->baseNonPressedEffect); + options.profiles[ProfileIndex].basePressedEffect = (AnimationPressedEffects)((int)ProfileToDecompress->basePressedEffect); + options.profiles[ProfileIndex].baseCaseEffect = (AnimationNonPressedEffects)((int)ProfileToDecompress->baseCaseEffect); + options.profiles[ProfileIndex].baseCycleTime = ProfileToDecompress->baseCycleTime; + options.profiles[ProfileIndex].basePressedCycleTime = ProfileToDecompress->basePressedCycleTime; + for(unsigned int packedPinIndex = 0; packedPinIndex < (NUM_BANK0_GPIOS/4)+1; ++packedPinIndex) + { + int pinIndex = packedPinIndex * 4; + if(packedPinIndex < ProfileToDecompress->notPressedStaticColors_count) + { + options.profiles[ProfileIndex].notPressedStaticColors[pinIndex + 0] = ProfileToDecompress->notPressedStaticColors[packedPinIndex] & 0xFF; + options.profiles[ProfileIndex].notPressedStaticColors[pinIndex + 1] = (ProfileToDecompress->notPressedStaticColors[packedPinIndex] >> 8) & 0xFF; + options.profiles[ProfileIndex].notPressedStaticColors[pinIndex + 2] = (ProfileToDecompress->notPressedStaticColors[packedPinIndex] >> 16) & 0xFF; + options.profiles[ProfileIndex].notPressedStaticColors[pinIndex + 3] = (ProfileToDecompress->notPressedStaticColors[packedPinIndex] >> 24) & 0xFF; + } + else + { + //Set all black + options.profiles[ProfileIndex].notPressedStaticColors[pinIndex + 0] = 0; + options.profiles[ProfileIndex].notPressedStaticColors[pinIndex + 1] = 0; + options.profiles[ProfileIndex].notPressedStaticColors[pinIndex + 2] = 0; + options.profiles[ProfileIndex].notPressedStaticColors[pinIndex + 3] = 0; + } + + if(packedPinIndex < ProfileToDecompress->pressedStaticColors_count) + { + options.profiles[ProfileIndex].pressedStaticColors[pinIndex + 0] = ProfileToDecompress->pressedStaticColors[packedPinIndex] & 0xFF; + options.profiles[ProfileIndex].pressedStaticColors[pinIndex + 1] = (ProfileToDecompress->pressedStaticColors[packedPinIndex] >> 8) & 0xFF; + options.profiles[ProfileIndex].pressedStaticColors[pinIndex + 2] = (ProfileToDecompress->pressedStaticColors[packedPinIndex] >> 16) & 0xFF; + options.profiles[ProfileIndex].pressedStaticColors[pinIndex + 3] = (ProfileToDecompress->pressedStaticColors[packedPinIndex] >> 24) & 0xFF; + } + else + { + //Set all black + options.profiles[ProfileIndex].pressedStaticColors[pinIndex + 0] = 0; + options.profiles[ProfileIndex].pressedStaticColors[pinIndex + 1] = 0; + options.profiles[ProfileIndex].pressedStaticColors[pinIndex + 2] = 0; + options.profiles[ProfileIndex].pressedStaticColors[pinIndex + 3] = 0; + } + } + for(unsigned int packedCaseIndex = 0; packedCaseIndex < (MAX_CASE_LIGHTS / 4); ++packedCaseIndex) + { + int caseIndex = packedCaseIndex * 4; + if(packedCaseIndex < ProfileToDecompress->caseStaticColors_count) + { + options.profiles[ProfileIndex].caseStaticColors[caseIndex + 0] = ProfileToDecompress->caseStaticColors[packedCaseIndex] & 0xFF; + options.profiles[ProfileIndex].caseStaticColors[caseIndex + 1] = (ProfileToDecompress->caseStaticColors[packedCaseIndex] >> 8) & 0xFF; + options.profiles[ProfileIndex].caseStaticColors[caseIndex + 2] = (ProfileToDecompress->caseStaticColors[packedCaseIndex] >> 16) & 0xFF; + options.profiles[ProfileIndex].caseStaticColors[caseIndex + 3] = (ProfileToDecompress->caseStaticColors[packedCaseIndex] >> 24) & 0xFF; + } + else + { + //Set all black + options.profiles[ProfileIndex].caseStaticColors[caseIndex + 0] = 0; + options.profiles[ProfileIndex].caseStaticColors[caseIndex + 1] = 0; + options.profiles[ProfileIndex].caseStaticColors[caseIndex + 2] = 0; + options.profiles[ProfileIndex].caseStaticColors[caseIndex + 3] = 0; + } + } + options.profiles[ProfileIndex].buttonPressHoldTimeInMs = ProfileToDecompress->buttonPressHoldTimeInMs; + options.profiles[ProfileIndex].buttonPressFadeOutTimeInMs = ProfileToDecompress->buttonPressFadeOutTimeInMs; + options.profiles[ProfileIndex].nonPressedSpecialColor = ProfileToDecompress->nonPressedSpecialColor; + options.profiles[ProfileIndex].pressedSpecialColor = ProfileToDecompress->pressedSpecialColor; + options.profiles[ProfileIndex].bUseCaseLightsInPressedAnimations = ProfileToDecompress->bUseCaseLightsInPressedAnimations; } -void AnimationStation::DimBrightnessTo0() { - brightnessX = 0; +void AnimationStation::DecompressSettings() +{ + const AnimationOptions& optionsProto = Storage::getInstance().getAnimationOptions(); + + options.checksum = 0; + options.NumValidProfiles = optionsProto.profiles_count; + for(int index = 0; index < options.NumValidProfiles && index < 4; ++index) //MAX_ANIMATION_PROFILES from AnimationStation.hpp + { + DecompressProfile(index, &(optionsProto.profiles[index])); + } + + options.brightness = std::min(optionsProto.brightness, brightnessSteps); + options.baseProfileIndex = optionsProto.baseProfileIndex; + options.autoDisableTime = optionsProto.autoDisableTime; + + customColors.clear(); + for(unsigned int customColIndex = 0; customColIndex < MAX_CUSTOM_COLORS; ++customColIndex) + { + customColors.push_back(optionsProto.customColors[customColIndex]); + } +} + +void AnimationStation::CheckForOptionsUpdate() +{ + //No saving in test/webconfig mode + if(TestMode != AnimationStationTestMode::AnimationStation_TestModeInvalid) + return; + + bool bChangeDetected = false; + AnimationOptions& optionsProto = Storage::getInstance().getAnimationOptions(); + + //Any changes? + for(int index = 0; index < MAX_ANIMATION_PROFILES && !bChangeDetected; ++index) + { + if(optionsProto.profiles[index].baseCycleTime != options.profiles[index].baseCycleTime) + bChangeDetected = true; + else if(optionsProto.profiles[index].basePressedCycleTime != options.profiles[index].basePressedCycleTime) + bChangeDetected = true; + } + if(optionsProto.brightness != options.brightness) + bChangeDetected = true; + else if(optionsProto.baseProfileIndex != options.baseProfileIndex) + bChangeDetected = true; + + //only change settings if they've been static for a little while + if(bChangeDetected) + { + if(!bAnimConfigSaveNeeded) + { + bAnimConfigSaveNeeded = true; + timeAnimationSaveSet = get_absolute_time(); + } + + if(bAnimConfigSaveNeeded && (absolute_time_diff_us(timeAnimationSaveSet, get_absolute_time()) / 1000) > 1000) // 1 second delay on saves + { + bAnimConfigSaveNeeded = false; + for(int index = 0; index < MAX_ANIMATION_PROFILES; ++index) + { + optionsProto.profiles[index].baseCycleTime = options.profiles[index].baseCycleTime; + optionsProto.profiles[index].basePressedCycleTime = options.profiles[index].basePressedCycleTime; + } + optionsProto.brightness = options.brightness; + optionsProto.baseProfileIndex = options.baseProfileIndex; + + EventManager::getInstance().triggerEvent(new GPStorageSaveEvent(false)); + } + } +} + +//Testmode functions +void AnimationStation::SetTestMode(AnimationStationTestMode TestType, const AnimationProfile* TestProfile) +{ + bTestModeChangeRequested = true; + + TestMode = TestType; + + //Decompress profile into the test entry + int testProfileIndex = MAX_ANIMATION_PROFILES_INCLUDING_TEST - 1; + if(TestMode == AnimationStationTestMode::AnimationStation_TestModeProfilePreview) + DecompressProfile(testProfileIndex, TestProfile); + else if(TestMode == AnimationStationTestMode::AnimationStation_TestModeLayout) + { + //Set up test profile that is Chase Random + options.profiles[testProfileIndex].baseCaseEffect = AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_CHASE_LEFT_TO_RIGHT; + options.profiles[testProfileIndex].baseNonPressedEffect = AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_CHASE_LEFT_TO_RIGHT; + options.profiles[testProfileIndex].basePressedEffect = AnimationPressedEffects::AnimationPressedEffects_PRESSEDEFFECT_STATIC_COLOR; + + for(unsigned int packedPinIndex = 0; packedPinIndex < (NUM_BANK0_GPIOS/4)+1; ++packedPinIndex) + { + int pinIndex = packedPinIndex * 4; + options.profiles[testProfileIndex].notPressedStaticColors[pinIndex + 0] = 0; + options.profiles[testProfileIndex].notPressedStaticColors[pinIndex + 1] = 0; + options.profiles[testProfileIndex].notPressedStaticColors[pinIndex + 2] = 0; + options.profiles[testProfileIndex].notPressedStaticColors[pinIndex + 3] = 0; + + options.profiles[testProfileIndex].pressedStaticColors[pinIndex + 0] = 0; + options.profiles[testProfileIndex].pressedStaticColors[pinIndex + 1] = 0; + options.profiles[testProfileIndex].pressedStaticColors[pinIndex + 2] = 0; + options.profiles[testProfileIndex].pressedStaticColors[pinIndex + 3] = 0; + } + + for(unsigned int packedCaseIndex = 0; packedCaseIndex < (MAX_CASE_LIGHTS / 4); ++packedCaseIndex) + { + int caseIndex = packedCaseIndex * 4; + options.profiles[testProfileIndex].caseStaticColors[caseIndex + 0] = 0; + options.profiles[testProfileIndex].caseStaticColors[caseIndex + 1] = 0; + options.profiles[testProfileIndex].caseStaticColors[caseIndex + 2] = 0; + options.profiles[testProfileIndex].caseStaticColors[caseIndex + 3] = 0; + } + + options.profiles[testProfileIndex].nonPressedSpecialColor = 0xFFFFFF; //White + options.profiles[testProfileIndex].baseCycleTime = 50; + } + else if(TestMode == AnimationStationTestMode::AnimationStation_TestModeButtons) + { + //Set up test profile that is all black + options.profiles[testProfileIndex].baseCaseEffect = AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_STATIC_COLOR; + options.profiles[testProfileIndex].baseNonPressedEffect = AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_STATIC_COLOR; + options.profiles[testProfileIndex].basePressedEffect = AnimationPressedEffects::AnimationPressedEffects_PRESSEDEFFECT_STATIC_COLOR; + + for(unsigned int packedPinIndex = 0; packedPinIndex < (NUM_BANK0_GPIOS/4)+1; ++packedPinIndex) + { + int pinIndex = packedPinIndex * 4; + options.profiles[testProfileIndex].notPressedStaticColors[pinIndex + 0] = 0; + options.profiles[testProfileIndex].notPressedStaticColors[pinIndex + 1] = 0; + options.profiles[testProfileIndex].notPressedStaticColors[pinIndex + 2] = 0; + options.profiles[testProfileIndex].notPressedStaticColors[pinIndex + 3] = 0; + + options.profiles[testProfileIndex].pressedStaticColors[pinIndex + 0] = 0; + options.profiles[testProfileIndex].pressedStaticColors[pinIndex + 1] = 0; + options.profiles[testProfileIndex].pressedStaticColors[pinIndex + 2] = 0; + options.profiles[testProfileIndex].pressedStaticColors[pinIndex + 3] = 0; + } + + for(unsigned int packedCaseIndex = 0; packedCaseIndex < (MAX_CASE_LIGHTS / 4); ++packedCaseIndex) + { + int caseIndex = packedCaseIndex * 4; + options.profiles[testProfileIndex].caseStaticColors[caseIndex + 0] = 0; + options.profiles[testProfileIndex].caseStaticColors[caseIndex + 1] = 0; + options.profiles[testProfileIndex].caseStaticColors[caseIndex + 2] = 0; + options.profiles[testProfileIndex].caseStaticColors[caseIndex + 3] = 0; + } + } +} + +void AnimationStation::SetTestPinState(int PinOrCaseIndex, bool IsCaseLight) +{ + int testProfileIndex = MAX_ANIMATION_PROFILES_INCLUDING_TEST - 1; + + //reset old test light + if(TestModePinOrCaseIndex != -1) + { + if(TestModeLightIsCase) + { + options.profiles[testProfileIndex].caseStaticColors[TestModePinOrCaseIndex] = 0x00; //Black/off + } + else + { + options.profiles[testProfileIndex].notPressedStaticColors[TestModePinOrCaseIndex] = 0x00; //Black/off + } + } + + //Store new test light + TestModePinOrCaseIndex = PinOrCaseIndex; + TestModeLightIsCase = IsCaseLight; + + if(TestModePinOrCaseIndex != -1) + { + if(IsCaseLight) + { + options.profiles[testProfileIndex].caseStaticColors[PinOrCaseIndex] = 0x01; //White + } + else + { + options.profiles[testProfileIndex].notPressedStaticColors[PinOrCaseIndex] = 0x01; //White + } + } } diff --git a/src/animationstation/effects/burstcolor.cpp b/src/animationstation/effects/burstcolor.cpp new file mode 100644 index 0000000000..b9a69e254d --- /dev/null +++ b/src/animationstation/effects/burstcolor.cpp @@ -0,0 +1,198 @@ +#include "burstcolor.h" + +#define BURST_CYCLE_INCREMENT 10 +#define BURST_CYCLE_MAX 100 +#define BURST_CYCLE_MIN 10 + +BurstColor::BurstColor(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType) : Animation(InRGBLights, InButtonCaseEffectType) +{ +} + +BurstColor::BurstColor(Lights& InRGBLights, bool bInRandomColor, bool bInSmallBurst, std::vector &InPressedPins, EButtonCaseEffectType InButtonCaseEffectType) : Animation(InRGBLights, InButtonCaseEffectType) +{ + isButtonAnimation = true; + pressedPins = InPressedPins; + + bRandomColor = bInRandomColor; + + bSmallBurst = bInSmallBurst; + + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + if(RGBLights->AllLights[lightIndex].Type != LightType::LightType_ActionButton && RGBLights->AllLights[lightIndex].Type != LightType::LightType_Case) + continue; + + if(RGBLights->AllLights[lightIndex].Position.XPosition > MaxXCoord) + MaxXCoord = RGBLights->AllLights[lightIndex].Position.XPosition; + if(RGBLights->AllLights[lightIndex].Position.YPosition > MaxYCoord) + MaxYCoord = RGBLights->AllLights[lightIndex].Position.YPosition; + + if(lightIndex == 0 || RGBLights->AllLights[lightIndex].Position.XPosition < MinXCoord) + MinXCoord = RGBLights->AllLights[lightIndex].Position.XPosition; + if(lightIndex == 0 || RGBLights->AllLights[lightIndex].Position.YPosition < MinYCoord) + MinYCoord = RGBLights->AllLights[lightIndex].Position.YPosition; + } + + int16_t cycleTime = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].basePressedCycleTime; + if (cycleTime == 0) + { + cycleTime = ((BURST_CYCLE_MAX - BURST_CYCLE_MIN) / 2) + BURST_CYCLE_MIN; + } + if (cycleTime < BURST_CYCLE_MIN) + { + cycleTime = BURST_CYCLE_MIN; + } + else if (cycleTime > BURST_CYCLE_MAX) + { + cycleTime = BURST_CYCLE_MAX; + } + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].basePressedCycleTime = cycleTime; +} + +void BurstColor::NewPressForPin(int lightIndex) +{ + //find empty burst data and create new burst + for(int index = 0; index < MAX_BURSTS; ++index) + { + if(RunningBursts[index].RunningTime < 0) + { + RunningBursts[index].RunningTime = 0.0f; + int pickedCol = (rand() % (colors.size()-1)) + 1; + RunningBursts[index].StartColor = bRandomColor ? colors[pickedCol] : GetPressedColorForLight(lightIndex); + RunningBursts[index].XPos = RGBLights->AllLights[lightIndex].Position.XPosition; + RunningBursts[index].YPos = RGBLights->AllLights[lightIndex].Position.YPosition; + return; + } + } +} + +void BurstColor::Animate(RGB (&frame)[FRAME_MAX]) +{ + UpdateTime(); + UpdatePresses(); + + std::vector OneLineGrid; + OneLineGrid.assign(MaxYCoord+1, FGridEntry()); + std::vector> FullGrid; + FullGrid.assign(MaxXCoord+1, OneLineGrid); + + //get each grid positions color and strength + for(int burstIndex = 0; burstIndex < MAX_BURSTS; ++burstIndex) + { + if(RunningBursts[burstIndex].RunningTime < 0.0f) + continue; + + RunningBursts[burstIndex].RunningTime += (((float)AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].basePressedCycleTime) / 1000.0f); + float travelledDist = RunningBursts[burstIndex].RunningTime * BURST_DISTANCE_PER_SEC; + + //is this the last frame? + float largestCoord = MAX(1 + (MaxXCoord - MinXCoord), 1 + (MaxYCoord - MinYCoord)); + float distanceToTravel = bSmallBurst ? (largestCoord / 13.0f) * (float)BURST_DISTANCE : largestCoord; //Burst_distance was designed with my setup for my T16 in mind which was 0-12 xcoord + if(travelledDist > (float)distanceToTravel + 4.0f) + RunningBursts[burstIndex].RunningTime = -1.0f; + + int xStart = RunningBursts[burstIndex].XPos - distanceToTravel; + if(xStart < MinXCoord) + xStart = MinXCoord; + int yStart = RunningBursts[burstIndex].YPos - distanceToTravel; + if(yStart < MinYCoord) + yStart = MinYCoord; + int xEnd = RunningBursts[burstIndex].XPos + distanceToTravel; + if(xEnd > MaxXCoord) + xEnd = MaxXCoord; + int yEnd = RunningBursts[burstIndex].YPos + distanceToTravel; + if(yEnd > MaxYCoord) + yEnd = MaxYCoord; + + for(int xCoord = xStart; xCoord <= xEnd; ++xCoord) + { + for(int yCoord = yStart; yCoord <= yEnd; ++yCoord) + { + //This is a square expansion. Furthest coord defines the distance + int distanceFromCenter = abs(yCoord - RunningBursts[burstIndex].YPos); + if(abs(xCoord - RunningBursts[burstIndex].XPos) > distanceFromCenter) + distanceFromCenter = abs(xCoord - RunningBursts[burstIndex].XPos); + + float Strength = 0.0f; + //2 wide here, up then down + if((int)travelledDist < distanceFromCenter || (int)travelledDist > distanceFromCenter + 3) + continue; + + if((int)travelledDist < distanceFromCenter+1) + Strength = travelledDist - (float)((int)travelledDist); + else if((int)travelledDist < distanceFromCenter+3) + Strength = 1.0f; + else + Strength = 1.0f - (travelledDist - (float)((int)travelledDist)); + + //update grid pos + //strength is highest applied to this point + if(Strength > FullGrid[xCoord][yCoord].Strength) + FullGrid[xCoord][yCoord].Strength = Strength; + int redToApply = (float)(RunningBursts[burstIndex].StartColor.r) * Strength; + if((int)FullGrid[xCoord][yCoord].Color.r + redToApply > 0xFF) + FullGrid[xCoord][yCoord].Color.r = 0xFF; + else + FullGrid[xCoord][yCoord].Color.r += redToApply; + + int greenToApply = (float)(RunningBursts[burstIndex].StartColor.g) * Strength; + if((int)FullGrid[xCoord][yCoord].Color.g + greenToApply > 0xFF) + FullGrid[xCoord][yCoord].Color.g = 0xFF; + else + FullGrid[xCoord][yCoord].Color.g += greenToApply; + + int blueToApply = (float)(RunningBursts[burstIndex].StartColor.b) * Strength; + if((int)FullGrid[xCoord][yCoord].Color.b + blueToApply > 0xFF) + FullGrid[xCoord][yCoord].Color.b = 0xFF; + else + FullGrid[xCoord][yCoord].Color.b += blueToApply; + } + } + } + + //now apply those values to lights + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + if(LightTypeIsForAnimation(RGBLights->AllLights[lightIndex].Type) == false) + continue; + + uint8_t firstLightIndex = RGBLights->AllLights[lightIndex].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[lightIndex].LedsPerLight; + + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + frame[ledIndex] = BlendColor(FullGrid[RGBLights->AllLights[lightIndex].Position.XPosition][RGBLights->AllLights[lightIndex].Position.YPosition].Color, + frame[ledIndex], + (1.0f - FullGrid[RGBLights->AllLights[lightIndex].Position.XPosition][RGBLights->AllLights[lightIndex].Position.YPosition].Strength)); + } + } + + // Count down the timer + DecrementFadeCounters(); +} + +void BurstColor::PressParameterUp() +{ + int32_t cycleTime = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].basePressedCycleTime; + cycleTime = cycleTime + BURST_CYCLE_INCREMENT; + + if (cycleTime > BURST_CYCLE_MAX) + { + cycleTime = BURST_CYCLE_MAX; + } + + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].basePressedCycleTime = cycleTime; +} + +void BurstColor::PressParameterDown() +{ + int16_t cycleTime = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].basePressedCycleTime; + cycleTime = cycleTime - BURST_CYCLE_INCREMENT; + + if (cycleTime < BURST_CYCLE_MIN) + { + cycleTime = BURST_CYCLE_MIN; + } + + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].basePressedCycleTime = cycleTime; +} \ No newline at end of file diff --git a/src/animationstation/effects/chase.cpp b/src/animationstation/effects/chase.cpp index 7ec5f70493..77de57eeb5 100644 --- a/src/animationstation/effects/chase.cpp +++ b/src/animationstation/effects/chase.cpp @@ -1,122 +1,395 @@ #include "chase.h" -#include "storagemanager.h" +#include -#define CHASE_CYCLE_INCREMENT 10 -#define CHASE_CYCLE_MAX INT16_MAX/2 +#define CHASE_CYCLE_INCREMENT 50 +#define CHASE_CYCLE_MAX 500 #define CHASE_CYCLE_MIN 10 +#define CHASE_SECOND_LIGHT_OFFSET 0.5f -Chase::Chase(PixelMatrix &matrix) : Animation(matrix) { -} +Chase::Chase(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType, ChaseTypes InChaseType) : Animation(InRGBLights, InButtonCaseEffectType) +{ + ChaseTypeInUse = InChaseType; + + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime == 0) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = ((CHASE_CYCLE_MAX - CHASE_CYCLE_MIN) / 2) + CHASE_CYCLE_MIN; + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime > CHASE_CYCLE_MAX) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = CHASE_CYCLE_MAX; + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime < CHASE_CYCLE_MIN) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = CHASE_CYCLE_MIN; + + ChaseTimes[0] = 1.0f; + ChaseTimes[1] = 1.0f + CHASE_SECOND_LIGHT_OFFSET; + + OrderedLights.clear(); + + //Get max x and y coords + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + if(LightTypeIsForAnimation(RGBLights->AllLights[lightIndex].Type) == false) + continue; + + if(RGBLights->AllLights[lightIndex].Position.XPosition > MaxXCoord) + MaxXCoord = RGBLights->AllLights[lightIndex].Position.XPosition; + if(RGBLights->AllLights[lightIndex].Position.YPosition > MaxYCoord) + MaxYCoord = RGBLights->AllLights[lightIndex].Position.YPosition; -bool Chase::Animate(RGB (&frame)[100]) { - if (!time_reached(this->nextRunTime)) { - return false; + if(lightIndex == 0 || RGBLights->AllLights[lightIndex].Position.XPosition < MinXCoord) + MinXCoord = RGBLights->AllLights[lightIndex].Position.XPosition; + if(lightIndex == 0 || RGBLights->AllLights[lightIndex].Position.YPosition < MinYCoord) + MinYCoord = RGBLights->AllLights[lightIndex].Position.YPosition; } - UpdateTime(); - UpdatePresses(frame); - - for (auto &col : matrix->pixels) { - for (auto &pixel : col) { - if (pixel.index == NO_PIXEL.index) - continue; - - // Count down the timer - DecrementFadeCounter(pixel.index); - - if (this->IsChasePixel(pixel.index)) { - RGB color = RGB::wheel(this->WheelFrame(pixel.index)); - for (auto &pos : pixel.positions) - frame[pos] = BlendColor(hitColor[pixel.index], color, times[pixel.index]); - } else { - for (auto &pos : pixel.positions) - frame[pos] = BlendColor(hitColor[pixel.index], ColorBlack, times[pixel.index]); - } + //store off all lights in sequential order used on this animation + for(int yCoord = 0; yCoord <= MaxYCoord; ++yCoord) + { + for(int xCoord = 0; xCoord <= MaxXCoord; ++xCoord) + { + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + if(LightTypeIsForAnimation(RGBLights->AllLights[lightIndex].Type) && + RGBLights->AllLights[lightIndex].Position.XPosition == xCoord && + RGBLights->AllLights[lightIndex].Position.YPosition == yCoord) + OrderedLights.push_back(lightIndex); + } } } - currentPixel++; + //pick starting type if using one of the multi types + switch(ChaseTypeInUse) + { + case ChaseTypes::CHASETYPES_RANDOM: + { + RandomChaseType = (SingleChaseTypes)(rand() % (int)SINGLECHASETYPES_MAX); + } break; + + case ChaseTypes::CHASETYPES_TESTLAYOUT: + { + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_LEFT_TO_RIGHT; + } break; + + case ChaseTypes::CHASETYPES_SEQUENTIAL_PINGPONG: + { + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_SEQUENTIAL; + } break; - if (currentPixel > matrix->getPixelCount() - 1) { - currentPixel = 0; + case ChaseTypes::CHASETYPES_HORIZONTAL_PINGPONG: + { + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_LEFT_TO_RIGHT; + } break; + + case ChaseTypes::CHASETYPES_VERTICAL_PINGPONG: + { + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_TOP_TO_BOTTOM; + } break; + + case ChaseTypes::CHASETYPES_TOP_TO_BOTTOM: + { + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_TOP_TO_BOTTOM; + } break; + + case ChaseTypes::CHASETYPES_BOTTOM_TO_TOP: + { + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_BOTTOM_TO_TOP; + } break; + + case ChaseTypes::CHASETYPES_LEFT_TO_RIGHT: + { + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_LEFT_TO_RIGHT; + } break; + + case ChaseTypes::CHASETYPES_RIGHT_TO_LEFT: + { + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_RIGHT_TO_LEFT; + } break; + + default: + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_SEQUENTIAL; + break; } +} - if (reverse) { - currentFrame--; +void Chase::Animate(RGB (&frame)[FRAME_MAX]) +{ + UpdateTime(); - if (currentFrame < 0) { - currentFrame = 1; - reverse = false; - } - } else { - currentFrame++; + //dont do anything if there aren't enough lights + if(OrderedLights.size() < 2) + return; - if (currentFrame > 255) { - currentFrame = 254; - reverse = true; - } + //update times and move to the next light(s) if required + ChaseTimes[0] -= (((float)AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime) / 1000.0f); + ChaseTimes[1] -= (((float)AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime) / 1000.0f); + if(ChaseTimes[0] < 0.0f) + { + ChaseTimes[0] = 0.0f; } - // this really shouldn't be nessecary, but something outside the param down might be changing this - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.chaseCycleTime < CHASE_CYCLE_MIN) { - animationOptions.chaseCycleTime = CHASE_CYCLE_MIN; - } else if (animationOptions.chaseCycleTime > CHASE_CYCLE_MAX) { - animationOptions.chaseCycleTime = CHASE_CYCLE_MAX; + //reset all lights first to ensure that if pressed lights are unpressed they can blend back to correct color + for(unsigned int lightIndex = 0; lightIndex < OrderedLights.size(); ++lightIndex) + { + uint8_t firstLightIndex = RGBLights->AllLights[OrderedLights[lightIndex]].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[OrderedLights[lightIndex]].LedsPerLight; + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + frame[ledIndex] = GetNonPressedColorForLight(OrderedLights[lightIndex]); + } } - this->nextRunTime = make_timeout_time_ms(animationOptions.chaseCycleTime); + //caclulate fade times here as these are used for all cases below + float fadeTimeOne = 0.0f; + if(ChaseTimes[0] < 1.0f && ChaseTimes[0] >= 0.8f) + fadeTimeOne = 1.0f - ((ChaseTimes[0] - 0.8f) / 0.2f); + else if(ChaseTimes[0] >= 0.2f && ChaseTimes[0] < 0.8f) + fadeTimeOne = 1.0f; + else if(ChaseTimes[0] < 0.2f && ChaseTimes[0] >= 0.0f) + fadeTimeOne = (ChaseTimes[0] / 0.2f); + float fadeTimeTwo = 0.0f; + if(ChaseTimes[1] < 1.0f && ChaseTimes[1] >= 0.8f) + fadeTimeTwo = 1.0f - ((ChaseTimes[1] - 0.8f) / 0.2f); + else if(ChaseTimes[1] >= 0.2f && ChaseTimes[1] < 0.8f) + fadeTimeTwo = 1.0f; + else if(ChaseTimes[1] < 0.2f && ChaseTimes[1] >= 0.0f) + fadeTimeTwo = (ChaseTimes[1] / 0.2f); - return true; -} + //get this and next light + int currentLightReverseAdjusted = CurrentLight; + int nextLightReverseAdjusted = currentLightReverseAdjusted + 1; + + //now light the correct lights + switch(RandomChaseType) + { + case SingleChaseTypes::SINGLECHASETYPES_SEQUENTIAL: + { + //if we're on the way back then invert the index + if(Reversed) + { + currentLightReverseAdjusted = (OrderedLights.size()-1) - CurrentLight; + nextLightReverseAdjusted = currentLightReverseAdjusted - 1; + } -bool Chase::IsChasePixel(int i) { - if (i == this->currentPixel || i == (this->currentPixel - 1) || - i == (this->currentPixel - 2)) { - return true; + uint8_t firstLightIndex = RGBLights->AllLights[OrderedLights[currentLightReverseAdjusted]].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[OrderedLights[currentLightReverseAdjusted]].LedsPerLight; + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + frame[ledIndex] = BlendColor(GetNonPressedColorForLight(OrderedLights[currentLightReverseAdjusted]), + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].nonPressedSpecialColor, + fadeTimeOne); + } + + if((unsigned int)(nextLightReverseAdjusted) < OrderedLights.size()) + { + uint8_t firstLightIndex = RGBLights->AllLights[OrderedLights[nextLightReverseAdjusted]].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[OrderedLights[nextLightReverseAdjusted]].LedsPerLight; + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + frame[ledIndex] = BlendColor(GetNonPressedColorForLight(OrderedLights[nextLightReverseAdjusted]), + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].nonPressedSpecialColor, + fadeTimeTwo); + } + } + } break; + + case SingleChaseTypes::SINGLECHASETYPES_RIGHT_TO_LEFT: + //reverse the order and fall through + currentLightReverseAdjusted = (MaxXCoord) - (CurrentLight - MinXCoord); + nextLightReverseAdjusted = currentLightReverseAdjusted - 1; + //Fall through (no break) + + case SingleChaseTypes::SINGLECHASETYPES_LEFT_TO_RIGHT: + { + for(unsigned int lightIndex = 0; lightIndex < OrderedLights.size(); ++lightIndex) + { + if(RGBLights->AllLights[OrderedLights[lightIndex]].Position.XPosition == currentLightReverseAdjusted) + { + uint8_t firstLightIndex = RGBLights->AllLights[OrderedLights[lightIndex]].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[OrderedLights[lightIndex]].LedsPerLight; + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + frame[ledIndex] = BlendColor(GetNonPressedColorForLight(OrderedLights[lightIndex]), + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].nonPressedSpecialColor, + fadeTimeOne); + } + } + + if((CurrentLight+1) <= MaxXCoord) + { + if(RGBLights->AllLights[OrderedLights[lightIndex]].Position.XPosition == nextLightReverseAdjusted) + { + uint8_t firstLightIndex = RGBLights->AllLights[OrderedLights[lightIndex]].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[OrderedLights[lightIndex]].LedsPerLight; + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + frame[ledIndex] = BlendColor(GetNonPressedColorForLight(OrderedLights[lightIndex]), + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].nonPressedSpecialColor, + fadeTimeTwo); + } + } + } + } + } break; + + case SingleChaseTypes::SINGLECHASETYPES_BOTTOM_TO_TOP: + //reverse the order and fall through + currentLightReverseAdjusted = (MaxYCoord) - (CurrentLight - MinYCoord); + nextLightReverseAdjusted = currentLightReverseAdjusted - 1; + //Fall through (no break) + + case SingleChaseTypes::SINGLECHASETYPES_TOP_TO_BOTTOM: + { + for(unsigned int lightIndex = 0; lightIndex < OrderedLights.size(); ++lightIndex) + { + if(RGBLights->AllLights[OrderedLights[lightIndex]].Position.YPosition == currentLightReverseAdjusted) + { + uint8_t firstLightIndex = RGBLights->AllLights[OrderedLights[lightIndex]].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[OrderedLights[lightIndex]].LedsPerLight; + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + frame[ledIndex] = BlendColor(GetNonPressedColorForLight(OrderedLights[lightIndex]), + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].nonPressedSpecialColor, + fadeTimeOne); + } + } + + if((CurrentLight+1) <= MaxYCoord) + { + if(RGBLights->AllLights[OrderedLights[lightIndex]].Position.YPosition == nextLightReverseAdjusted) + { + uint8_t firstLightIndex = RGBLights->AllLights[OrderedLights[lightIndex]].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[OrderedLights[lightIndex]].LedsPerLight; + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + frame[ledIndex] = BlendColor(GetNonPressedColorForLight(OrderedLights[lightIndex]), + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].nonPressedSpecialColor, + fadeTimeTwo); + } + } + } + } + } break; + + default: + break; } - return false; + if(ChaseTimes[0] <= 0.0f) + { + CurrentLight++; + ChaseTimes[0] = ChaseTimes[1]; + ChaseTimes[1] += CHASE_SECOND_LIGHT_OFFSET; + CheckForEndOfSequence(); + } } -int Chase::WheelFrame(int i) { - int frame = this->currentFrame; - int pixelCount = matrix->getPixelCount(); - if (i == (this->currentPixel - 1) % pixelCount) { - if (this->reverse) { - frame = frame + 16; - } else { - frame = frame - 16; - } +void Chase::CheckForEndOfSequence() +{ + bool hasEnded = false; + + switch(RandomChaseType) + { + case SingleChaseTypes::SINGLECHASETYPES_SEQUENTIAL: + { + if((unsigned int)CurrentLight >= OrderedLights.size()) + { + hasEnded = true; + CurrentLight = 0; + ChaseTimes[0] = 1.0f; + ChaseTimes[1] = 1.0f + CHASE_SECOND_LIGHT_OFFSET; + } + } break; + + case SingleChaseTypes::SINGLECHASETYPES_LEFT_TO_RIGHT: + case SingleChaseTypes::SINGLECHASETYPES_RIGHT_TO_LEFT: + { + if(CurrentLight > MaxXCoord) + { + hasEnded = true; + CurrentLight = MinXCoord; + ChaseTimes[0] = 1.0f; + ChaseTimes[1] = 1.0f + CHASE_SECOND_LIGHT_OFFSET; + } + } break; + + case SingleChaseTypes::SINGLECHASETYPES_TOP_TO_BOTTOM: + case SingleChaseTypes::SINGLECHASETYPES_BOTTOM_TO_TOP: + { + if(CurrentLight > MaxYCoord) + { + hasEnded = true; + CurrentLight = MinYCoord; + ChaseTimes[0] = 1.0f; + ChaseTimes[1] = 1.0f + CHASE_SECOND_LIGHT_OFFSET; + } + } break; + + default: + break; } - if (i == (this->currentPixel - 2) % pixelCount) { - if (this->reverse) { - frame = frame + 32; - } else { - frame = frame - 32; + if(hasEnded) + { + //if a multi type then decide next type + switch(ChaseTypeInUse) + { + case ChaseTypes::CHASETYPES_SEQUENTIAL_PINGPONG: + { + Reversed = !Reversed; + } break; + + case ChaseTypes::CHASETYPES_HORIZONTAL_PINGPONG: + { + if(RandomChaseType == SingleChaseTypes::SINGLECHASETYPES_LEFT_TO_RIGHT) + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_RIGHT_TO_LEFT; + else + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_LEFT_TO_RIGHT; + } break; + + case ChaseTypes::CHASETYPES_VERTICAL_PINGPONG: + { + if(RandomChaseType == SingleChaseTypes::SINGLECHASETYPES_TOP_TO_BOTTOM) + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_BOTTOM_TO_TOP; + else + RandomChaseType = SingleChaseTypes::SINGLECHASETYPES_TOP_TO_BOTTOM; + } break; + + case ChaseTypes::CHASETYPES_RANDOM: + { + RandomChaseType = (SingleChaseTypes)(rand() % (int)SINGLECHASETYPES_MAX); + } break; + + case ChaseTypes::CHASETYPES_TESTLAYOUT: + { + TestLayoutFlipFlop = !TestLayoutFlipFlop; + RandomChaseType = TestLayoutFlipFlop ? SingleChaseTypes::SINGLECHASETYPES_TOP_TO_BOTTOM : SingleChaseTypes::SINGLECHASETYPES_LEFT_TO_RIGHT; + } break; + + default: + break; } } +} + +void Chase::ParameterUp() +{ + int32_t cycleTime = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime; + cycleTime = cycleTime + CHASE_CYCLE_INCREMENT; - if (frame < 0) { - return 0; + if (cycleTime > CHASE_CYCLE_MAX) + { + cycleTime = CHASE_CYCLE_MAX; } - return frame; + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = cycleTime; } -void Chase::ParameterUp() { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - animationOptions.chaseCycleTime = animationOptions.chaseCycleTime + CHASE_CYCLE_INCREMENT; - if (animationOptions.chaseCycleTime > CHASE_CYCLE_MAX) { - animationOptions.chaseCycleTime = CHASE_CYCLE_MAX; - } -} +void Chase::ParameterDown() +{ + int16_t cycleTime = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime; + cycleTime = cycleTime - CHASE_CYCLE_INCREMENT; -void Chase::ParameterDown() { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - animationOptions.chaseCycleTime =animationOptions.chaseCycleTime - CHASE_CYCLE_INCREMENT; - if (animationOptions.chaseCycleTime < CHASE_CYCLE_MIN) { - animationOptions.chaseCycleTime = CHASE_CYCLE_MIN; + if (cycleTime < CHASE_CYCLE_MIN) + { + cycleTime = CHASE_CYCLE_MIN; } + + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = cycleTime; } diff --git a/src/animationstation/effects/customtheme.cpp b/src/animationstation/effects/customtheme.cpp deleted file mode 100644 index 2e081cd139..0000000000 --- a/src/animationstation/effects/customtheme.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "customtheme.h" -#include "storagemanager.h" - -CustomTheme::CustomTheme(PixelMatrix &matrix) : Animation(matrix) { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.hasCustomTheme) - { - theme[GAMEPAD_MASK_DU] = RGB(animationOptions.customThemeUp); - theme[GAMEPAD_MASK_DD] = RGB(animationOptions.customThemeDown); - theme[GAMEPAD_MASK_DL] = RGB(animationOptions.customThemeLeft); - theme[GAMEPAD_MASK_DR] = RGB(animationOptions.customThemeRight); - theme[GAMEPAD_MASK_B1] = RGB(animationOptions.customThemeB1); - theme[GAMEPAD_MASK_B2] = RGB(animationOptions.customThemeB2); - theme[GAMEPAD_MASK_B3] = RGB(animationOptions.customThemeB3); - theme[GAMEPAD_MASK_B4] = RGB(animationOptions.customThemeB4); - theme[GAMEPAD_MASK_L1] = RGB(animationOptions.customThemeL1); - theme[GAMEPAD_MASK_R1] = RGB(animationOptions.customThemeR1); - theme[GAMEPAD_MASK_L2] = RGB(animationOptions.customThemeL2); - theme[GAMEPAD_MASK_R2] = RGB(animationOptions.customThemeR2); - theme[GAMEPAD_MASK_S1] = RGB(animationOptions.customThemeS1); - theme[GAMEPAD_MASK_S2] = RGB(animationOptions.customThemeS2); - theme[GAMEPAD_MASK_A1] = RGB(animationOptions.customThemeA1); - theme[GAMEPAD_MASK_A2] = RGB(animationOptions.customThemeA2); - theme[GAMEPAD_MASK_L3] = RGB(animationOptions.customThemeL3); - theme[GAMEPAD_MASK_R3] = RGB(animationOptions.customThemeR3); - } -} - -bool CustomTheme::Animate(RGB (&frame)[100]) { - UpdateTime(); - UpdatePresses(frame); - - for (size_t r = 0; r != matrix->pixels.size(); r++) { - for (size_t c = 0; c != matrix->pixels[r].size(); c++) { - if (matrix->pixels[r][c].index == NO_PIXEL.index) - continue; - - // Count down the timer - DecrementFadeCounter(matrix->pixels[r][c].index); - - auto itr = theme.find(matrix->pixels[r][c].mask); - if (itr != theme.end()) { - for (size_t p = 0; p != matrix->pixels[r][c].positions.size(); p++) { - // Interpolate from hitColor (color the button was assigned when pressed) back to the theme color - frame[matrix->pixels[r][c].positions[p]] = BlendColor(hitColor[matrix->pixels[r][c].index], itr->second, times[matrix->pixels[r][c].index]); - } - } else { - for (size_t p = 0; p != matrix->pixels[r][c].positions.size(); p++) { - frame[matrix->pixels[r][c].positions[p]] = defaultColor; - } - } - } - } - - return true; -} - -bool CustomTheme::HasTheme() { - return theme.size() > 0; -} - -void CustomTheme::ParameterUp() { -} - -void CustomTheme::ParameterDown() { -} diff --git a/src/animationstation/effects/customthemepressed.cpp b/src/animationstation/effects/customthemepressed.cpp deleted file mode 100644 index c2b71df5e9..0000000000 --- a/src/animationstation/effects/customthemepressed.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "customthemepressed.h" -#include "GamepadState.h" -#include "storagemanager.h" - -CustomThemePressed::CustomThemePressed(PixelMatrix &matrix) : Animation(matrix) { - this->filtered = true; - - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.hasCustomTheme) - { - theme[GAMEPAD_MASK_DU] = RGB(animationOptions.customThemeUpPressed); - theme[GAMEPAD_MASK_DD] = RGB(animationOptions.customThemeDownPressed); - theme[GAMEPAD_MASK_DL] = RGB(animationOptions.customThemeLeftPressed); - theme[GAMEPAD_MASK_DR] = RGB(animationOptions.customThemeRightPressed); - theme[GAMEPAD_MASK_B1] = RGB(animationOptions.customThemeB1Pressed); - theme[GAMEPAD_MASK_B2] = RGB(animationOptions.customThemeB2Pressed); - theme[GAMEPAD_MASK_B3] = RGB(animationOptions.customThemeB3Pressed); - theme[GAMEPAD_MASK_B4] = RGB(animationOptions.customThemeB4Pressed); - theme[GAMEPAD_MASK_L1] = RGB(animationOptions.customThemeL1Pressed); - theme[GAMEPAD_MASK_R1] = RGB(animationOptions.customThemeR1Pressed); - theme[GAMEPAD_MASK_L2] = RGB(animationOptions.customThemeL2Pressed); - theme[GAMEPAD_MASK_R2] = RGB(animationOptions.customThemeR2Pressed); - theme[GAMEPAD_MASK_S1] = RGB(animationOptions.customThemeS1Pressed); - theme[GAMEPAD_MASK_S2] = RGB(animationOptions.customThemeS2Pressed); - theme[GAMEPAD_MASK_A1] = RGB(animationOptions.customThemeA1Pressed); - theme[GAMEPAD_MASK_A2] = RGB(animationOptions.customThemeA2Pressed); - theme[GAMEPAD_MASK_L3] = RGB(animationOptions.customThemeL3Pressed); - theme[GAMEPAD_MASK_R3] = RGB(animationOptions.customThemeR3Pressed); - } -} - -CustomThemePressed::CustomThemePressed(PixelMatrix &matrix, std::vector &pixels) : Animation(matrix), pixels(&pixels) { - this->filtered = true; - - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.hasCustomTheme) - { - theme[GAMEPAD_MASK_DU] = RGB(animationOptions.customThemeUpPressed); - theme[GAMEPAD_MASK_DD] = RGB(animationOptions.customThemeDownPressed); - theme[GAMEPAD_MASK_DL] = RGB(animationOptions.customThemeLeftPressed); - theme[GAMEPAD_MASK_DR] = RGB(animationOptions.customThemeRightPressed); - theme[GAMEPAD_MASK_B1] = RGB(animationOptions.customThemeB1Pressed); - theme[GAMEPAD_MASK_B2] = RGB(animationOptions.customThemeB2Pressed); - theme[GAMEPAD_MASK_B3] = RGB(animationOptions.customThemeB3Pressed); - theme[GAMEPAD_MASK_B4] = RGB(animationOptions.customThemeB4Pressed); - theme[GAMEPAD_MASK_L1] = RGB(animationOptions.customThemeL1Pressed); - theme[GAMEPAD_MASK_R1] = RGB(animationOptions.customThemeR1Pressed); - theme[GAMEPAD_MASK_L2] = RGB(animationOptions.customThemeL2Pressed); - theme[GAMEPAD_MASK_R2] = RGB(animationOptions.customThemeR2Pressed); - theme[GAMEPAD_MASK_S1] = RGB(animationOptions.customThemeS1Pressed); - theme[GAMEPAD_MASK_S2] = RGB(animationOptions.customThemeS2Pressed); - theme[GAMEPAD_MASK_A1] = RGB(animationOptions.customThemeA1Pressed); - theme[GAMEPAD_MASK_A2] = RGB(animationOptions.customThemeA2Pressed); - theme[GAMEPAD_MASK_L3] = RGB(animationOptions.customThemeL3Pressed); - theme[GAMEPAD_MASK_R3] = RGB(animationOptions.customThemeR3Pressed); - } -} - -bool CustomThemePressed::Animate(RGB (&frame)[100]) { - for (size_t r = 0; r != matrix->pixels.size(); r++) { - for (size_t c = 0; c != matrix->pixels[r].size(); c++) { - if (matrix->pixels[r][c].index == NO_PIXEL.index || this->notInFilter(matrix->pixels[r][c])) - continue; - - auto itr = theme.find(matrix->pixels[r][c].mask); - if (itr != theme.end()) - for (size_t p = 0; p != matrix->pixels[r][c].positions.size(); p++) - frame[matrix->pixels[r][c].positions[p]] = itr->second; - else - for (size_t p = 0; p != matrix->pixels[r][c].positions.size(); p++) - frame[matrix->pixels[r][c].positions[p]] = defaultColor; - } - } - return true; -} - -bool CustomThemePressed::HasTheme() { - return theme.size() > 0; -} diff --git a/src/animationstation/effects/idletimeout.cpp b/src/animationstation/effects/idletimeout.cpp new file mode 100644 index 0000000000..06af42eac5 --- /dev/null +++ b/src/animationstation/effects/idletimeout.cpp @@ -0,0 +1,14 @@ +#include "idletimeout.h" + +IdleTimeout::IdleTimeout(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType) : StaticColor(InRGBLights, InButtonCaseEffectType) +{ +} + +IdleTimeout::IdleTimeout(Lights& InRGBLights, std::vector &InPressedPins) : StaticColor(InRGBLights, EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_ONLY) +{ +} + +RGB IdleTimeout::GetNonPressedColorForLight(uint32_t LightIndex) +{ + return ColorBlack; +} diff --git a/src/animationstation/effects/jigglestaticcolor.cpp b/src/animationstation/effects/jigglestaticcolor.cpp new file mode 100644 index 0000000000..8a3d9d0da5 --- /dev/null +++ b/src/animationstation/effects/jigglestaticcolor.cpp @@ -0,0 +1,32 @@ +#include "jigglestaticcolor.h" +#include "staticcolor.h" + +JiggleStaticColor::JiggleStaticColor(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType) : StaticColor(InRGBLights, InButtonCaseEffectType) +{ +} + +JiggleStaticColor::JiggleStaticColor(Lights& InRGBLights, std::vector &InPressedPins) : StaticColor(InRGBLights, InPressedPins) +{ +} + +RGB JiggleStaticColor::AdjustColor(RGB InColor) +{ + RGB outColor; + float newR = (((float)InColor.r) * 0.85f) + (38 * (((float)(rand() % 100) / 100.0f))); + if(newR > 255.0f) + newR = 255; + outColor.r = (int)newR; + +float newG = (((float)InColor.g) * 0.85f) + (38 * (((float)(rand() % 100) / 100.0f))); + if(newG > 255.0f) + newG = 255; + outColor.g = (int)newG; + + float newB = (((float)InColor.b) * 0.85f) + (38 * (((float)(rand() % 100) / 100.0f))); + if(newB > 255.0f) + newB = 255; + outColor.b = (int)newB; + + return outColor; +} + \ No newline at end of file diff --git a/src/animationstation/effects/jiggletwostaticcolor.cpp b/src/animationstation/effects/jiggletwostaticcolor.cpp new file mode 100644 index 0000000000..c43b016e4e --- /dev/null +++ b/src/animationstation/effects/jiggletwostaticcolor.cpp @@ -0,0 +1,48 @@ +#include "jiggletwostaticcolor.h" +#include "staticcolor.h" + +JiggleTwoStaticColor::JiggleTwoStaticColor(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType) : StaticColor(InRGBLights, InButtonCaseEffectType) +{ +} + +JiggleTwoStaticColor::JiggleTwoStaticColor(Lights& InRGBLights, std::vector &InPressedPins) : StaticColor(InRGBLights, InPressedPins) +{ +} + +RGB JiggleTwoStaticColor::AdjustColor(RGB InColor) +{ + RGB otherColor; + + if(isButtonAnimation) + otherColor = RGB(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].pressedSpecialColor); + else + otherColor = RGB(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].nonPressedSpecialColor); + + RGB outColor; + float rDiff = (float)otherColor.r - (float)InColor.r; + float newR = (float)InColor.r + (rDiff * (((float)(rand() % 100) / 100.0f))); + if(newR > 255.0f) + newR = 255; + if(newR < 0) + newR = 0; + outColor.r = (int)newR; + + float gDiff = (float)otherColor.g - (float)InColor.g; + float newG = (float)InColor.g + (gDiff * (((float)(rand() % 100) / 100.0f))); + if(newG > 255.0f) + newG = 255; + if(newG < 0) + newG = 0; + outColor.g = (int)newG; + + float bDiff = (float)otherColor.b - (float)InColor.b; + float newB = (float)InColor.b + (bDiff * (((float)(rand() % 100) / 100.0f))); + if(newB > 255.0f) + newB = 255; + if(newB < 0) + newB = 0; + outColor.b = (int)newB; + + return outColor; +} + \ No newline at end of file diff --git a/src/animationstation/effects/rain.cpp b/src/animationstation/effects/rain.cpp new file mode 100644 index 0000000000..75fb423db3 --- /dev/null +++ b/src/animationstation/effects/rain.cpp @@ -0,0 +1,258 @@ +#include "rain.h" +#include + +#define RAIN_CYCLE_INCREMENT 50 +#define RAIN_CYCLE_MAX 500 +#define RAIN_CYCLE_MIN 10 + +Rain::Rain(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType, ERainFrequency InRainFrequency) : Animation(InRGBLights, InButtonCaseEffectType) +{ + RainFrequency = InRainFrequency; + + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime == 0) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = ((RAIN_CYCLE_MAX - RAIN_CYCLE_MIN) / 2) + RAIN_CYCLE_MIN; + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime > RAIN_CYCLE_MAX) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = RAIN_CYCLE_MAX; + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime < RAIN_CYCLE_MIN) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = RAIN_CYCLE_MIN; + + for(int index = 0; index < MAX_RAIN_DROPS; ++index) + { + RainYCoords[index] = -1.0f; + RainXCoords[index] = -1; + } + + for(int index = 0; index < RAIN_DROP_NO_REPEAT_X_NUM; ++index) + { + previousRainDropXCoords[index] = -1; + } + + //Get max x and y coords + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + if(LightTypeIsForAnimation(RGBLights->AllLights[lightIndex].Type) == false) + continue; + + if(RGBLights->AllLights[lightIndex].Position.XPosition > MaxXCoord) + MaxXCoord = RGBLights->AllLights[lightIndex].Position.XPosition; + if(RGBLights->AllLights[lightIndex].Position.YPosition > MaxYCoord) + MaxYCoord = RGBLights->AllLights[lightIndex].Position.YPosition; + + if(lightIndex == 0 || RGBLights->AllLights[lightIndex].Position.XPosition < MinXCoord) + MinXCoord = RGBLights->AllLights[lightIndex].Position.XPosition; + if(lightIndex == 0 || RGBLights->AllLights[lightIndex].Position.YPosition < MinYCoord) + MinYCoord = RGBLights->AllLights[lightIndex].Position.YPosition; + } +} + +float Rain::GetTimeTillNextDrop() +{ + switch(RainFrequency) + { + case ERainFrequency::RAIN_LOW: + return 3.5f; + break; + + case ERainFrequency::RAIN_MEDIUM: + return 2.5f; + break; + + case ERainFrequency::RAIN_HIGH: + return 1.0f; + break; + + default: + return 3.0f; + break; + } +} + +bool Rain::RainHistoryContains(int newXCoord) +{ + for(int index = 0; index < RAIN_DROP_NO_REPEAT_X_NUM; ++index) + { + if(previousRainDropXCoords[index] == newXCoord) + return true; + if(newXCoord > 0 && (previousRainDropXCoords[index] == newXCoord-1)) + return true; + if(newXCoord > 1 && (previousRainDropXCoords[index] == newXCoord-2)) + return true; + if(newXCoord < MaxXCoord && (previousRainDropXCoords[index] == newXCoord+1)) + return true; + if(newXCoord < MaxXCoord-1 && (previousRainDropXCoords[index] == newXCoord+2)) + return true; + } + + return false; +} + +int Rain::FindLightForCoord(int xCoord, int yCoord) +{ + //if we can find an exact match we return that. Otherwise we look for one to the left and then right of where we are. Max 1 spot away + int backupLeftIndex = -1; + int backupRightIndex = -1; + + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + if(LightTypeIsForAnimation(RGBLights->AllLights[lightIndex].Type) == false) + continue; + + if(RGBLights->AllLights[lightIndex].Position.YPosition == yCoord) + { + int xPos = RGBLights->AllLights[lightIndex].Position.XPosition; + if(xPos == xCoord) + return lightIndex; + else if (xPos+1 == xCoord) + backupLeftIndex = lightIndex; + else if (xPos-1 == xCoord) + backupRightIndex = lightIndex; + } + } + + if(backupLeftIndex != -1) + return backupLeftIndex; + + return backupRightIndex; +} + +void Rain::Animate(RGB (&frame)[FRAME_MAX]) +{ + UpdateTime(); + + //reset all lights first to ensure that if pressed lights are unpressed they can blend back to correct color + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + if(LightTypeIsForAnimation(RGBLights->AllLights[lightIndex].Type) == false) + continue; + + RGB lightCol = GetNonPressedColorForLight(lightIndex); + uint8_t firstLedIndex = RGBLights->AllLights[lightIndex].FirstLedIndex; + uint8_t lastLedIndex = firstLedIndex + RGBLights->AllLights[lightIndex].LedsPerLight; + for(uint8_t ledIndex = firstLedIndex; ledIndex < lastLedIndex; ++ledIndex) + { + frame[ledIndex] = lightCol; + } + } + + //if there isnt enough lights for this effect then do nothing + if((MaxXCoord - MinXCoord) < (RAIN_DROP_NO_REPEAT_X_NUM + 2)) + return; + + //update times and spawn next drop if required + TimeTillNextRain -= (((float)AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime) / 1000.0f); + if(TimeTillNextRain < 0.0f) + { + //reduce history by 1 + for(int index = 0; index < (RAIN_DROP_NO_REPEAT_X_NUM - 1); ++index) + { + previousRainDropXCoords[index] = previousRainDropXCoords[index + 1]; + } + previousRainDropXCoords[RAIN_DROP_NO_REPEAT_X_NUM - 1] = -1; + + //generate new X coord that isnt in the history + int newRainXCoord = -1; + int infGuard = 10; + while (infGuard > 0 && (newRainXCoord == -1 || RainHistoryContains(newRainXCoord))) + { + newRainXCoord = rand() % ((MaxXCoord - MinXCoord) + 1); + newRainXCoord += MinXCoord; + infGuard--; + } + + if(infGuard != 0) + { + //spawn new drop + for(int rainIndex = 0; rainIndex < MAX_RAIN_DROPS; ++rainIndex) + { + if(RainXCoords[rainIndex] < 0) + { + RainXCoords[rainIndex] = newRainXCoord; + previousRainDropXCoords[RAIN_DROP_NO_REPEAT_X_NUM - 1] = newRainXCoord; + RainYCoords[rainIndex] = ((float)MinYCoord) - 1.0f; + break; + } + } + } + + TimeTillNextRain += GetTimeTillNextDrop(); + } + + //update rain drops + for(int rainIndex = 0; rainIndex < MAX_RAIN_DROPS; ++rainIndex) + { + if(RainXCoords[rainIndex] < 0) + continue; + + //advance rain drop vertical + RainYCoords[rainIndex] += (DefaultRainSpeed * (((float)AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime) / 1000.0f)); + + //is it finished (off the bottom) + if(RainYCoords[rainIndex] > (float)(MaxYCoord + 1)) + { + RainXCoords[rainIndex] = -1; + continue; + } + + //Find closest light to this and the next y coord + int firstYIndex = (int)RainYCoords[rainIndex]; + int firstLightIndex = FindLightForCoord(RainXCoords[rainIndex], firstYIndex); + int secondLightIndex = FindLightForCoord(RainXCoords[rainIndex], firstYIndex + 1); + + float firstLightAlpha = 1.0f - (RainYCoords[rainIndex] - (float)firstYIndex); + float secondLightAlpha = 1.0f - firstLightAlpha; + + RGB specialLightCol = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].nonPressedSpecialColor; + + if(firstLightIndex >= 0) + { + RGB firstLightCol = GetNonPressedColorForLight(firstLightIndex); + uint8_t firstLedIndex = RGBLights->AllLights[firstLightIndex].FirstLedIndex; + uint8_t lastLedIndex = firstLedIndex + RGBLights->AllLights[firstLightIndex].LedsPerLight; + for(uint8_t ledIndex = firstLedIndex; ledIndex < lastLedIndex; ++ledIndex) + { + frame[ledIndex] = BlendColor(firstLightCol, + specialLightCol, + firstLightAlpha); + } + } + + if(secondLightIndex >= 0) + { + RGB secondLightCol = GetNonPressedColorForLight(secondLightIndex); + uint8_t firstLedIndex = RGBLights->AllLights[secondLightIndex].FirstLedIndex; + uint8_t lastLedIndex = firstLedIndex + RGBLights->AllLights[secondLightIndex].LedsPerLight; + for(uint8_t ledIndex = firstLedIndex; ledIndex < lastLedIndex; ++ledIndex) + { + frame[ledIndex] = BlendColor(secondLightCol, + specialLightCol, + secondLightAlpha); + } + } + } +} + +void Rain::ParameterUp() +{ + int32_t cycleTime = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime; + cycleTime = cycleTime + RAIN_CYCLE_INCREMENT; + + if (cycleTime > RAIN_CYCLE_MAX) + { + cycleTime = RAIN_CYCLE_MAX; + } + + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = cycleTime; +} + +void Rain::ParameterDown() +{ + int16_t cycleTime = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime; + cycleTime = cycleTime - RAIN_CYCLE_INCREMENT; + + if (cycleTime < RAIN_CYCLE_MIN) + { + cycleTime = RAIN_CYCLE_MIN; + } + + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = cycleTime; +} diff --git a/src/animationstation/effects/rainbow.cpp b/src/animationstation/effects/rainbow.cpp index ab9cf9be65..c374e64497 100644 --- a/src/animationstation/effects/rainbow.cpp +++ b/src/animationstation/effects/rainbow.cpp @@ -1,72 +1,167 @@ #include "rainbow.h" #include "storagemanager.h" -Rainbow::Rainbow(PixelMatrix &matrix) : Animation(matrix) { +#define RAINBOW_COLORWHEEL_FRAME_MAX 255 + +// clamp rainbowCycleTime to [1 ... INT16_MAX] +#define RAINBOW_CYCLE_INCREMENT 5 +#define RAINBOW_CYCLE_MAX 60 +#define RAINBOW_CYCLE_MIN 2 + +//grid distance to frame offset value +#define RAINBOW_GRID_OFFSET_ADJUST 30 + +RainbowSynced::RainbowSynced(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType) : Animation(InRGBLights, InButtonCaseEffectType) +{ + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime == 0) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = ((RAINBOW_CYCLE_MAX - RAINBOW_CYCLE_MIN) / 2) + RAINBOW_CYCLE_MIN; + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime > RAINBOW_CYCLE_MAX) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = RAINBOW_CYCLE_MAX; + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime < RAINBOW_CYCLE_MIN ) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = RAINBOW_CYCLE_MIN; } -bool Rainbow::Animate(RGB (&frame)[100]) { - if (!time_reached(this->nextRunTime)) { - return false; - } +void RainbowSynced::Animate(RGB (&frame)[FRAME_MAX]) +{ + if (time_reached(this->nextRunTime)) + { + if (reverse) + { + currentFrame--; - UpdateTime(); - UpdatePresses(frame); + if (currentFrame < 0) + { + currentFrame = 1; + reverse = false; + } + } + else + { + currentFrame++; - for (auto &col : matrix->pixels) { - for (auto &pixel : col) { - if (pixel.index == NO_PIXEL.index) - continue; + if (currentFrame > RAINBOW_COLORWHEEL_FRAME_MAX) + { + currentFrame = RAINBOW_COLORWHEEL_FRAME_MAX - 1; + reverse = true; + } + } - // Count down the timer - DecrementFadeCounter(pixel.index); + this->nextRunTime = make_timeout_time_ms(RAINBOW_CYCLE_MAX - AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime); + } - RGB color = RGB::wheel(this->currentFrame); - for (auto &pos : pixel.positions) - frame[pos] = BlendColor(hitColor[pixel.index], color, times[pixel.index]); + RGB color = RGB::wheel(this->currentFrame); + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + if(LightTypeIsForAnimation(RGBLights->AllLights[lightIndex].Type)) + { + uint8_t firstLightIndex = RGBLights->AllLights[lightIndex].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[lightIndex].LedsPerLight; + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + //Non pressed simply sets the RGB color + frame[ledIndex] = color; + } } } +} - if (reverse) { - currentFrame--; +void RainbowSynced::ParameterUp() +{ + int16_t cycleTime = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime; - if (currentFrame < 0) { - currentFrame = 1; - reverse = false; - } - } else { + cycleTime = cycleTime + RAINBOW_CYCLE_INCREMENT; + + if (cycleTime > RAINBOW_CYCLE_MAX) + { + cycleTime = RAINBOW_CYCLE_MAX; + } + + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = cycleTime; +} + +void RainbowSynced::ParameterDown() +{ + int16_t cycleTime = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime; + + cycleTime = cycleTime - RAINBOW_CYCLE_INCREMENT; + + if (cycleTime < RAINBOW_CYCLE_MIN) + { + cycleTime = RAINBOW_CYCLE_MIN; + } + + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = cycleTime; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +RainbowRotate::RainbowRotate(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType) : Animation(InRGBLights, InButtonCaseEffectType) +{ + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime == 0) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = ((RAINBOW_CYCLE_MAX - RAINBOW_CYCLE_MIN) / 2) + RAINBOW_CYCLE_MIN; + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime > RAINBOW_CYCLE_MAX) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = RAINBOW_CYCLE_MAX; + if(AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime < RAINBOW_CYCLE_MIN) + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = RAINBOW_CYCLE_MIN;} + +void RainbowRotate::Animate(RGB (&frame)[FRAME_MAX]) +{ + if (time_reached(this->nextRunTime)) + { currentFrame++; - if (currentFrame > 255) { - currentFrame = 254; - reverse = true; + if (currentFrame >= 255) + { + currentFrame = 0; } - } - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - this->nextRunTime = make_timeout_time_ms(animationOptions.rainbowCycleTime); + this->nextRunTime = make_timeout_time_ms(RAINBOW_CYCLE_MAX - AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime); + } - return true; -} + //the way this works is we offset the current frame by the distance from the top left of the grid + int thisFrame = this->currentFrame; + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + if(LightTypeIsForAnimation(RGBLights->AllLights[lightIndex].Type)) + { + int gridOffset = RGBLights->AllLights[lightIndex].Position.XPosition + RGBLights->AllLights[lightIndex].Position.YPosition; + int thisLightFrame = (thisFrame + (gridOffset * RAINBOW_GRID_OFFSET_ADJUST)) % RAINBOW_COLORWHEEL_FRAME_MAX; + RGB color = RGB::wheel(thisLightFrame); -// clamp rainbowCycleTime to [1 ... INT16_MAX] -#define RAINBOW_CYCLE_INCREMENT 10 -#define RAINBOW_CYCLE_MAX INT16_MAX - RAINBOW_CYCLE_INCREMENT -#define RAINBOW_CYCLE_MIN 1 + RAINBOW_CYCLE_INCREMENT - -void Rainbow::ParameterUp() { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.rainbowCycleTime < RAINBOW_CYCLE_MAX) { - animationOptions.rainbowCycleTime = animationOptions.rainbowCycleTime + RAINBOW_CYCLE_INCREMENT; - } else { - animationOptions.rainbowCycleTime = INT16_MAX; + uint8_t firstLightIndex = RGBLights->AllLights[lightIndex].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[lightIndex].LedsPerLight; + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + //Non pressed simply sets the RGB color + frame[ledIndex] = color; + } + } } } -void Rainbow::ParameterDown() { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.rainbowCycleTime > RAINBOW_CYCLE_MIN) { - animationOptions.rainbowCycleTime = animationOptions.rainbowCycleTime - RAINBOW_CYCLE_INCREMENT; - } else { - animationOptions.rainbowCycleTime = 1; - } +void RainbowRotate::ParameterUp() +{ + int16_t cycleTime = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime; + + cycleTime = cycleTime + RAINBOW_CYCLE_INCREMENT; + + if (cycleTime > RAINBOW_CYCLE_MAX) + { + cycleTime = RAINBOW_CYCLE_MAX; + } + + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = cycleTime; } + +void RainbowRotate::ParameterDown() +{ + int16_t cycleTime = AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime; + + cycleTime = cycleTime - RAINBOW_CYCLE_INCREMENT; + + if (cycleTime < RAINBOW_CYCLE_MIN) + { + cycleTime = RAINBOW_CYCLE_MIN; + } + + AnimationStation::options.profiles[AnimationStation::options.baseProfileIndex].baseCycleTime = cycleTime; +} \ No newline at end of file diff --git a/src/animationstation/effects/randomcolor.cpp b/src/animationstation/effects/randomcolor.cpp new file mode 100644 index 0000000000..15941c1dfe --- /dev/null +++ b/src/animationstation/effects/randomcolor.cpp @@ -0,0 +1,49 @@ +#include "randomcolor.h" + +RandomColor::RandomColor(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType) : Animation(InRGBLights, InButtonCaseEffectType) +{ +} + +RandomColor::RandomColor(Lights& InRGBLights, std::vector &InPressedPins) : Animation(InRGBLights, EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_ONLY) +{ + isButtonAnimation = true; + pressedPins = InPressedPins; + + savedPressedColor.clear(); + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + savedPressedColor.push_back(RGB(0)); + } +} + +void RandomColor::NewPressForPin(int lightIndex) +{ + //dont pick 0 as thats black + savedPressedColor[lightIndex] = colors[(rand() % (colors.size()-1)) + 1]; +} + +void RandomColor::Animate(RGB (&frame)[FRAME_MAX]) +{ + UpdateTime(); + UpdatePresses(); + + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + uint8_t firstLightIndex = RGBLights->AllLights[lightIndex].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[lightIndex].LedsPerLight; + + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + //Non pressed simply sets the RGB color + if (LightTypeIsForAnimation(RGBLights->AllLights[lightIndex].Type)) + { + frame[ledIndex] = FadeColor(savedPressedColor[lightIndex], + frame[ledIndex], + fadeTimes[ledIndex]); + } + } + } + + // Count down the timer + DecrementFadeCounters(); +} \ No newline at end of file diff --git a/src/animationstation/effects/staticcolor.cpp b/src/animationstation/effects/staticcolor.cpp index 5d8fb63840..0e13a10108 100644 --- a/src/animationstation/effects/staticcolor.cpp +++ b/src/animationstation/effects/staticcolor.cpp @@ -1,88 +1,45 @@ #include "staticcolor.h" #include "storagemanager.h" -StaticColor::StaticColor(PixelMatrix &matrix) : Animation(matrix) { +StaticColor::StaticColor(Lights& InRGBLights, EButtonCaseEffectType InButtonCaseEffectType) : Animation(InRGBLights, InButtonCaseEffectType) +{ } -StaticColor::StaticColor(PixelMatrix &matrix, std::vector &inpixels) : Animation(matrix) { - this->filtered = true; - pixels = inpixels; +StaticColor::StaticColor(Lights& InRGBLights, std::vector &InPressedPins) : Animation(InRGBLights, EButtonCaseEffectType::BUTTONCASELIGHTTYPE_BUTTON_ONLY) +{ + isButtonAnimation = true; + pressedPins = InPressedPins; } -bool StaticColor::Animate(RGB (&frame)[100]) { +void StaticColor::Animate(RGB (&frame)[FRAME_MAX]) +{ UpdateTime(); - UpdatePresses(frame); - - for (size_t r = 0; r != matrix->pixels.size(); r++) { - for (size_t c = 0; c != matrix->pixels[r].size(); c++) { - if (matrix->pixels[r][c].index == NO_PIXEL.index || this->notInFilter(matrix->pixels[r][c])) - continue; - - // Count down the timer - DecrementFadeCounter(matrix->pixels[r][c].index); - - for (size_t p = 0; p != matrix->pixels[r][c].positions.size(); p++) { - // Interpolate from hitColor (color the button was assigned when pressed) back to the theme color - if (!this->filtered) { - frame[matrix->pixels[r][c].positions[p]] = BlendColor(hitColor[matrix->pixels[r][c].index], colors[this->GetColor()], times[matrix->pixels[r][c].index]); - } else { - frame[matrix->pixels[r][c].positions[p]] = colors[this->GetColor()]; + UpdatePresses(); + + for(unsigned int lightIndex = 0; lightIndex < RGBLights->AllLights.size(); ++lightIndex) + { + uint8_t firstLightIndex = RGBLights->AllLights[lightIndex].FirstLedIndex; + uint8_t lastLightIndex = firstLightIndex + RGBLights->AllLights[lightIndex].LedsPerLight; + + for(uint8_t ledIndex = firstLightIndex; ledIndex < lastLightIndex; ++ledIndex) + { + //Non pressed simply sets the RGB color + if(LightTypeIsForAnimation(RGBLights->AllLights[lightIndex].Type)) + { + if(!isButtonAnimation) + { + frame[ledIndex] = AdjustColor(GetNonPressedColorForLight(lightIndex)); + } + else if (isButtonAnimation) + { + frame[ledIndex] = FadeColor(AdjustColor(GetPressedColorForLight(lightIndex)), + frame[ledIndex], + fadeTimes[ledIndex]); } } } } - return true; -} - -uint8_t StaticColor::GetColor() { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (this->filtered) { - return animationOptions.buttonColorIndex; - } else { - return animationOptions.staticColorIndex; - } -} - -void StaticColor::ParameterUp() { - uint8_t colorIndex; - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (this->filtered) { - colorIndex = animationOptions.buttonColorIndex; - } else { - colorIndex = animationOptions.staticColorIndex; - } - if (colorIndex < colors.size() - 1) { - colorIndex++; - } else { - colorIndex = 0; - } - - this->SaveIndexOptions(colorIndex); -} - -void StaticColor::SaveIndexOptions(uint8_t colorIndex) { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (this->filtered) { - animationOptions.buttonColorIndex = colorIndex; - } else { - animationOptions.staticColorIndex = colorIndex; - } -} - -void StaticColor::ParameterDown() { - uint8_t colorIndex; - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (this->filtered) { - colorIndex = animationOptions.buttonColorIndex; - } else { - colorIndex = animationOptions.staticColorIndex; - } - - if (colorIndex > 0) { - colorIndex--; - } else { - colorIndex = colors.size() - 1; - } - this->SaveIndexOptions(colorIndex); + // Count down the timer + DecrementFadeCounters(); } diff --git a/src/animationstation/effects/statictheme.cpp b/src/animationstation/effects/statictheme.cpp deleted file mode 100644 index f0db7f7d26..0000000000 --- a/src/animationstation/effects/statictheme.cpp +++ /dev/null @@ -1,356 +0,0 @@ -#include "statictheme.h" -#include "storagemanager.h" -#include "enums.pb.h" - -StaticTheme::StaticTheme(PixelMatrix &matrix) : Animation(matrix) { - - const std::map themeGuiltyGearTypeA({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B1, ColorPink }, - { GAMEPAD_MASK_B3, ColorBlue }, - { GAMEPAD_MASK_B4, ColorGreen }, - { GAMEPAD_MASK_R1, ColorRed }, - { GAMEPAD_MASK_R2, ColorOrange }, - }); - - const std::map themeGuiltyGearTypeB({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B1, ColorRed }, - { GAMEPAD_MASK_B3, ColorPink }, - { GAMEPAD_MASK_B4, ColorBlue }, - { GAMEPAD_MASK_R1, ColorGreen }, - { GAMEPAD_MASK_R2, ColorOrange }, - }); - - const std::map themeGuiltyGearTypeC({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B1, ColorOrange }, - { GAMEPAD_MASK_B3, ColorPink }, - { GAMEPAD_MASK_B4, ColorBlue }, - { GAMEPAD_MASK_R1, ColorGreen }, - { GAMEPAD_MASK_R2, ColorRed }, - }); - - const std::map themeGuiltyGearTypeD({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B3, ColorPink }, - { GAMEPAD_MASK_B1, ColorBlue }, - { GAMEPAD_MASK_B4, ColorGreen }, - { GAMEPAD_MASK_B2, ColorRed }, - { GAMEPAD_MASK_R1, ColorOrange }, - }); - - const std::map themeGuiltyGearTypeE({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B3, ColorPink }, - { GAMEPAD_MASK_B1, ColorGreen }, - { GAMEPAD_MASK_B4, ColorBlue }, - { GAMEPAD_MASK_B2, ColorRed }, - { GAMEPAD_MASK_R1, ColorOrange }, - }); - - const std::map themeNeoGeo({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B3, ColorRed }, - { GAMEPAD_MASK_B4, ColorYellow }, - { GAMEPAD_MASK_R1, ColorGreen }, - { GAMEPAD_MASK_L1, ColorBlue }, - }); - - const std::map themeNeoGeoCurved({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B1, ColorRed }, - { GAMEPAD_MASK_B3, ColorYellow }, - { GAMEPAD_MASK_B4, ColorGreen }, - { GAMEPAD_MASK_R1, ColorBlue }, - }); - - const std::map themeNeoGeoModern({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B3, ColorRed }, - { GAMEPAD_MASK_B1, ColorYellow }, - { GAMEPAD_MASK_B4, ColorGreen }, - { GAMEPAD_MASK_B2, ColorBlue }, - }); - - const std::map themeSixButtonFighter({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B3, ColorBlue }, - { GAMEPAD_MASK_B1, ColorBlue }, - { GAMEPAD_MASK_B4, ColorYellow }, - { GAMEPAD_MASK_B2, ColorYellow }, - { GAMEPAD_MASK_R1, ColorRed }, - { GAMEPAD_MASK_R2, ColorRed }, - }); - - const std::map themeSixButtonFighterPlus({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B3, ColorBlue }, - { GAMEPAD_MASK_B1, ColorBlue }, - { GAMEPAD_MASK_B4, ColorYellow }, - { GAMEPAD_MASK_B2, ColorYellow }, - { GAMEPAD_MASK_R1, ColorRed }, - { GAMEPAD_MASK_R2, ColorRed }, - { GAMEPAD_MASK_L1, ColorGreen }, - { GAMEPAD_MASK_L2, ColorGreen }, - }); - - const std::map themeStreetFighter2({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B3, ColorRed }, - { GAMEPAD_MASK_B1, ColorRed }, - { GAMEPAD_MASK_B4, ColorWhite }, - { GAMEPAD_MASK_B2, ColorWhite }, - { GAMEPAD_MASK_R1, ColorBlue }, - { GAMEPAD_MASK_R2, ColorBlue }, - { GAMEPAD_MASK_L1, ColorBlack }, - { GAMEPAD_MASK_L2, ColorBlack }, - }); - - const std::map themeTekken({ - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_B3, ColorYellow }, - { GAMEPAD_MASK_B1, ColorAqua }, - { GAMEPAD_MASK_B4, ColorGreen }, - { GAMEPAD_MASK_B2, ColorPink }, - { GAMEPAD_MASK_R1, ColorRed }, - }); - - const std::map themePlayStation({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B1, ColorBlue }, - { GAMEPAD_MASK_B2, ColorRed }, - { GAMEPAD_MASK_B3, ColorMagenta }, - { GAMEPAD_MASK_B4, ColorGreen }, - { GAMEPAD_MASK_R1, ColorBlack }, - { GAMEPAD_MASK_R2, ColorBlack }, - { GAMEPAD_MASK_L1, ColorBlack }, - { GAMEPAD_MASK_L2, ColorBlack }, - }); - - const std::map themePlayStationAll({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B1, ColorBlue }, - { GAMEPAD_MASK_B2, ColorRed }, - { GAMEPAD_MASK_B3, ColorMagenta }, - { GAMEPAD_MASK_B4, ColorGreen }, - { GAMEPAD_MASK_R1, ColorWhite }, - { GAMEPAD_MASK_R2, ColorWhite }, - { GAMEPAD_MASK_L1, ColorWhite }, - { GAMEPAD_MASK_L2, ColorWhite }, - }); - - const std::map themeSuperFamicom({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B1, ColorYellow }, - { GAMEPAD_MASK_B2, ColorRed }, - { GAMEPAD_MASK_B3, ColorGreen }, - { GAMEPAD_MASK_B4, ColorBlue }, - { GAMEPAD_MASK_R1, ColorBlack }, - { GAMEPAD_MASK_R2, ColorBlack }, - { GAMEPAD_MASK_L1, ColorBlack }, - { GAMEPAD_MASK_L2, ColorBlack }, - }); - - const std::map themeSuperFamicomAll({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B1, ColorYellow }, - { GAMEPAD_MASK_B2, ColorRed }, - { GAMEPAD_MASK_B3, ColorGreen }, - { GAMEPAD_MASK_B4, ColorBlue }, - { GAMEPAD_MASK_R1, ColorWhite }, - { GAMEPAD_MASK_R2, ColorWhite }, - { GAMEPAD_MASK_L1, ColorWhite }, - { GAMEPAD_MASK_L2, ColorWhite }, - }); - - const std::map themeXbox({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B1, ColorGreen }, - { GAMEPAD_MASK_B2, ColorRed }, - { GAMEPAD_MASK_B3, ColorBlue }, - { GAMEPAD_MASK_B4, ColorYellow }, - { GAMEPAD_MASK_R1, ColorBlack }, - { GAMEPAD_MASK_R2, ColorBlack }, - { GAMEPAD_MASK_L1, ColorBlack }, - { GAMEPAD_MASK_L2, ColorBlack }, - }); - - const std::map themeXboxAll({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B1, ColorGreen }, - { GAMEPAD_MASK_B2, ColorRed }, - { GAMEPAD_MASK_B3, ColorBlue }, - { GAMEPAD_MASK_B4, ColorYellow }, - { GAMEPAD_MASK_R1, ColorWhite }, - { GAMEPAD_MASK_R2, ColorWhite }, - { GAMEPAD_MASK_L1, ColorWhite }, - { GAMEPAD_MASK_L2, ColorWhite }, - }); - - const std::map themeFightboard({ - { GAMEPAD_MASK_DU, ColorWhite }, - { GAMEPAD_MASK_DD, ColorWhite }, - { GAMEPAD_MASK_DL, ColorWhite }, - { GAMEPAD_MASK_DR, ColorWhite }, - { GAMEPAD_MASK_B1, ColorGreen }, - { GAMEPAD_MASK_B2, ColorRed }, - { GAMEPAD_MASK_B3, ColorBlue }, - { GAMEPAD_MASK_B4, ColorYellow }, - { GAMEPAD_MASK_R1, ColorPurple }, - { GAMEPAD_MASK_R2, ColorAqua }, - { GAMEPAD_MASK_L1, ColorOrange }, - { GAMEPAD_MASK_L2, ColorPink }, - }); - - // Rainbow theme on a Stickless layout should use green for up button - std::map themeStaticRainbow({ - { GAMEPAD_MASK_DL, ColorRed }, - { GAMEPAD_MASK_DD, ColorOrange }, - { GAMEPAD_MASK_DR, ColorYellow }, - { GAMEPAD_MASK_DU, ColorOrange }, - { GAMEPAD_MASK_B3, ColorGreen }, - { GAMEPAD_MASK_B1, ColorGreen }, - { GAMEPAD_MASK_B4, ColorAqua }, - { GAMEPAD_MASK_B2, ColorAqua }, - { GAMEPAD_MASK_R1, ColorBlue }, - { GAMEPAD_MASK_R2, ColorBlue }, - { GAMEPAD_MASK_L1, ColorMagenta }, - { GAMEPAD_MASK_L2, ColorMagenta }, - }); - - LEDOptions & ledOptions = Storage::getInstance().getLedOptions(); - themeStaticRainbow[GAMEPAD_MASK_DU] = (ledOptions.ledLayout == BUTTON_LAYOUT_STICKLESS) ? ColorGreen : ColorOrange; - - ClearThemes(); - AddTheme(themeStaticRainbow); - AddTheme(themeXbox); - AddTheme(themeXboxAll); - AddTheme(themeSuperFamicom); - AddTheme(themeSuperFamicomAll); - AddTheme(themePlayStation); - AddTheme(themePlayStationAll); - AddTheme(themeNeoGeo); - AddTheme(themeNeoGeoCurved); - AddTheme(themeNeoGeoModern); - AddTheme(themeSixButtonFighter); - AddTheme(themeSixButtonFighterPlus); - AddTheme(themeStreetFighter2); - AddTheme(themeTekken); - AddTheme(themeGuiltyGearTypeA); - AddTheme(themeGuiltyGearTypeB); - AddTheme(themeGuiltyGearTypeC); - AddTheme(themeGuiltyGearTypeD); - AddTheme(themeGuiltyGearTypeE); - AddTheme(themeFightboard); - - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.themeIndex >= themes.size()) { - animationOptions.themeIndex = 0; - } -} - -bool StaticTheme::Animate(RGB (&frame)[100]) { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (themes.size() > 0) { - UpdateTime(); - UpdatePresses(frame); - - for (size_t r = 0; r != matrix->pixels.size(); r++) { - for (size_t c = 0; c != matrix->pixels[r].size(); c++) { - if (matrix->pixels[r][c].index == NO_PIXEL.index) - continue; - - // Count down the timer - DecrementFadeCounter(matrix->pixels[r][c].index); - - std::map theme = themes.at(animationOptions.themeIndex); - - auto itr = theme.find(matrix->pixels[r][c].mask); - if (itr != theme.end()) { - for (size_t p = 0; p != matrix->pixels[r][c].positions.size(); p++) { - // Interpolate from hitColor (color the button was assigned when pressed) back to the theme color - frame[matrix->pixels[r][c].positions[p]] = BlendColor(hitColor[matrix->pixels[r][c].index], itr->second, times[matrix->pixels[r][c].index]); - } - } else { - for (size_t p = 0; p != matrix->pixels[r][c].positions.size(); p++) { - frame[matrix->pixels[r][c].positions[p]] = defaultColor; - } - } - } - } - } - return true; -} - -void StaticTheme::ParameterUp() { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.themeIndex < StaticTheme::themes.size() - 1) { - animationOptions.themeIndex++; - } else { - animationOptions.themeIndex = 0; - } -} - -void StaticTheme::ParameterDown() { - AnimationOptions & animationOptions = Storage::getInstance().getAnimationOptions(); - if (animationOptions.themeIndex > 0) { - animationOptions.themeIndex--; - } else { - animationOptions.themeIndex = StaticTheme::themes.size() - 1; - } -} diff --git a/src/config_legacy.cpp b/src/config_legacy.cpp index fe35513b33..c7c05a6bef 100644 --- a/src/config_legacy.cpp +++ b/src/config_legacy.cpp @@ -901,59 +901,6 @@ bool ConfigUtils::fromLegacyStorage(Config& config) SET_PROPERTY(ledOptions, pledColor, legacyLEDOptions.pledColor.value(LED_FORMAT_RGB)); } - const ConfigLegacy::AnimationOptions& legacyAnimationOptions = *reinterpret_cast(EEPROM_ADDRESS_START + ANIMATION_STORAGE_INDEX); - if (legacyAnimationOptions.checksum == computeChecksum(reinterpret_cast(&legacyAnimationOptions), sizeof(ConfigLegacy::AnimationOptions), offsetof(ConfigLegacy::AnimationOptions, checksum))) - { - legacyConfigFound = true; - - AnimationOptions& animationOptions = config.animationOptions; - config.has_animationOptions = true; - SET_PROPERTY(animationOptions, baseAnimationIndex, legacyAnimationOptions.baseAnimationIndex); - SET_PROPERTY(animationOptions, brightness, legacyAnimationOptions.brightness); - SET_PROPERTY(animationOptions, staticColorIndex, legacyAnimationOptions.staticColorIndex); - SET_PROPERTY(animationOptions, buttonColorIndex, legacyAnimationOptions.buttonColorIndex); - SET_PROPERTY(animationOptions, chaseCycleTime, legacyAnimationOptions.chaseCycleTime); - SET_PROPERTY(animationOptions, rainbowCycleTime, legacyAnimationOptions.rainbowCycleTime); - SET_PROPERTY(animationOptions, themeIndex, legacyAnimationOptions.themeIndex); - SET_PROPERTY(animationOptions, hasCustomTheme, legacyAnimationOptions.hasCustomTheme); - SET_PROPERTY(animationOptions, customThemeUp, legacyAnimationOptions.customThemeUp); - SET_PROPERTY(animationOptions, customThemeDown, legacyAnimationOptions.customThemeDown); - SET_PROPERTY(animationOptions, customThemeLeft, legacyAnimationOptions.customThemeLeft); - SET_PROPERTY(animationOptions, customThemeRight, legacyAnimationOptions.customThemeRight); - SET_PROPERTY(animationOptions, customThemeB1, legacyAnimationOptions.customThemeB1); - SET_PROPERTY(animationOptions, customThemeB2, legacyAnimationOptions.customThemeB2); - SET_PROPERTY(animationOptions, customThemeB3, legacyAnimationOptions.customThemeB3); - SET_PROPERTY(animationOptions, customThemeB4, legacyAnimationOptions.customThemeB4); - SET_PROPERTY(animationOptions, customThemeL1, legacyAnimationOptions.customThemeL1); - SET_PROPERTY(animationOptions, customThemeR1, legacyAnimationOptions.customThemeR1); - SET_PROPERTY(animationOptions, customThemeL2, legacyAnimationOptions.customThemeL2); - SET_PROPERTY(animationOptions, customThemeR2, legacyAnimationOptions.customThemeR2); - SET_PROPERTY(animationOptions, customThemeS1, legacyAnimationOptions.customThemeS1); - SET_PROPERTY(animationOptions, customThemeS2, legacyAnimationOptions.customThemeS2); - SET_PROPERTY(animationOptions, customThemeL3, legacyAnimationOptions.customThemeL3); - SET_PROPERTY(animationOptions, customThemeR3, legacyAnimationOptions.customThemeR3); - SET_PROPERTY(animationOptions, customThemeA1, legacyAnimationOptions.customThemeA1); - SET_PROPERTY(animationOptions, customThemeA2, legacyAnimationOptions.customThemeA2); - SET_PROPERTY(animationOptions, customThemeUpPressed, legacyAnimationOptions.customThemeUpPressed); - SET_PROPERTY(animationOptions, customThemeDownPressed, legacyAnimationOptions.customThemeDownPressed); - SET_PROPERTY(animationOptions, customThemeLeftPressed, legacyAnimationOptions.customThemeLeftPressed); - SET_PROPERTY(animationOptions, customThemeRightPressed, legacyAnimationOptions.customThemeRightPressed); - SET_PROPERTY(animationOptions, customThemeB1Pressed, legacyAnimationOptions.customThemeB1Pressed); - SET_PROPERTY(animationOptions, customThemeB2Pressed, legacyAnimationOptions.customThemeB2Pressed); - SET_PROPERTY(animationOptions, customThemeB3Pressed, legacyAnimationOptions.customThemeB3Pressed); - SET_PROPERTY(animationOptions, customThemeB4Pressed, legacyAnimationOptions.customThemeB4Pressed); - SET_PROPERTY(animationOptions, customThemeL1Pressed, legacyAnimationOptions.customThemeL1Pressed); - SET_PROPERTY(animationOptions, customThemeR1Pressed, legacyAnimationOptions.customThemeR1Pressed); - SET_PROPERTY(animationOptions, customThemeL2Pressed, legacyAnimationOptions.customThemeL2Pressed); - SET_PROPERTY(animationOptions, customThemeR2Pressed, legacyAnimationOptions.customThemeR2Pressed); - SET_PROPERTY(animationOptions, customThemeS1Pressed, legacyAnimationOptions.customThemeS1Pressed); - SET_PROPERTY(animationOptions, customThemeS2Pressed, legacyAnimationOptions.customThemeS2Pressed); - SET_PROPERTY(animationOptions, customThemeL3Pressed, legacyAnimationOptions.customThemeL3Pressed); - SET_PROPERTY(animationOptions, customThemeR3Pressed, legacyAnimationOptions.customThemeR3Pressed); - SET_PROPERTY(animationOptions, customThemeA1Pressed, legacyAnimationOptions.customThemeA1Pressed); - SET_PROPERTY(animationOptions, customThemeA2Pressed, legacyAnimationOptions.customThemeA2Pressed); - } - const ConfigLegacy::AddonOptions& legacyAddonOptions = *reinterpret_cast(EEPROM_ADDRESS_START + ADDON_STORAGE_INDEX); if (legacyAddonOptions.checksum == computeChecksum(reinterpret_cast(&legacyAddonOptions), sizeof(ConfigLegacy::AddonOptions), offsetof(ConfigLegacy::AddonOptions, checksum))) { diff --git a/src/config_utils.cpp b/src/config_utils.cpp index 19d6f2b798..425f66bb63 100644 --- a/src/config_utils.cpp +++ b/src/config_utils.cpp @@ -491,7 +491,6 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config) INIT_UNSET_PROPERTY(config.ledOptions, ledLayout, BUTTON_LAYOUT); INIT_UNSET_PROPERTY(config.ledOptions, ledsPerButton, LEDS_PER_PIXEL); INIT_UNSET_PROPERTY(config.ledOptions, brightnessMaximum, LED_BRIGHTNESS_MAXIMUM); - INIT_UNSET_PROPERTY(config.ledOptions, brightnessSteps, LED_BRIGHTNESS_STEPS); INIT_UNSET_PROPERTY(config.ledOptions, turnOffWhenSuspended, LEDS_TURN_OFF_WHEN_SUSPENDED); INIT_UNSET_PROPERTY(config.ledOptions, indexUp, LEDS_DPAD_UP); @@ -526,67 +525,50 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config) INIT_UNSET_PROPERTY(config.ledOptions, pledIndex2, PLED2_PIN); INIT_UNSET_PROPERTY(config.ledOptions, pledIndex3, PLED3_PIN); INIT_UNSET_PROPERTY(config.ledOptions, pledIndex4, PLED4_PIN); + // lightEntries + INIT_UNSET_PROPERTY(config.ledOptions, lightClusterDataInitialised, false); + + // animationOptions + if(LEDS_BRIGHTNESS >= 0 && LEDS_BRIGHTNESS <= AnimationStation::brightnessSteps) + { + INIT_UNSET_PROPERTY(config.animationOptions, brightness, LEDS_BRIGHTNESS); + } + else + { + INIT_UNSET_PROPERTY(config.animationOptions, brightness, AnimationStation::brightnessSteps); + } + INIT_UNSET_PROPERTY(config.animationOptions, baseProfileIndex, 0); + INIT_UNSET_PROPERTY(config.animationOptions, autoDisableTime, 15000); - INIT_UNSET_PROPERTY(config.ledOptions, caseRGBType, CASE_RGB_TYPE); - INIT_UNSET_PROPERTY(config.ledOptions, caseRGBIndex, CASE_RGB_INDEX); - INIT_UNSET_PROPERTY(config.ledOptions, caseRGBCount, CASE_RGB_COUNT); + //Default to rainbow rotate if a fresh settings + if(config.animationOptions.profiles[0].has_bEnabled == false) + { + INIT_UNSET_PROPERTY(config.animationOptions.profiles[0], bEnabled, 1); + INIT_UNSET_PROPERTY(config.animationOptions.profiles[0], basePressedCycleTime, 100); + INIT_UNSET_PROPERTY(config.animationOptions.profiles[0], baseCycleTime, 100); + config.animationOptions.profiles_count = 1; + config.animationOptions.profiles[0].notPressedStaticColors_count = (NUM_BANK0_GPIOS/4)+1; + config.animationOptions.profiles[0].pressedStaticColors_count = 0; + for (unsigned int lightIndex = 0; lightIndex < (NUM_BANK0_GPIOS/4)+1; ++lightIndex) + { + config.animationOptions.profiles[0].notPressedStaticColors[lightIndex] = 0; //Black + config.animationOptions.profiles[0].pressedStaticColors[lightIndex] = 0; //Black + } + config.animationOptions.profiles[0].caseStaticColors_count = 0; + config.animationOptions.profiles[0].baseNonPressedEffect = AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_RAINBOW_ROTATE; + config.animationOptions.profiles[0].basePressedEffect = AnimationPressedEffects::AnimationPressedEffects_PRESSEDEFFECT_STATIC_COLOR; + config.animationOptions.profiles[0].baseCaseEffect = AnimationNonPressedEffects::AnimationNonPressedEffects_EFFECT_RAINBOW_ROTATE; + INIT_UNSET_PROPERTY(config.animationOptions.profiles[0], bUseCaseLightsInPressedAnimations, 1); + } - // animationOptions - INIT_UNSET_PROPERTY(config.animationOptions, baseAnimationIndex, LEDS_BASE_ANIMATION_INDEX); - INIT_UNSET_PROPERTY(config.animationOptions, brightness, LEDS_BRIGHTNESS); - INIT_UNSET_PROPERTY(config.animationOptions, staticColorIndex, LEDS_STATIC_COLOR_INDEX); - INIT_UNSET_PROPERTY(config.animationOptions, buttonColorIndex, LEDS_BUTTON_COLOR_INDEX); - INIT_UNSET_PROPERTY(config.animationOptions, chaseCycleTime, LEDS_CHASE_CYCLE_TIME); - INIT_UNSET_PROPERTY(config.animationOptions, rainbowCycleTime, LEDS_RAINBOW_CYCLE_TIME); - INIT_UNSET_PROPERTY(config.animationOptions, themeIndex, LEDS_THEME_INDEX); - INIT_UNSET_PROPERTY(config.animationOptions, hasCustomTheme, false); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeUp, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeDown, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeLeft, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeRight, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeB1, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeB2, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeB3, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeB4, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeL1, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeR1, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeL2, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeR2, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeS1, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeS2, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeL3, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeR3, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeA1, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeA2, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeUpPressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeDownPressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeLeftPressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeRightPressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeB1Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeB2Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeB3Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeB4Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeL1Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeR1Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeL2Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeR2Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeS1Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeS2Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeL3Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeR3Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeA1Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, customThemeA2Pressed, 0); - INIT_UNSET_PROPERTY(config.animationOptions, buttonPressColorCooldownTimeInMs, LEDS_PRESS_COLOR_COOLDOWN_TIME); - INIT_UNSET_PROPERTY(config.animationOptions, ambientLightEffectsCountIndex, AMBIENT_LIGHT_EFFECT); - INIT_UNSET_PROPERTY(config.animationOptions, alStaticColorBrightnessCustomX, AMBIENT_STATIC_COLOR_BRIGHTNESS); - INIT_UNSET_PROPERTY(config.animationOptions, alGradientBrightnessCustomX, AMBIENT_GRADIENT_COLOR_BRIGHTNESS); - INIT_UNSET_PROPERTY(config.animationOptions, alChaseBrightnessCustomX, AMBIENT_CHASE_COLOR_BRIGHTNESS); - INIT_UNSET_PROPERTY(config.animationOptions, alStaticBrightnessCustomThemeX, AMBIENT_CUSTOM_THEME_BRIGHTNESS); - INIT_UNSET_PROPERTY(config.animationOptions, ambientLightGradientSpeed, AMBIENT_GRADIENT_SPEED); - INIT_UNSET_PROPERTY(config.animationOptions, ambientLightChaseSpeed, AMBIENT_CHASE_SPEED); - INIT_UNSET_PROPERTY(config.animationOptions, ambientLightBreathSpeed, AMBIENT_BREATH_SPEED); - INIT_UNSET_PROPERTY(config.animationOptions, alCustomStaticThemeIndex, AMBIENT_CUSTOM_THEME); - INIT_UNSET_PROPERTY(config.animationOptions, alCustomStaticColorIndex, AMBIENT_STATIC_COLOR); + //Since we force a profile 0 on new settings we only need to now force disable profiles 1 to max + for (unsigned int profileIndex = 1; profileIndex < MAX_ANIMATION_PROFILES; ++profileIndex) + { + INIT_UNSET_PROPERTY(config.animationOptions.profiles[profileIndex], bEnabled, 0); + INIT_UNSET_PROPERTY(config.animationOptions.profiles[profileIndex], basePressedCycleTime, 0); + INIT_UNSET_PROPERTY(config.animationOptions.profiles[profileIndex], baseCycleTime, 0); + INIT_UNSET_PROPERTY(config.animationOptions.profiles[profileIndex], bUseCaseLightsInPressedAnimations, 0); + } // addonOptions.bootselButtonOptions INIT_UNSET_PROPERTY(config.addonOptions.bootselButtonOptions, enabled, !!BOOTSEL_BUTTON_ENABLED); diff --git a/src/display/ui/screens/ButtonLayoutScreen.cpp b/src/display/ui/screens/ButtonLayoutScreen.cpp index 41ebd293b2..a2062bfb3d 100644 --- a/src/display/ui/screens/ButtonLayoutScreen.cpp +++ b/src/display/ui/screens/ButtonLayoutScreen.cpp @@ -10,7 +10,6 @@ void ButtonLayoutScreen::init() { inputHistoryX = Storage::getInstance().getDisplayOptions().inputHistoryRow; inputHistoryY = Storage::getInstance().getDisplayOptions().inputHistoryCol; inputHistoryLength = Storage::getInstance().getDisplayOptions().inputHistoryLength; - bannerDelayStart = getMillis(); gamepad = Storage::getInstance().GetGamepad(); inputMode = DriverManager::getInstance().getInputMode(); @@ -35,10 +34,9 @@ void ButtonLayoutScreen::init() { pushElement(currLayoutRight[elementCtr]); } - // start with profile mode displayed - bannerDisplay = true; - prevProfileNumber = -1; - + // get current profile number. Future changes are communicated by event + gamePadProfileNumber = (int16_t)(getGamepad()->getOptions().profileNumber); + prevLayoutLeft = Storage::getInstance().getDisplayOptions().buttonLayout; prevLayoutRight = Storage::getInstance().getDisplayOptions().buttonLayoutRight; prevLeftOptions = Storage::getInstance().getDisplayOptions().buttonLayoutCustomOptions.paramsLeft; @@ -85,15 +83,68 @@ void ButtonLayoutScreen::init() { void ButtonLayoutScreen::shutdown() { clearElements(); +} + +void ButtonLayoutScreen::addCustomHeader(std::string newStr, std::string identifier){ + for(unsigned int index = 0; index < bannerIdentifier.size(); ++index) + { + if(bannerIdentifier[index].compare(identifier) == 0) + { + bannerDelayStart = getMillis(); + bannerString[index] = newStr; + return; + } + } + + if(bannerString.size() == 0) + bannerDelayStart = getMillis(); + + bannerString.push_back(newStr); + bannerIdentifier.push_back(identifier); +} + +void ButtonLayoutScreen::updateCustomHeaders() +{ + Storage& storage = Storage::getInstance(); + + // Check to see if gamepad profile has changed + if (prevGamepadProfileNumber != gamePadProfileNumber) { + prevGamepadProfileNumber = gamePadProfileNumber; + + std::string profileStr; + profileStr.assign(storage.currentProfileLabel(), strlen(storage.currentProfileLabel())); + if (profileStr.empty()) { + profileStr = " Profile #"; + profileStr += std::to_string(gamePadProfileNumber); + } else { + profileStr.insert(profileStr.begin(), (21-profileStr.length())/2, ' '); + } - EventManager::getInstance().unregisterEventHandler(GP_EVENT_PROFILE_CHANGE, GPEVENT_CALLBACK(this->handleProfileChange(event))); - EventManager::getInstance().unregisterEventHandler(GP_EVENT_USBHOST_MOUNT, GPEVENT_CALLBACK(this->handleUSB(event))); - EventManager::getInstance().unregisterEventHandler(GP_EVENT_USBHOST_UNMOUNT, GPEVENT_CALLBACK(this->handleUSB(event))); + addCustomHeader(profileStr, "profile"); + } + + // Check to see if LED animation profile has changed + int8_t profileNumber = AnimationStation::options.baseProfileIndex; + if (prevLEDAnimationProfileNumber != profileNumber) { + prevLEDAnimationProfileNumber = profileNumber; + + std::string profileStr; + if(profileNumber != -1) + { + profileStr = " LED Profile #"; + profileStr += std::to_string(profileNumber+1); //add 1 so its from 1-x not from 0-x + } + else + { + profileStr = " LED Profile OFF"; + } + + addCustomHeader(profileStr, "led"); + } } int8_t ButtonLayoutScreen::update() { bool configMode = DriverManager::getInstance().isConfigMode(); - uint8_t profileNumber = getGamepad()->getOptions().profileNumber; // Check if we've updated button layouts while in config mode if (configMode) { @@ -107,12 +158,7 @@ int8_t ButtonLayoutScreen::update() { } } - // main logic loop - if (prevProfileNumber != profileNumber) { - bannerDelayStart = getMillis(); - prevProfileNumber = profileNumber; - bannerDisplay = true; - } + updateCustomHeaders(); // main logic loop generateHeader(); @@ -139,25 +185,28 @@ void ButtonLayoutScreen::generateHeader() { statusBar.clear(); Storage& storage = Storage::getInstance(); - // Display Profile # banner - if ( bannerDisplay ) { - if (((getMillis() - bannerDelayStart) / 1000) < bannerDelay) { - if (bannerMessage.empty()) { - statusBar.assign(storage.currentProfileLabel(), strlen(storage.currentProfileLabel())); - if (statusBar.empty()) { - statusBar = " Profile #"; - statusBar += std::to_string(getGamepad()->getOptions().profileNumber); - } else { - statusBar.insert(statusBar.begin(), (21-statusBar.length())/2, ' '); - } - } else { - statusBar = bannerMessage; - } - return; - } else { - bannerDisplay = false; - bannerMessage.clear(); + // Display Profile # banner + if ( bannerString.size() ) { + if (!inbetweenBanners) + { + if(((getMillis() - bannerDelayStart) / 1000) < bannerDelay) { + statusBar = bannerString[0]; + } else { + bannerString.pop_front(); + bannerIdentifier.pop_front(); + if( bannerString.size() ) { + inbetweenBanners = true; + bannerDelayStart = getMillis(); + } + } + } else if (inbetweenBanners){ + if (((float)(getMillis() - bannerDelayStart) / 1000.0f) > inbetweenBannerDelay) { + inbetweenBanners = false; + bannerDelayStart = getMillis(); + } } + + return; } if (showInputMode) { @@ -220,8 +269,6 @@ void ButtonLayoutScreen::generateHeader() { } } - const GamepadOptions & options = gamepad->getOptions(); - if (showDpadMode) { switch (gamepad->getActiveDpadMode()) { @@ -260,7 +307,7 @@ void ButtonLayoutScreen::generateHeader() { } void ButtonLayoutScreen::drawScreen() { - if (bannerDisplay) { + if (bannerString.size() > 0) { getRenderer()->drawRectangle(0, 0, 128, 7, true, true); getRenderer()->drawText(0, 0, statusBar, true); } else { @@ -538,21 +585,22 @@ bool ButtonLayoutScreen::pressedDownRight() void ButtonLayoutScreen::handleProfileChange(GPEvent* e) { GPProfileChangeEvent* event = (GPProfileChangeEvent*)e; - profileNumber = event->currentValue; - prevProfileNumber = event->previousValue; + gamePadProfileNumber = event->currentValue; + prevGamepadProfileNumber = event->previousValue; } void ButtonLayoutScreen::handleUSB(GPEvent* e) { - GPUSBHostEvent* event = (GPUSBHostEvent*)e; - bannerDelayStart = getMillis(); - prevProfileNumber = profileNumber; + //GPUSBHostEvent* event = (GPUSBHostEvent*)e; + + std::string customBannerStr; if (e->eventType() == GP_EVENT_USBHOST_MOUNT) { - bannerMessage = " USB Connected"; + customBannerStr = " USB Connected"; } else if (e->eventType() == GP_EVENT_USBHOST_UNMOUNT) { - bannerMessage = " USB Disconnnected"; + customBannerStr = " USB Disconnnected"; } - bannerDisplay = true; + + addCustomHeader(customBannerStr, "USB"); } void ButtonLayoutScreen::trim(std::string &s) { diff --git a/src/storagemanager.cpp b/src/storagemanager.cpp index 93cfaec7e7..acbe09b82a 100644 --- a/src/storagemanager.cpp +++ b/src/storagemanager.cpp @@ -6,7 +6,6 @@ #include "storagemanager.h" #include "BoardConfig.h" -#include "animationstorage.h" #include "FlashPROM.h" #include "drivermanager.h" #include "eventmanager.h" diff --git a/src/webconfig.cpp b/src/webconfig.cpp index 0cfe1a8bb5..d7c796824d 100644 --- a/src/webconfig.cpp +++ b/src/webconfig.cpp @@ -6,12 +6,13 @@ #include "eventmanager.h" #include "layoutmanager.h" #include "peripheralmanager.h" -#include "animationstorage.h" #include "system.h" #include "config_utils.h" #include "types.h" #include "version.h" +#include "neopicoleds.h" + #include #include #include @@ -40,7 +41,7 @@ extern struct fsdata_file file__index_html[]; -const static char* spaPaths[] = { "/backup", "/display-config", "/led-config", "/pin-mapping", "/settings", "/reset-settings", "/add-ons", "/custom-theme", "/macro", "/peripheral-mapping" }; +const static char* spaPaths[] = { "/animation", "/backup", "/custom-theme", "/display-config", "/led-config", "/pin-mapping", "/settings", "/reset-settings", "/add-ons", "/macro", "/peripheral-mapping" }; const static char* excludePaths[] = { "/css", "/images", "/js", "/static" }; const static uint32_t rebootDelayMs = 500; static string http_post_uri; @@ -792,42 +793,17 @@ std::string getGamepadOptions() std::string setLedOptions() { DynamicJsonDocument doc = get_post_data(); - - const auto readIndex = [&](int32_t& var, const char* key0, const char* key1) - { - var = -1; - if (hasValue(doc, key0, key1)) - { - readDoc(var, doc, key0, key1); - } - }; - LEDOptions& ledOptions = Storage::getInstance().getLedOptions(); + docToPin(ledOptions.dataPin, doc, "dataPin"); readDoc(ledOptions.ledFormat, doc, "ledFormat"); - readDoc(ledOptions.ledLayout, doc, "ledLayout"); - readDoc(ledOptions.ledsPerButton, doc, "ledsPerButton"); - readDoc(ledOptions.brightnessMaximum, doc, "brightnessMaximum"); - readDoc(ledOptions.brightnessSteps, doc, "brightnessSteps"); readDoc(ledOptions.turnOffWhenSuspended, doc, "turnOffWhenSuspended"); - readIndex(ledOptions.indexUp, "ledButtonMap", "Up"); - readIndex(ledOptions.indexDown, "ledButtonMap", "Down"); - readIndex(ledOptions.indexLeft, "ledButtonMap", "Left"); - readIndex(ledOptions.indexRight, "ledButtonMap", "Right"); - readIndex(ledOptions.indexB1, "ledButtonMap", "B1"); - readIndex(ledOptions.indexB2, "ledButtonMap", "B2"); - readIndex(ledOptions.indexB3, "ledButtonMap", "B3"); - readIndex(ledOptions.indexB4, "ledButtonMap", "B4"); - readIndex(ledOptions.indexL1, "ledButtonMap", "L1"); - readIndex(ledOptions.indexR1, "ledButtonMap", "R1"); - readIndex(ledOptions.indexL2, "ledButtonMap", "L2"); - readIndex(ledOptions.indexR2, "ledButtonMap", "R2"); - readIndex(ledOptions.indexS1, "ledButtonMap", "S1"); - readIndex(ledOptions.indexS2, "ledButtonMap", "S2"); - readIndex(ledOptions.indexL3, "ledButtonMap", "L3"); - readIndex(ledOptions.indexR3, "ledButtonMap", "R3"); - readIndex(ledOptions.indexA1, "ledButtonMap", "A1"); - readIndex(ledOptions.indexA2, "ledButtonMap", "A2"); + + readDoc(ledOptions.brightnessMaximum, doc, "brightnessMaximum"); + uint32_t checkedBrightnessMax = std::clamp(ledOptions.brightnessMaximum, 0, 100); + ledOptions.brightnessMaximum = int(((float)checkedBrightnessMax * 2.55f) + + 0.5f); //+0.5 to cause it to round to nearest number + ledOptions.brightnessMaximum = std::clamp(ledOptions.brightnessMaximum, 0, 255); + readDoc(ledOptions.pledType, doc, "pledType"); docToPin(ledOptions.pledPin1, doc, "pledPin1"); docToPin(ledOptions.pledPin2, doc, "pledPin2"); @@ -838,9 +814,6 @@ std::string setLedOptions() readDoc(ledOptions.pledIndex3, doc, "pledIndex3"); readDoc(ledOptions.pledIndex4, doc, "pledIndex4"); readDoc(ledOptions.pledColor, doc, "pledColor"); - readDoc(ledOptions.caseRGBType, doc, "caseRGBType"); - readDoc(ledOptions.caseRGBIndex, doc, "caseRGBIndex"); - readDoc(ledOptions.caseRGBCount, doc, "caseRGBCount"); EventManager::getInstance().triggerEvent(new GPStorageSaveEvent(true)); return serialize_json(doc); @@ -853,41 +826,12 @@ std::string getLedOptions() const LEDOptions& ledOptions = Storage::getInstance().getLedOptions(); writeDoc(doc, "dataPin", cleanPin(ledOptions.dataPin)); writeDoc(doc, "ledFormat", ledOptions.ledFormat); - writeDoc(doc, "ledLayout", ledOptions.ledLayout); - writeDoc(doc, "ledsPerButton", ledOptions.ledsPerButton); - writeDoc(doc, "brightnessMaximum", ledOptions.brightnessMaximum); - writeDoc(doc, "brightnessSteps", ledOptions.brightnessSteps); writeDoc(doc, "turnOffWhenSuspended", ledOptions.turnOffWhenSuspended); - const auto writeIndex = [&](const char* key0, const char* key1, int var) - { - if (var < 0) - { - writeDoc(doc, key0, key1, nullptr); - } - else - { - writeDoc(doc, key0, key1, var); - } - }; - writeIndex("ledButtonMap", "Up", ledOptions.indexUp); - writeIndex("ledButtonMap", "Down", ledOptions.indexDown); - writeIndex("ledButtonMap", "Left", ledOptions.indexLeft); - writeIndex("ledButtonMap", "Right", ledOptions.indexRight); - writeIndex("ledButtonMap", "B1", ledOptions.indexB1); - writeIndex("ledButtonMap", "B2", ledOptions.indexB2); - writeIndex("ledButtonMap", "B3", ledOptions.indexB3); - writeIndex("ledButtonMap", "B4", ledOptions.indexB4); - writeIndex("ledButtonMap", "L1", ledOptions.indexL1); - writeIndex("ledButtonMap", "R1", ledOptions.indexR1); - writeIndex("ledButtonMap", "L2", ledOptions.indexL2); - writeIndex("ledButtonMap", "R2", ledOptions.indexR2); - writeIndex("ledButtonMap", "S1", ledOptions.indexS1); - writeIndex("ledButtonMap", "S2", ledOptions.indexS2); - writeIndex("ledButtonMap", "L3", ledOptions.indexL3); - writeIndex("ledButtonMap", "R3", ledOptions.indexR3); - writeIndex("ledButtonMap", "A1", ledOptions.indexA1); - writeIndex("ledButtonMap", "A2", ledOptions.indexA2); + uint32_t adjustedbrightnessMax = (uint32_t)(((float)ledOptions.brightnessMaximum / 2.55f) + 0.5f); //+0.5 to cause it to round to nearest number + adjustedbrightnessMax = std::clamp(adjustedbrightnessMax, 0, 100); + writeDoc(doc, "brightnessMaximum", adjustedbrightnessMax); + writeDoc(doc, "pledType", ledOptions.pledType); writeDoc(doc, "pledPin1", ledOptions.pledPin1); writeDoc(doc, "pledPin2", ledOptions.pledPin2); @@ -898,9 +842,6 @@ std::string getLedOptions() writeDoc(doc, "pledIndex3", ledOptions.pledIndex3); writeDoc(doc, "pledIndex4", ledOptions.pledIndex4); writeDoc(doc, "pledColor", ((RGB)ledOptions.pledColor).value(LED_FORMAT_RGB)); - writeDoc(doc, "caseRGBType", ledOptions.caseRGBType); - writeDoc(doc, "caseRGBIndex", ledOptions.caseRGBIndex); - writeDoc(doc, "caseRGBCount", ledOptions.caseRGBCount); return serialize_json(doc); } @@ -928,33 +869,12 @@ std::string getButtonLayouts() { const size_t capacity = JSON_OBJECT_SIZE(500); DynamicJsonDocument doc(capacity); - const LEDOptions& ledOptions = Storage::getInstance().getLedOptions(); const DisplayOptions& displayOptions = Storage::getInstance().getDisplayOptions(); uint16_t elementCtr = 0; LayoutManager::LayoutList layoutA = LayoutManager::getInstance().getLayoutA(); LayoutManager::LayoutList layoutB = LayoutManager::getInstance().getLayoutB(); - writeDoc(doc, "ledLayout", "id", ledOptions.ledLayout); - writeDoc(doc, "ledLayout", "indexUp", ledOptions.indexUp); - writeDoc(doc, "ledLayout", "indexDown", ledOptions.indexDown); - writeDoc(doc, "ledLayout", "indexLeft", ledOptions.indexLeft); - writeDoc(doc, "ledLayout", "indexRight", ledOptions.indexRight); - writeDoc(doc, "ledLayout", "indexB1", ledOptions.indexB1); - writeDoc(doc, "ledLayout", "indexB2", ledOptions.indexB2); - writeDoc(doc, "ledLayout", "indexB3", ledOptions.indexB3); - writeDoc(doc, "ledLayout", "indexB4", ledOptions.indexB4); - writeDoc(doc, "ledLayout", "indexL1", ledOptions.indexL1); - writeDoc(doc, "ledLayout", "indexR1", ledOptions.indexR1); - writeDoc(doc, "ledLayout", "indexL2", ledOptions.indexL2); - writeDoc(doc, "ledLayout", "indexR2", ledOptions.indexR2); - writeDoc(doc, "ledLayout", "indexS1", ledOptions.indexS1); - writeDoc(doc, "ledLayout", "indexS2", ledOptions.indexS2); - writeDoc(doc, "ledLayout", "indexL3", ledOptions.indexL3); - writeDoc(doc, "ledLayout", "indexR3", ledOptions.indexR3); - writeDoc(doc, "ledLayout", "indexA1", ledOptions.indexA1); - writeDoc(doc, "ledLayout", "indexA2", ledOptions.indexA2); - writeDoc(doc, "displayLayouts", "buttonLayoutId", displayOptions.buttonLayout); for (elementCtr = 0; elementCtr < layoutA.size(); elementCtr++) { const size_t elementSize = JSON_OBJECT_SIZE(12); @@ -998,112 +918,385 @@ std::string getButtonLayouts() return serialize_json(doc); } -std::string setCustomTheme() +std::string setLightsDataOptions() { DynamicJsonDocument doc = get_post_data(); - AnimationOptions & options = Storage::getInstance().getAnimationOptions(); + LEDOptions& options = Storage::getInstance().getLedOptions(); - const auto readDocDefaultToZero = [&](const char* key0, const char* key1) -> uint32_t + JsonObject docJson = doc.as(); + JsonObject AnimOptions = docJson["LightData"]; + JsonArray lightsList = AnimOptions["Lights"]; + options.lightClusterData_count = 0; + options.lightClusterDataInitialised = true; + for (JsonObject light : lightsList) { - uint32_t result = 0; - if (hasValue(doc, key0, key1)) - { - readDoc(result, doc, key0, key1); + int thisEntryIndex = options.lightClusterData_count; + options.lightClusterData[thisEntryIndex].lightLocationData = light["firstLedIndex"].as(); + options.lightClusterData[thisEntryIndex].lightLocationData += ((int)light["numLedsOnLight"].as()) << 8; + options.lightClusterData[thisEntryIndex].lightLocationData += ((int)light["xCoord"].as()) << 16; + options.lightClusterData[thisEntryIndex].lightLocationData += ((int)light["yCoord"].as()) << 24; + options.lightClusterData[thisEntryIndex].lightTypeData = light["GPIOPinorCaseChainIndex"].as(); + options.lightClusterData[thisEntryIndex].lightTypeData += ((int)light["lightType"].as()) << 8; + + options.lightClusterData_count++; + + if(options.lightClusterData_count >= FRAME_MAX) //100 entries total + break; + } + + NeoPicoLEDAddon::RestartLedSystem(); + + EventManager::getInstance().triggerEvent(new GPStorageSaveEvent(true)); + return serialize_json(doc); +} + +std::string getLightsDataOptions() +{ + DynamicJsonDocument doc(LWIP_HTTPD_POST_MAX_PAYLOAD_LEN); + const LEDOptions& options = Storage::getInstance().getLedOptions(); + + JsonObject LedOptions = doc.createNestedObject("LightData"); + JsonArray lightsList = LedOptions.createNestedArray("Lights"); + for (int lightsIndex = 0; lightsIndex < options.lightClusterData_count; ++lightsIndex) + { + JsonObject light = lightsList.createNestedObject(); + light["firstLedIndex"] = options.lightClusterData[lightsIndex].lightLocationData & 0xFF; + light["numLedsOnLight"] = (options.lightClusterData[lightsIndex].lightLocationData >> 8) & 0xFF; + light["xCoord"] = (options.lightClusterData[lightsIndex].lightLocationData >> 16) & 0xFF; + light["yCoord"] = (options.lightClusterData[lightsIndex].lightLocationData >> 24) & 0xFF; + light["GPIOPinorCaseChainIndex"] = options.lightClusterData[lightsIndex].lightTypeData; + light["lightType"] = (options.lightClusterData[lightsIndex].lightTypeData >> 8) & 0xFF; + } + + return serialize_json(doc); +} + +std::string getLightsDataPresets() +{ + DynamicJsonDocument outDoc(LWIP_HTTPD_POST_MAX_PAYLOAD_LEN); + JsonArray presetsArray = outDoc.to(); + + auto addPreset = [&](const char* name, const unsigned char* data, int32_t dataSize) + { + if (strcmp(name, "") != 0) { + JsonObject preset = presetsArray.createNestedObject(); + preset["name"] = name; + + JsonObject lightDataObj = preset.createNestedObject("lightData"); + JsonArray lightsList = lightDataObj.createNestedArray("Lights"); + + for (int lightsIndex = 0; lightsIndex < dataSize; ++lightsIndex) + { + int thisEntryIndex = lightsIndex * 6; + JsonObject light = lightsList.createNestedObject(); + light["firstLedIndex"] = data[thisEntryIndex]; + light["numLedsOnLight"] = data[thisEntryIndex+1]; + light["xCoord"] = data[thisEntryIndex+2]; + light["yCoord"] = data[thisEntryIndex+3]; + light["GPIOPinorCaseChainIndex"] = data[thisEntryIndex+4]; + light["lightType"] = data[thisEntryIndex+5]; + } } - return result; }; - readDoc(options.hasCustomTheme, doc, "enabled"); - options.customThemeUp = readDocDefaultToZero("Up", "u"); - options.customThemeDown = readDocDefaultToZero("Down", "u"); - options.customThemeLeft = readDocDefaultToZero("Left", "u"); - options.customThemeRight = readDocDefaultToZero("Right", "u"); - options.customThemeB1 = readDocDefaultToZero("B1", "u"); - options.customThemeB2 = readDocDefaultToZero("B2", "u"); - options.customThemeB3 = readDocDefaultToZero("B3", "u"); - options.customThemeB4 = readDocDefaultToZero("B4", "u"); - options.customThemeL1 = readDocDefaultToZero("L1", "u"); - options.customThemeR1 = readDocDefaultToZero("R1", "u"); - options.customThemeL2 = readDocDefaultToZero("L2", "u"); - options.customThemeR2 = readDocDefaultToZero("R2", "u"); - options.customThemeS1 = readDocDefaultToZero("S1", "u"); - options.customThemeS2 = readDocDefaultToZero("S2", "u"); - options.customThemeL3 = readDocDefaultToZero("L3", "u"); - options.customThemeR3 = readDocDefaultToZero("R3", "u"); - options.customThemeA1 = readDocDefaultToZero("A1", "u"); - options.customThemeA2 = readDocDefaultToZero("A2", "u"); - options.customThemeUpPressed = readDocDefaultToZero("Up", "d"); - options.customThemeDownPressed = readDocDefaultToZero("Down", "d"); - options.customThemeLeftPressed = readDocDefaultToZero("Left", "d"); - options.customThemeRightPressed = readDocDefaultToZero("Right", "d"); - options.customThemeB1Pressed = readDocDefaultToZero("B1", "d"); - options.customThemeB2Pressed = readDocDefaultToZero("B2", "d"); - options.customThemeB3Pressed = readDocDefaultToZero("B3", "d"); - options.customThemeB4Pressed = readDocDefaultToZero("B4", "d"); - options.customThemeL1Pressed = readDocDefaultToZero("L1", "d"); - options.customThemeR1Pressed = readDocDefaultToZero("R1", "d"); - options.customThemeL2Pressed = readDocDefaultToZero("L2", "d"); - options.customThemeR2Pressed = readDocDefaultToZero("R2", "d"); - options.customThemeS1Pressed = readDocDefaultToZero("S1", "d"); - options.customThemeS2Pressed = readDocDefaultToZero("S2", "d"); - options.customThemeL3Pressed = readDocDefaultToZero("L3", "d"); - options.customThemeR3Pressed = readDocDefaultToZero("R3", "d"); - options.customThemeA1Pressed = readDocDefaultToZero("A1", "d"); - options.customThemeA2Pressed = readDocDefaultToZero("A2", "d"); - - uint32_t pressCooldown = 0; - readDoc(pressCooldown, doc, "buttonPressColorCooldownTimeInMs"); - options.buttonPressColorCooldownTimeInMs = pressCooldown; + if(strcmp(LIGHT_DATA_NAME_DEFAULT, "") != 0) { + const unsigned char lightData[] = { LIGHT_DATA_DEFAULT }; + addPreset(LIGHT_DATA_NAME_DEFAULT, lightData, LIGHT_DATA_SIZE_DEFAULT); + } + if(strcmp(LIGHT_DATA_NAME_1, "") != 0) { + const unsigned char lightData[] = { LIGHT_DATA_1 }; + addPreset(LIGHT_DATA_NAME_1, lightData, LIGHT_DATA_SIZE_1); + } + if(strcmp(LIGHT_DATA_NAME_2, "") != 0) { + const unsigned char lightData[] = { LIGHT_DATA_2 }; + addPreset(LIGHT_DATA_NAME_2, lightData, LIGHT_DATA_SIZE_2); + } + if(strcmp(LIGHT_DATA_NAME_3, "") != 0) { + const unsigned char lightData[] = { LIGHT_DATA_3 }; + addPreset(LIGHT_DATA_NAME_3, lightData, LIGHT_DATA_SIZE_3); + } + if(strcmp(LIGHT_DATA_NAME_4, "") != 0) { + const unsigned char lightData[] = { LIGHT_DATA_4 }; + addPreset(LIGHT_DATA_NAME_4, lightData, LIGHT_DATA_SIZE_4); + } + if(strcmp(LIGHT_DATA_NAME_5, "") != 0) { + const unsigned char lightData[] = { LIGHT_DATA_5 }; + addPreset(LIGHT_DATA_NAME_5, lightData, LIGHT_DATA_SIZE_5); + } + if(strcmp(LIGHT_DATA_NAME_6, "") != 0) { + const unsigned char lightData[] = { LIGHT_DATA_6 }; + addPreset(LIGHT_DATA_NAME_6, lightData, LIGHT_DATA_SIZE_6); + } + if(strcmp(LIGHT_DATA_NAME_7, "") != 0) { + const unsigned char lightData[] = { LIGHT_DATA_7 }; + addPreset(LIGHT_DATA_NAME_7, lightData, LIGHT_DATA_SIZE_7); + } + + return serialize_json(outDoc); +} + +std::string setLightsToDefault() +{ + DynamicJsonDocument doc = get_post_data(); + + JsonObject docJson = doc.as(); + const char* resetName = docJson["ResetName"]; + + if(strcmp(resetName, LIGHT_DATA_NAME_DEFAULT) == 0) + { + const unsigned char lightData[] = { LIGHT_DATA_DEFAULT }; + NeoPicoLEDAddon::AssignLedPreset(lightData, sizeof(lightData)); + } + else if(strcmp(resetName, LIGHT_DATA_NAME_1) == 0) + { + const unsigned char lightData[] = { LIGHT_DATA_1 }; + NeoPicoLEDAddon::AssignLedPreset(lightData, sizeof(lightData)); + } + else if(strcmp(resetName, LIGHT_DATA_NAME_2) == 0) + { + const unsigned char lightData[] = { LIGHT_DATA_2 }; + NeoPicoLEDAddon::AssignLedPreset(lightData, sizeof(lightData)); + } + else if(strcmp(resetName, LIGHT_DATA_NAME_3) == 0) + { + const unsigned char lightData[] = { LIGHT_DATA_3 }; + NeoPicoLEDAddon::AssignLedPreset(lightData, sizeof(lightData)); + } + else if(strcmp(resetName, LIGHT_DATA_NAME_4) == 0) + { + const unsigned char lightData[] = { LIGHT_DATA_4 }; + NeoPicoLEDAddon::AssignLedPreset(lightData, sizeof(lightData)); + } + else if(strcmp(resetName, LIGHT_DATA_NAME_5) == 0) + { + const unsigned char lightData[] = { LIGHT_DATA_5 }; + NeoPicoLEDAddon::AssignLedPreset(lightData, sizeof(lightData)); + } + else if(strcmp(resetName, LIGHT_DATA_NAME_6) == 0) + { + const unsigned char lightData[] = { LIGHT_DATA_6 }; + NeoPicoLEDAddon::AssignLedPreset(lightData, sizeof(lightData)); + } + else if(strcmp(resetName, LIGHT_DATA_NAME_7) == 0) + { + const unsigned char lightData[] = { LIGHT_DATA_7 }; + NeoPicoLEDAddon::AssignLedPreset(lightData, sizeof(lightData)); + } + + NeoPicoLEDAddon::RestartLedSystem(); + + EventManager::getInstance().triggerEvent(new GPStorageSaveEvent(true)); + + return serialize_json(doc); +} + +void helperGetProfileFromJsonObject(AnimationProfile* Profile, JsonObject* JsonData) +{ + Profile->bEnabled = (*JsonData)["bEnabled"].as(); + if(Profile->baseNonPressedEffect != (AnimationNonPressedEffects)((*JsonData)["baseNonPressedEffect"].as())) + { + Profile->baseNonPressedEffect = (AnimationNonPressedEffects)((*JsonData)["baseNonPressedEffect"].as()); + Profile->baseCycleTime = 0; + } + if(Profile->basePressedEffect != (AnimationPressedEffects)((*JsonData)["basePressedEffect"].as())) + { + Profile->basePressedEffect = (AnimationPressedEffects)((*JsonData)["basePressedEffect"].as()); + Profile->basePressedCycleTime = 0; + } + Profile->buttonPressHoldTimeInMs = (*JsonData)["buttonPressHoldTimeInMs"].as(); + Profile->buttonPressFadeOutTimeInMs = (*JsonData)["buttonPressFadeOutTimeInMs"].as(); + Profile->nonPressedSpecialColor = (*JsonData)["nonPressedSpecialColor"].as(); + Profile->bUseCaseLightsInPressedAnimations = (*JsonData)["bUseCaseLightsInPressedAnimations"].as(); + Profile->baseCaseEffect = (AnimationNonPressedEffects)((*JsonData)["baseCaseEffect"].as()); + Profile->pressedSpecialColor = (*JsonData)["pressedSpecialColor"].as(); + + JsonArray notPressedStaticColorsList = (*JsonData)["notPressedStaticColors"]; + Profile->notPressedStaticColors_count = 0; + for(unsigned int packedPinIndex = 0; packedPinIndex < (NUM_BANK0_GPIOS/4)+1; ++packedPinIndex) + { + unsigned int pinIndex = packedPinIndex * 4; + if(pinIndex < notPressedStaticColorsList.size()) + Profile->notPressedStaticColors[packedPinIndex] = notPressedStaticColorsList[pinIndex].as() & 0xFF; + else + break; + if(pinIndex+1 < notPressedStaticColorsList.size()) + Profile->notPressedStaticColors[packedPinIndex] += ((notPressedStaticColorsList[pinIndex+1].as() & 0xFF) << 8); + if(pinIndex+2 < notPressedStaticColorsList.size()) + Profile->notPressedStaticColors[packedPinIndex] += ((notPressedStaticColorsList[pinIndex+2].as() & 0xFF) << 16); + if(pinIndex+3 < notPressedStaticColorsList.size()) + Profile->notPressedStaticColors[packedPinIndex] += ((notPressedStaticColorsList[pinIndex+3].as() & 0xFF) << 24); + Profile->notPressedStaticColors_count = packedPinIndex+1; + } + + JsonArray pressedStaticColorsList = (*JsonData)["pressedStaticColors"]; + Profile->pressedStaticColors_count = 0; + for(unsigned int packedPinIndex = 0; packedPinIndex < (NUM_BANK0_GPIOS/4)+1; ++packedPinIndex) + { + unsigned int pinIndex = packedPinIndex * 4; + if(pinIndex < pressedStaticColorsList.size()) + Profile->pressedStaticColors[packedPinIndex] = pressedStaticColorsList[pinIndex].as() & 0xFF; + else + break; + if(pinIndex+1 < pressedStaticColorsList.size()) + Profile->pressedStaticColors[packedPinIndex] += ((pressedStaticColorsList[pinIndex+1].as() & 0xFF) << 8); + if(pinIndex+2 < pressedStaticColorsList.size()) + Profile->pressedStaticColors[packedPinIndex] += ((pressedStaticColorsList[pinIndex+2].as() & 0xFF) << 16); + if(pinIndex+3 < pressedStaticColorsList.size()) + Profile->pressedStaticColors[packedPinIndex] += ((pressedStaticColorsList[pinIndex+3].as() & 0xFF) << 24); + Profile->pressedStaticColors_count = packedPinIndex+1; + } + + JsonArray caseStaticColorsList = (*JsonData)["caseStaticColors"]; + Profile->caseStaticColors_count = 0; + for(unsigned int packedPinIndex = 0; packedPinIndex < (MAX_CASE_LIGHTS/4)+1; ++packedPinIndex) + { + unsigned int pinIndex = packedPinIndex * 4; + if(pinIndex < caseStaticColorsList.size()) + Profile->caseStaticColors[packedPinIndex] = caseStaticColorsList[pinIndex].as() & 0xFF; + else + break; + if(pinIndex+1 < caseStaticColorsList.size()) + Profile->caseStaticColors[packedPinIndex] += ((caseStaticColorsList[pinIndex+1].as() & 0xFF) << 8); + if(pinIndex+2 < caseStaticColorsList.size()) + Profile->caseStaticColors[packedPinIndex] += ((caseStaticColorsList[pinIndex+2].as() & 0xFF) << 16); + if(pinIndex+3 < caseStaticColorsList.size()) + Profile->caseStaticColors[packedPinIndex] += ((caseStaticColorsList[pinIndex+3].as() & 0xFF) << 24); + Profile->caseStaticColors_count = packedPinIndex+1; + } +} + +std::string setAnimationButtonTestMode() +{ + DynamicJsonDocument doc = get_post_data(); + + JsonObject docJson = doc.as(); + JsonObject testOptions = docJson["TestData"]; + + AnimationStationTestMode testMode = (AnimationStationTestMode)(testOptions["testMode"].as()); + + AnimationProfile testAnimProfile; + if(testMode == AnimationStationTestMode::AnimationStation_TestModeProfilePreview) + { + JsonObject testProfile = testOptions["testProfile"]; + helperGetProfileFromJsonObject(&testAnimProfile, &testProfile); + } + + AnimationStation::SetTestMode(testMode, &testAnimProfile); + + return serialize_json(doc); +} + +std::string setAnimationButtonTestState() +{ + DynamicJsonDocument doc = get_post_data(); + + JsonObject docJson = doc.as(); + JsonObject testOptions = docJson["TestLight"]; + int testButton = testOptions["testID"].as(); + bool testIsCaseLight = testOptions["testIsCaseLight"].as(); + + AnimationStation::SetTestPinState(testButton, testIsCaseLight); + + return serialize_json(doc); +} + +std::string setAnimationProtoOptions() +{ + DynamicJsonDocument doc = get_post_data(); + + AnimationOptions& options = Storage::getInstance().getAnimationOptions(); + + JsonObject docJson = doc.as(); + JsonObject AnimOptions = docJson["AnimationOptions"]; + + options.brightness = AnimOptions["brightness"].as(); + options.brightness = std::clamp(options.brightness, 0, 10); + options.autoDisableTime = AnimOptions["idletimeout"].as() * 1000; + options.baseProfileIndex = AnimOptions["baseProfileIndex"].as(); + JsonArray customColorsList = AnimOptions["customColors"]; + options.customColors_count = 0; + for(unsigned int customColorsIndex = 0; customColorsIndex < customColorsList.size() && customColorsIndex < MAX_CUSTOM_COLORS; ++customColorsIndex) + { + options.customColors[customColorsIndex] = customColorsList[customColorsIndex]; + options.customColors_count = customColorsIndex+1; + } + + JsonArray profilesList = AnimOptions["profiles"]; + int profilesIndex = 0; + options.profiles_count = 0; + for (JsonObject profile : profilesList) + { + helperGetProfileFromJsonObject(&(options.profiles[profilesIndex]), &profile); + + options.profiles_count = profilesIndex+1; + + if (++profilesIndex >= MAX_ANIMATION_PROFILES) + break; + } + + NeoPicoLEDAddon::RestartLedSystem(); EventManager::getInstance().triggerEvent(new GPStorageSaveEvent(true)); return serialize_json(doc); } -std::string getCustomTheme() +std::string getAnimationProtoOptions() { - const size_t capacity = JSON_OBJECT_SIZE(100); - DynamicJsonDocument doc(capacity); + DynamicJsonDocument doc(LWIP_HTTPD_POST_MAX_PAYLOAD_LEN); const AnimationOptions& options = Storage::getInstance().getAnimationOptions(); - writeDoc(doc, "enabled", options.hasCustomTheme); - writeDoc(doc, "Up", "u", options.customThemeUp); - writeDoc(doc, "Up", "d", options.customThemeUpPressed); - writeDoc(doc, "Down", "u", options.customThemeDown); - writeDoc(doc, "Down", "d", options.customThemeDownPressed); - writeDoc(doc, "Left", "u", options.customThemeLeft); - writeDoc(doc, "Left", "d", options.customThemeLeftPressed); - writeDoc(doc, "Right", "u", options.customThemeRight); - writeDoc(doc, "Right", "d", options.customThemeRightPressed); - writeDoc(doc, "B1", "u", options.customThemeB1); - writeDoc(doc, "B1", "d", options.customThemeB1Pressed); - writeDoc(doc, "B2", "u", options.customThemeB2); - writeDoc(doc, "B2", "d", options.customThemeB2Pressed); - writeDoc(doc, "B3", "u", options.customThemeB3); - writeDoc(doc, "B3", "d", options.customThemeB3Pressed); - writeDoc(doc, "B4", "u", options.customThemeB4); - writeDoc(doc, "B4", "d", options.customThemeB4Pressed); - writeDoc(doc, "L1", "u", options.customThemeL1); - writeDoc(doc, "L1", "d", options.customThemeL1Pressed); - writeDoc(doc, "R1", "u", options.customThemeR1); - writeDoc(doc, "R1", "d", options.customThemeR1Pressed); - writeDoc(doc, "L2", "u", options.customThemeL2); - writeDoc(doc, "L2", "d", options.customThemeL2Pressed); - writeDoc(doc, "R2", "u", options.customThemeR2); - writeDoc(doc, "R2", "d", options.customThemeR2Pressed); - writeDoc(doc, "S1", "u", options.customThemeS1); - writeDoc(doc, "S1", "d", options.customThemeS1Pressed); - writeDoc(doc, "S2", "u", options.customThemeS2); - writeDoc(doc, "S2", "d", options.customThemeS2Pressed); - writeDoc(doc, "A1", "u", options.customThemeA1); - writeDoc(doc, "A1", "d", options.customThemeA1Pressed); - writeDoc(doc, "A2", "u", options.customThemeA2); - writeDoc(doc, "A2", "d", options.customThemeA2Pressed); - writeDoc(doc, "L3", "u", options.customThemeL3); - writeDoc(doc, "L3", "d", options.customThemeL3Pressed); - writeDoc(doc, "R3", "u", options.customThemeR3); - writeDoc(doc, "R3", "d", options.customThemeR3Pressed); - writeDoc(doc, "buttonPressColorCooldownTimeInMs", options.buttonPressColorCooldownTimeInMs); + uint32_t checkedBrightness = std::clamp(options.brightness, 0, AnimationStation::brightnessSteps); + + JsonObject AnimOptions = doc.createNestedObject("AnimationOptions"); + AnimOptions["brightness"] = checkedBrightness; + AnimOptions["baseProfileIndex"] = options.baseProfileIndex; + AnimOptions["idletimeout"] = (options.autoDisableTime / 1000); + JsonArray customColorsList = AnimOptions.createNestedArray("customColors"); + for (int customColorsIndex = 0; customColorsIndex < options.customColors_count; ++customColorsIndex) + { + customColorsList.add(options.customColors[customColorsIndex]); + } + + JsonArray profileList = AnimOptions.createNestedArray("profiles"); + for (int profilesIndex = 0; profilesIndex < options.profiles_count; ++profilesIndex) + { + JsonObject profile = profileList.createNestedObject(); + profile["bEnabled"] = options.profiles[profilesIndex].bEnabled ? 1 : 0; + profile["baseNonPressedEffect"] = options.profiles[profilesIndex].baseNonPressedEffect; + profile["basePressedEffect"] = options.profiles[profilesIndex].basePressedEffect; + profile["buttonPressHoldTimeInMs"] = options.profiles[profilesIndex].buttonPressHoldTimeInMs; + profile["buttonPressFadeOutTimeInMs"] = options.profiles[profilesIndex].buttonPressFadeOutTimeInMs; + profile["nonPressedSpecialColor"] = options.profiles[profilesIndex].nonPressedSpecialColor; + profile["bUseCaseLightsInPressedAnimations"] = options.profiles[profilesIndex].bUseCaseLightsInPressedAnimations ? 1 : 0; + profile["baseCaseEffect"] = options.profiles[profilesIndex].baseCaseEffect; + profile["pressedSpecialColor"] = options.profiles[profilesIndex].pressedSpecialColor; + + JsonArray notPressedStaticColorsList = profile.createNestedArray("notPressedStaticColors"); + for (int notPressedStaticColorsIndex = 0; notPressedStaticColorsIndex < options.profiles[profilesIndex].notPressedStaticColors_count; ++notPressedStaticColorsIndex) + { + notPressedStaticColorsList.add(options.profiles[profilesIndex].notPressedStaticColors[notPressedStaticColorsIndex] & 0xFF); + notPressedStaticColorsList.add((options.profiles[profilesIndex].notPressedStaticColors[notPressedStaticColorsIndex] >> 8) & 0xFF); + notPressedStaticColorsList.add((options.profiles[profilesIndex].notPressedStaticColors[notPressedStaticColorsIndex] >> 16) & 0xFF); + notPressedStaticColorsList.add((options.profiles[profilesIndex].notPressedStaticColors[notPressedStaticColorsIndex] >> 24) & 0xFF); + } + JsonArray pressedStaticColorsList = profile.createNestedArray("pressedStaticColors"); + for (int pressedStaticColorsIndex = 0; pressedStaticColorsIndex < options.profiles[profilesIndex].pressedStaticColors_count; ++pressedStaticColorsIndex) + { + pressedStaticColorsList.add(options.profiles[profilesIndex].pressedStaticColors[pressedStaticColorsIndex] & 0xFF); + pressedStaticColorsList.add((options.profiles[profilesIndex].pressedStaticColors[pressedStaticColorsIndex] >> 8) & 0xFF); + pressedStaticColorsList.add((options.profiles[profilesIndex].pressedStaticColors[pressedStaticColorsIndex] >> 16) & 0xFF); + pressedStaticColorsList.add((options.profiles[profilesIndex].pressedStaticColors[pressedStaticColorsIndex] >> 24) & 0xFF); + } + JsonArray caseStaticColorsList = profile.createNestedArray("caseStaticColors"); + for (int caseStaticColorsIndex = 0; caseStaticColorsIndex < options.profiles[profilesIndex].caseStaticColors_count; ++caseStaticColorsIndex) + { + caseStaticColorsList.add(options.profiles[profilesIndex].caseStaticColors[caseStaticColorsIndex] & 0xFF); + caseStaticColorsList.add((options.profiles[profilesIndex].caseStaticColors[caseStaticColorsIndex] >> 8) & 0xFF); + caseStaticColorsList.add((options.profiles[profilesIndex].caseStaticColors[caseStaticColorsIndex] >> 16) & 0xFF); + caseStaticColorsList.add((options.profiles[profilesIndex].caseStaticColors[caseStaticColorsIndex] >> 24) & 0xFF); + } + } return serialize_json(doc); } @@ -1446,22 +1639,22 @@ std::string setHETriggerCalibration() calibrationSelectPins[1] = doc["muxSelectPin1"]; calibrationSelectPins[2] = doc["muxSelectPin2"]; calibrationSelectPins[3] = doc["muxSelectPin3"]; - + calibrationADCPins[0] = doc["muxADCPin0"]; calibrationADCPins[1] = doc["muxADCPin1"]; calibrationADCPins[2] = doc["muxADCPin2"]; calibrationADCPins[3] = doc["muxADCPin3"]; for (int i = 0; i < 4; i++) { - if ( calibrationSelectPins[i] != -1 && - calibrationSelectPins[i] >= 0 && + if ( calibrationSelectPins[i] != -1 && + calibrationSelectPins[i] >= 0 && calibrationSelectPins[i] <= 29 ) { gpio_init(calibrationSelectPins[i]); gpio_set_dir(calibrationSelectPins[i], GPIO_OUT); gpio_put(calibrationSelectPins[i], 0); } - if ( calibrationADCPins[i] != -1 && - calibrationADCPins[i] >= 26 && + if ( calibrationADCPins[i] != -1 && + calibrationADCPins[i] >= 26 && calibrationADCPins[i] <= 29 ) { adc_gpio_init(calibrationADCPins[i]); } @@ -1541,9 +1734,9 @@ std::string getHETriggerOptions() { const size_t capacity = JSON_OBJECT_SIZE(500); DynamicJsonDocument doc(capacity); - + HETriggerInfo * heTriggers = Storage::getInstance().getAddonOptions().heTriggerOptions.triggers; - + JsonArray triggerList = doc.createNestedArray("triggers"); for(int i = 0; i < 32; i++) { JsonObject trigger = triggerList.createNestedObject(); @@ -1571,7 +1764,7 @@ std::string setHETriggerOptions() heTriggers[i].max = doc["triggers"][i]["max"]; heTriggers[i].polarity = doc["triggers"][i]["polarity"]; } - + Storage::getInstance().getAddonOptions().heTriggerOptions.triggers_count = 32; EventManager::getInstance().triggerEvent(new GPStorageSaveEvent(true)); @@ -2525,8 +2718,14 @@ static const std::pair handlerFuncs[] = { "/api/setPreviewDisplayOptions", setPreviewDisplayOptions }, { "/api/setGamepadOptions", setGamepadOptions }, { "/api/setLedOptions", setLedOptions }, - { "/api/setCustomTheme", setCustomTheme }, - { "/api/getCustomTheme", getCustomTheme }, + { "/api/setAnimationButtonTestMode", setAnimationButtonTestMode }, + { "/api/setAnimationButtonTestState", setAnimationButtonTestState }, + { "/api/setAnimationProtoOptions", setAnimationProtoOptions }, + { "/api/getAnimationProtoOptions", getAnimationProtoOptions }, + { "/api/setLightsDataOptions", setLightsDataOptions }, + { "/api/getLightsDataOptions", getLightsDataOptions }, + { "/api/getLightsDataPresets", getLightsDataPresets }, + { "/api/setLightsToDefault", setLightsToDefault }, { "/api/setPinMappings", setPinMappings }, { "/api/setProfileOptions", setProfileOptions }, { "/api/setPeripheralOptions", setPeripheralOptions }, diff --git a/www/.env b/www/.env index 10d2346dbf..ce3bfc5b4c 100644 --- a/www/.env +++ b/www/.env @@ -1,4 +1,3 @@ VITE_GP2040_BOARD=pico -VITE_GP2040_CONTROLLER=pico VITE_DEV_BASE_URL=http://localhost:8080 VITE_DEV_HOST=localhost diff --git a/www/env.d.ts b/www/env.d.ts index f538ee7624..1719185b3b 100644 --- a/www/env.d.ts +++ b/www/env.d.ts @@ -2,7 +2,6 @@ interface ImportMetaEnv { readonly VITE_GP2040_BOARD: string; - readonly VITE_GP2040_CONTROLLER: string; readonly VITE_DEV_BASE_URL: string; readonly VITE_DEV_HOST: string; } diff --git a/www/package-lock.json b/www/package-lock.json index b16bc0c271..4b8f975834 100644 --- a/www/package-lock.json +++ b/www/package-lock.json @@ -8,21 +8,20 @@ "name": "gp2040-configurator", "version": "0.1.0", "dependencies": { - "@hello-pangea/color-picker": "^3.2.2", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", "bootstrap": "^5.3.0-alpha3", "formik": "^2.2.9", "i18next": "^23.1.0", "i18next-browser-languagedetector": "^7.0.2", - "javascript-color-gradient": "^2.4.4", "jsencrypt": "^3.3.2", "lodash-es": "^4.17.21", "react": "^18.2.0", - "react-beautiful-dnd": "^13.1.1", "react-bootstrap": "^2.7.4", "react-dom": "^18.2.0", "react-i18next": "^12.3.1", "react-router-dom": "^6.10.0", - "react-select": "^5.7.5", + "react-select": "^5.10.1", "yup": "^1.1.1", "zustand": "^4.5.5" }, @@ -407,6 +406,83 @@ "node": ">=6.9.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/accessibility/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@dnd-kit/modifiers": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz", + "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/modifiers/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@emotion/babel-plugin": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", @@ -986,22 +1062,6 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" }, - "node_modules/@hello-pangea/color-picker": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@hello-pangea/color-picker/-/color-picker-3.2.2.tgz", - "integrity": "sha512-1JIWv8zh9SVDXlfzlCtzdbsRElLWGX+Ljh5RYzJKigVFroycTToP/6I7qISoeQJr3B045Uq6sZXyE/F/cKmz3g==", - "dependencies": { - "@babel/runtime": "^7.20.1", - "lodash": "^4.17.21", - "tinycolor2": "^1.4.2" - }, - "engines": { - "node": ">=14.6.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -1290,15 +1350,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", - "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1360,17 +1411,6 @@ "csstype": "^3.0.2" } }, - "node_modules/@types/react-redux": { - "version": "7.1.33", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz", - "integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==", - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, "node_modules/@types/react-transition-group": { "version": "4.4.6", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", @@ -2419,14 +2459,6 @@ "node": ">= 8" } }, - "node_modules/css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "dependencies": { - "tiny-invariant": "^1.0.6" - } - }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -4164,11 +4196,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/javascript-color-gradient": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/javascript-color-gradient/-/javascript-color-gradient-2.4.4.tgz", - "integrity": "sha512-kbt3Y1eltW4X789P1eQzCUEB5X7hFmLNpQT4bgDFMtj/cW2v+j8ms/rLSR7jndZUAP6yya4vyeZK6bRqh6yClA==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4440,11 +4467,6 @@ "node": ">= 0.6" } }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -5256,11 +5278,6 @@ } ] }, - "node_modules/raf-schd": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -5296,24 +5313,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-beautiful-dnd": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", - "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", - "dependencies": { - "@babel/runtime": "^7.9.2", - "css-box-model": "^1.2.0", - "memoize-one": "^5.1.1", - "raf-schd": "^4.0.2", - "react-redux": "^7.2.0", - "redux": "^4.0.4", - "use-memo-one": "^1.1.1" - }, - "peerDependencies": { - "react": "^16.8.5 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-bootstrap": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.7.4.tgz", @@ -5391,35 +5390,6 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, - "node_modules/react-redux": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", - "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", - "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/react-redux": "^7.1.20", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^17.0.2" - }, - "peerDependencies": { - "react": "^16.8.3 || ^17 || ^18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/react-redux/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -5460,9 +5430,10 @@ } }, "node_modules/react-select": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.0.tgz", - "integrity": "sha512-TfjLDo58XrhP6VG5M/Mi56Us0Yt8X7xD6cDybC7yoRMUNm7BGO7qk8J0TLQOua/prb8vUOtsfnXZwfm30HGsAA==", + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.1.tgz", + "integrity": "sha512-roPEZUL4aRZDx6DcsD+ZNreVl+fM8VsKn0Wtex1v4IazH60ILp5xhdlp464IsEAlJdXeD+BhDAFsBVMfvLQueA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.0", "@emotion/cache": "^11.4.0", @@ -5472,11 +5443,11 @@ "memoize-one": "^6.0.0", "prop-types": "^15.6.0", "react-transition-group": "^4.3.0", - "use-isomorphic-layout-effect": "^1.1.2" + "use-isomorphic-layout-effect": "^1.2.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react-select/node_modules/memoize-one": { @@ -5511,14 +5482,6 @@ "node": ">=8.10.0" } }, - "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", @@ -6060,21 +6023,11 @@ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" }, - "node_modules/tiny-invariant": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", - "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" - }, "node_modules/tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" - }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -6342,11 +6295,12 @@ } }, "node_modules/use-isomorphic-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "license": "MIT", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -6354,14 +6308,6 @@ } } }, - "node_modules/use-memo-one": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", - "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/use-sync-external-store": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", diff --git a/www/package.json b/www/package.json index 930846e442..5f299f2c0e 100644 --- a/www/package.json +++ b/www/package.json @@ -4,21 +4,20 @@ "private": true, "type": "module", "dependencies": { - "@hello-pangea/color-picker": "^3.2.2", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", "bootstrap": "^5.3.0-alpha3", "formik": "^2.2.9", "i18next": "^23.1.0", "i18next-browser-languagedetector": "^7.0.2", - "javascript-color-gradient": "^2.4.4", "jsencrypt": "^3.3.2", "lodash-es": "^4.17.21", "react": "^18.2.0", - "react-beautiful-dnd": "^13.1.1", "react-bootstrap": "^2.7.4", "react-dom": "^18.2.0", "react-i18next": "^12.3.1", "react-router-dom": "^6.10.0", - "react-select": "^5.7.5", + "react-select": "^5.10.1", "yup": "^1.1.1", "zustand": "^4.5.5" }, diff --git a/www/server/app.js b/www/server/app.js index 2cb25972f7..f97e0c5c2c 100644 --- a/www/server/app.js +++ b/www/server/app.js @@ -17,6 +17,247 @@ const { pico: picoController } = JSON.parse( readFileSync(path.resolve(__dirname, '../src/Data/Controllers.json'), 'utf8'), ); +const BoardLights = [ + { + name: 'TestPad/Config A12', + lightData: { + Lights: [ + { + firstLedIndex: 0, + numLedsOnLight: 1, + xCoord: 0, + yCoord: 2, + GPIOPinorCaseChainIndex: 5, + lightType: 0, + }, + { + firstLedIndex: 1, + numLedsOnLight: 1, + xCoord: 2, + yCoord: 2, + GPIOPinorCaseChainIndex: 3, + lightType: 0, + }, + { + firstLedIndex: 2, + numLedsOnLight: 1, + xCoord: 4, + yCoord: 3, + GPIOPinorCaseChainIndex: 4, + lightType: 0, + }, + { + firstLedIndex: 3, + numLedsOnLight: 1, + xCoord: 5, + yCoord: 7, + GPIOPinorCaseChainIndex: 2, + lightType: 0, + }, + { + firstLedIndex: 4, + numLedsOnLight: 1, + xCoord: 6, + yCoord: 2, + GPIOPinorCaseChainIndex: 10, + lightType: 0, + }, + { + firstLedIndex: 5, + numLedsOnLight: 1, + xCoord: 8, + yCoord: 1, + GPIOPinorCaseChainIndex: 11, + lightType: 0, + }, + { + firstLedIndex: 6, + numLedsOnLight: 1, + xCoord: 10, + yCoord: 1, + GPIOPinorCaseChainIndex: 12, + lightType: 0, + }, + { + firstLedIndex: 7, + numLedsOnLight: 1, + xCoord: 12, + yCoord: 1, + GPIOPinorCaseChainIndex: 13, + lightType: 0, + }, + { + firstLedIndex: 8, + numLedsOnLight: 1, + xCoord: 6, + yCoord: 4, + GPIOPinorCaseChainIndex: 6, + lightType: 0, + }, + { + firstLedIndex: 9, + numLedsOnLight: 1, + xCoord: 8, + yCoord: 3, + GPIOPinorCaseChainIndex: 7, + lightType: 0, + }, + { + firstLedIndex: 10, + numLedsOnLight: 1, + xCoord: 10, + yCoord: 3, + GPIOPinorCaseChainIndex: 8, + lightType: 0, + }, + { + firstLedIndex: 11, + numLedsOnLight: 1, + xCoord: 12, + yCoord: 3, + GPIOPinorCaseChainIndex: 9, + lightType: 0, + }, + ], + }, + }, + { + name: 'TestPad/Config B16', + lightData: { + Lights: [ + { + firstLedIndex: 0, + numLedsOnLight: 1, + xCoord: 0, + yCoord: 2, + GPIOPinorCaseChainIndex: 5, + lightType: 0, + }, + { + firstLedIndex: 1, + numLedsOnLight: 1, + xCoord: 2, + yCoord: 2, + GPIOPinorCaseChainIndex: 3, + lightType: 0, + }, + { + firstLedIndex: 2, + numLedsOnLight: 1, + xCoord: 4, + yCoord: 3, + GPIOPinorCaseChainIndex: 4, + lightType: 0, + }, + { + firstLedIndex: 3, + numLedsOnLight: 1, + xCoord: 6, + yCoord: 7, + GPIOPinorCaseChainIndex: 2, + lightType: 0, + }, + { + firstLedIndex: 4, + numLedsOnLight: 1, + xCoord: 6, + yCoord: 2, + GPIOPinorCaseChainIndex: 10, + lightType: 0, + }, + { + firstLedIndex: 5, + numLedsOnLight: 1, + xCoord: 8, + yCoord: 1, + GPIOPinorCaseChainIndex: 11, + lightType: 0, + }, + { + firstLedIndex: 6, + numLedsOnLight: 1, + xCoord: 10, + yCoord: 1, + GPIOPinorCaseChainIndex: 12, + lightType: 0, + }, + { + firstLedIndex: 7, + numLedsOnLight: 1, + xCoord: 12, + yCoord: 1, + GPIOPinorCaseChainIndex: 13, + lightType: 0, + }, + { + firstLedIndex: 8, + numLedsOnLight: 1, + xCoord: 6, + yCoord: 4, + GPIOPinorCaseChainIndex: 6, + lightType: 0, + }, + { + firstLedIndex: 9, + numLedsOnLight: 1, + xCoord: 8, + yCoord: 3, + GPIOPinorCaseChainIndex: 7, + lightType: 0, + }, + { + firstLedIndex: 10, + numLedsOnLight: 1, + xCoord: 10, + yCoord: 3, + GPIOPinorCaseChainIndex: 8, + lightType: 0, + }, + { + firstLedIndex: 11, + numLedsOnLight: 1, + xCoord: 12, + yCoord: 3, + GPIOPinorCaseChainIndex: 9, + lightType: 0, + }, + { + firstLedIndex: 12, + numLedsOnLight: 1, + xCoord: 3, + yCoord: 0, + GPIOPinorCaseChainIndex: 27, + lightType: 0, + }, + { + firstLedIndex: 13, + numLedsOnLight: 1, + xCoord: 6, + yCoord: 0, + GPIOPinorCaseChainIndex: 18, + lightType: 0, + }, + { + firstLedIndex: 14, + numLedsOnLight: 1, + xCoord: 8, + yCoord: 5, + GPIOPinorCaseChainIndex: 19, + lightType: 0, + }, + { + firstLedIndex: 15, + numLedsOnLight: 1, + xCoord: 3, + yCoord: 6, + GPIOPinorCaseChainIndex: 26, + lightType: 0, + }, + ], + }, + }, +]; + // Structure pin mappings to include masks and profile label const createPinMappings = ({ profileLabel = 'Profile', enabled = true }) => { let pinMappings = { profileLabel, enabled }; @@ -103,6 +344,67 @@ app.get('/api/getSplashImage', (req, res) => { return res.send(data); }); +app.get('/api/getAnimationProtoOptions', (req, res) => { + return res.send({ + AnimationOptions: { + brightness: 5, + idletimeout: 0, + baseProfileIndex: 0, + customColors: [255], + profiles: [ + { + bEnabled: 1, + baseNonPressedEffect: 1, + basePressedEffect: 0, + buttonPressHoldTimeInMs: 500, + buttonPressFadeOutTimeInMs: 500, + nonPressedSpecialColor: 0xffff00, + bUseCaseLightsInSpecialMoves: 0, + bUseCaseLightsInPressedAnimations: 0, + baseCaseEffect: 0, + pressedSpecialColor: 0, + notPressedStaticColors: [ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, + ], + pressedStaticColors: [ + 4, 6, 10, 12, 4, 6, 10, 12, 4, 6, 10, 12, 4, 6, 10, 12, 4, 6, 10, + 12, 4, 6, 10, 12, 4, 6, 10, 12, 4, 6, 10, 12, + ], + caseStaticColors: [ + 1, 5, 2, 1, 2, 9, 2, 2, 2, 2, 3, 2, 3, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 9, 2, 5, 3, 2, 2, 7, 2, 2, 2, 11, 10, 4, 2, 2, 2, + ], + }, + { + bEnabled: 1, + baseNonPressedEffect: 0, + basePressedEffect: 3, + buttonPressHoldTimeInMs: 500, + buttonPressFadeOutTimeInMs: 500, + nonPressedSpecialColor: 255, + bUseCaseLightsInSpecialMoves: 1, + bUseCaseLightsInPressedAnimations: 1, + baseCaseEffect: 0, + pressedSpecialColor: 0x80ff00, + notPressedStaticColors: [ + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, + ], + pressedStaticColors: [ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, + ], + caseStaticColors: [ + 13, 1, 1, 1, 13, 1, 1, 1, 13, 1, 1, 1, 13, 1, 1, 1, 13, 1, 1, 1, 13, + 1, 1, 1, 13, 1, 1, 1, 13, 1, 1, 1, 13, 1, 1, 1, 13, 1, 1, 1, + ], + }, + ], + }, + }); +}); + app.get('/api/getGamepadOptions', (req, res) => { return res.send({ dpadMode: 0, @@ -221,72 +523,10 @@ app.get('/api/getGamepadOptions', (req, res) => { app.get('/api/getLedOptions', (req, res) => { return res.send({ - brightnessMaximum: 255, - brightnessSteps: 5, dataPin: 22, ledFormat: 0, - ledLayout: 1, - ledsPerButton: 2, - ledButtonMap: { - Up: 3, - Down: 1, - Left: 0, - Right: 2, - B1: 8, - B2: 9, - B3: 4, - B4: 5, - L1: 7, - R1: 6, - L2: 11, - R2: 10, - S1: null, - S2: null, - L3: null, - R3: null, - A1: null, - A2: null, - }, - usedPins: Object.values(picoController), - pledType: 1, - pledPin1: 12, - pledPin2: 13, - pledPin3: 14, - pledPin4: 15, - pledIndex1: 12, - pledIndex2: 13, - pledIndex3: 14, - pledIndex4: 15, - pledColor: 65280, - caseRGBType: 0, - caseRGBIndex: -1, - caseRGBCount: 0, turnOffWhenSuspended: 0, - }); -}); - -app.get('/api/getCustomTheme', (req, res) => { - console.log('/api/getCustomTheme'); - return res.send({ - enabled: true, - Up: { u: 16711680, d: 255 }, - Down: { u: 16711680, d: 255 }, - Left: { u: 16711680, d: 255 }, - Right: { u: 16711680, d: 255 }, - B1: { u: 65280, d: 16711680 }, - B2: { u: 65280, d: 16711680 }, - B3: { u: 255, d: 65280 }, - B4: { u: 255, d: 65280 }, - L1: { u: 255, d: 65280 }, - R1: { u: 255, d: 65280 }, - L2: { u: 65280, d: 16711680 }, - R2: { u: 65280, d: 16711680 }, - S1: { u: 65535, d: 16776960 }, - S2: { u: 65535, d: 16776960 }, - L3: { u: 65416, d: 16746496 }, - R3: { u: 65416, d: 16746496 }, - A1: { u: 8913151, d: 65416 }, - A2: { u: 8913151, d: 65416 }, + brightnessMaximum: 50, }); }); @@ -579,6 +819,16 @@ app.get('/api/getAddonsOptions', (req, res) => { }); }); +app.get('/api/getLightsDataOptions', (req, res) => { + return res.send({ + LightData: BoardLights[0].lightData, + }); +}); + +app.get('/api/getLightsDataPresets', (req, res) => { + return res.send(BoardLights); +}); + app.get('/api/getExpansionPins', (req, res) => { return res.send({ pins: { @@ -609,16 +859,16 @@ app.get('/api/getExpansionPins', (req, res) => { app.get('/api/getHETriggerOptions', (req, res) => { var triggers = []; triggers.push({ action: 2, idle: 120, max: 3500, active: 1500, polarity: 0 }); - for(var i = 1; i < 32; i++) { + for (var i = 1; i < 32; i++) { triggers.push({ action: -10, idle: 100, active: 2000, max: 3500, - polarity: 0 + polarity: 0, }); } - return res.send({triggers}); + return res.send({ triggers }); }); app.get('/api/getMacroAddonOptions', (req, res) => { @@ -712,21 +962,195 @@ app.get('/api/getFirmwareVersion', (req, res) => { }); }); -app.get('/api/getButtonLayoutCustomOptions', (req, res) => { +app.get('/api/getButtonLayouts', (req, res) => { return res.send({ - params: { - layout: 2, - startX: 8, - startY: 28, - buttonRadius: 8, - buttonPadding: 2, - }, - paramsRight: { - layout: 9, - startX: 8, - startY: 28, - buttonRadius: 8, - buttonPadding: 2, + displayLayouts: { + buttonLayoutId: 27, + buttonLayout: { + 0: { + elementType: 4, + parameters: { + x1: 8, + y1: 20, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 5, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + 1: { + elementType: 4, + parameters: { + x1: 26, + y1: 20, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 3, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + 2: { + elementType: 4, + parameters: { + x1: 41, + y1: 29, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 4, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + 3: { + elementType: 4, + parameters: { + x1: 48, + y1: 53, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 2, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + }, + buttonLayoutRightId: 31, + buttonLayoutRight: { + 0: { + elementType: 4, + parameters: { + x1: 57, + y1: 20, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 10, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + 1: { + elementType: 4, + parameters: { + x1: 75, + y1: 16, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 11, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + 2: { + elementType: 4, + parameters: { + x1: 93, + y1: 16, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 12, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + 3: { + elementType: 4, + parameters: { + x1: 111, + y1: 20, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 13, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + 4: { + elementType: 4, + parameters: { + x1: 57, + y1: 38, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 6, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + 5: { + elementType: 4, + parameters: { + x1: 75, + y1: 34, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 7, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + 6: { + elementType: 4, + parameters: { + x1: 93, + y1: 34, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 8, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + 7: { + elementType: 4, + parameters: { + x1: 111, + y1: 38, + x2: 8, + y2: 8, + stroke: 1, + fill: 1, + value: 9, + shape: 0, + angleStart: 0, + angleEnd: 0, + }, + }, + }, }, }); }); @@ -858,7 +1282,7 @@ app.get('/api/abortGetHeldPins', async (req, res) => { app.post('/api/getHETriggerCalibration', (req, res) => { return res.send({ voltage: 0.0, - debug: true + debug: true, }); }); diff --git a/www/src/Addons/Turbo.tsx b/www/src/Addons/Turbo.tsx index eb033b92f5..d67a2a322c 100644 --- a/www/src/Addons/Turbo.tsx +++ b/www/src/Addons/Turbo.tsx @@ -1,18 +1,15 @@ -import { useState } from 'react'; import { useContext } from 'react'; import { useTranslation } from 'react-i18next'; import { FormCheck, Row } from 'react-bootstrap'; import * as yup from 'yup'; import { AppContext } from '../Contexts/AppContext'; -import ColorPicker from '../Components/ColorPicker'; import Section from '../Components/Section'; import FormSelect from '../Components/FormSelect'; import FormControl from '../Components/FormControl'; import AnalogPinOptions from '../Components/AnalogPinOptions'; import { BUTTON_MASKS_OPTIONS } from '../Data/Buttons'; import { DUAL_STICK_MODES } from '../Data/Addons'; -import LEDColors from '../Data/LEDColors'; import { ANALOG_PINS } from '../Data/Buttons'; import { AddonPropTypes } from '../Pages/AddonsConfigPage'; @@ -146,30 +143,22 @@ const Turbo = ({ }: AddonPropTypes) => { const { t } = useTranslation(); - const [colorPickerTarget, setColorPickerTarget] = useState(null); - const [showPicker, setShowPicker] = useState(false); - - const toggleRgbPledPicker = (e) => { - e.stopPropagation(); - setColorPickerTarget(e.target); - setShowPicker(!showPicker); - }; - const { usedPins } = useContext(AppContext); const availableAnalogPins = ANALOG_PINS.filter( - (pin) => !usedPins?.includes(pin), - ); + (pin) => !usedPins?.includes(pin), + ); return ( -
- {t('AddonsConfig:turbo-header-text')} - - } +
+ {t('AddonsConfig:turbo-header-text')} + + } > + ); +} + +export default ButtonLayoutPreview; diff --git a/www/src/Pages/CustomThemePage/ColorSlector.tsx b/www/src/Pages/CustomThemePage/ColorSlector.tsx new file mode 100644 index 0000000000..c76087f14a --- /dev/null +++ b/www/src/Pages/CustomThemePage/ColorSlector.tsx @@ -0,0 +1,58 @@ +import { StylesConfig } from 'react-select'; +import CustomSelect from '../../Components/CustomSelect'; +import LEDColors from '../../Data/LEDColors'; + +type ColorOption = { + value: number; + label: string; + color: string; +}; + +const colorDot = (color = 'transparent') => ({ + alignItems: 'center', + display: 'flex', + + ':before': { + backgroundColor: color, + borderRadius: 15, + content: '" "', + display: 'block', + flexShrink: 0, + marginRight: 8, + height: 15, + width: 15, + }, +}); + +const colorStyles: StylesConfig<(typeof LEDColors)[number]> = { + control: (styles) => ({ ...styles, backgroundColor: 'white' }), + option: (styles, { data }) => ({ ...styles, ...colorDot(data.color) }), + input: (styles) => ({ ...styles }), + placeholder: (styles) => ({ ...styles, ...colorDot('#ccc') }), + singleValue: (styles, { data }) => ({ + ...styles, + ...colorDot(data.color), + }), +}; + +function ColorSelector({ + options, + value, + onChange, +}: { + options: ColorOption[]; + value?: ColorOption | null; + onChange: (selected: ColorOption | null) => void; +}) { + return ( + + ); +} + +export default ColorSelector; diff --git a/www/src/Pages/CustomThemePage/ImportLayout.tsx b/www/src/Pages/CustomThemePage/ImportLayout.tsx new file mode 100644 index 0000000000..7de8fa4acc --- /dev/null +++ b/www/src/Pages/CustomThemePage/ImportLayout.tsx @@ -0,0 +1,47 @@ +import CustomSelect from '../../Components/CustomSelect'; +import { Light } from '../../Store/useLedStore'; +import WebApi from '../../Services/WebApi'; +import { useEffect, useState } from 'react'; + +function ImportLayout({ + setFieldValue, +}: { + setFieldValue: (field: string, value: any) => void; +}) { + const [presets, setPresets] = useState< + { name: string; lightData: { Lights: Light[] } }[] + >([]); + + useEffect(() => { + WebApi.getLightsDataPresets().then(setPresets).catch(console.error); + }, []); + + const handleImport = async ( + selectedOption: { value: string; label: string } | null, + ) => { + if (selectedOption) { + const preset = presets.find((p) => p.name === selectedOption.value); + if (preset) { + setFieldValue('Lights', preset.lightData.Lights); + } + } + }; + + return ( + <> +
+

Choose from predefined installed layouts

+ ({ + value: preset.name, + label: preset.name, + }))} + isMulti={false} + onChange={handleImport} + /> + + ); +} + +export default ImportLayout; diff --git a/www/src/Pages/CustomThemePage/LightCoordsSection.tsx b/www/src/Pages/CustomThemePage/LightCoordsSection.tsx new file mode 100644 index 0000000000..ea2a9aea02 --- /dev/null +++ b/www/src/Pages/CustomThemePage/LightCoordsSection.tsx @@ -0,0 +1,591 @@ +import { useState, useCallback, useEffect } from 'react'; +import { + useSensor, + MouseSensor, + TouchSensor, + KeyboardSensor, + useSensors, + DragEndEvent, + DndContext, +} from '@dnd-kit/core'; +import { + snapCenterToCursor, + restrictToParentElement, +} from '@dnd-kit/modifiers'; +import { FormikErrors } from 'formik'; +import { Row, Col, Button, Alert } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import { + AnimationOptions, + Light, + MAX_CASE_LIGHTS, + MAX_LIGHTS, +} from '../../Store/useLedStore'; +import useLedsPreview from '../../Hooks/useLedsPreview'; +import { useGetContainerDimensions } from '../../Hooks/useGetContainerDimensions'; +import FormControl from '../../Components/FormControl'; +import FormSelect from '../../Components/FormSelect'; +import LEDColors from '../../Data/LEDColors'; +import boards from '../../Data/Boards.json'; +import { rgbIntToHex } from '../../Services/Utilities'; +import ColorSelector from './ColorSlector'; +import { LightIndicator } from './LightIndicator'; + +const GRID_SIZE = 30; +const GPIO_PIN_LENGTH = + boards[import.meta.env.VITE_GP2040_BOARD as keyof typeof boards].maxPin + 1; + +const getFirstEmptyLightCoord = (lights: Light[]) => { + const existingCoords = lights.map( + (light) => `${light.xCoord},${light.yCoord}`, + ); + const total = GRID_SIZE * GRID_SIZE; + + for (let i = 0; i < total; i++) { + const x = i % GRID_SIZE; + const y = Math.floor(i / GRID_SIZE); + if (!existingCoords.includes(`${x},${y}`)) { + return { xCoord: x, yCoord: y }; + } + } + return null; +}; + +export default function LightCoordsSection({ + pressedStaticColors, + notPressedStaticColors, + caseStaticColors, + profileIndex, + values, + errors, + setFieldValue, + setValues, + handleChange, +}: { + pressedStaticColors: number[]; + notPressedStaticColors: number[]; + caseStaticColors: number[]; + profileIndex: number; + values: { Lights: Light[]; AnimationOptions: AnimationOptions }; + errors: FormikErrors<{ + AnimationOptions: AnimationOptions; + Lights: Light[]; + }>; + setFieldValue: (field: string, value: any, shouldValidate?: boolean) => void; + setValues: ( + values: { Lights: Light[]; AnimationOptions: AnimationOptions }, + shouldValidate?: boolean, + ) => void; + handleChange: (e: React.ChangeEvent) => void; +}) { + const { dimensions, containerRef } = useGetContainerDimensions(); + const { t } = useTranslation(''); + const { activateLedsOnId, activateLedsChase, turnOffLeds } = useLedsPreview(); + const [previewGpioPin, setPreviewGpioPin] = useState(0); + const [previewCaseId, setPreviewCaseId] = useState(0); + + const [gridSize, setGridSize] = useState(GRID_SIZE); + const [cellWidth, setCellWidth] = useState(dimensions.width / gridSize); + const [selectedLight, setSelectedLight] = useState(null); + + const mouseSensor = useSensor(MouseSensor, { + activationConstraint: undefined, + }); + const touchSensor = useSensor(TouchSensor, { + activationConstraint: undefined, + }); + + const keyboardSensor = useSensor(KeyboardSensor, {}); + const sensors = useSensors( + // useSensor(PointerSensor, { + // activationConstraint: { delay: 150, tolerance: 0 }, + // }), + mouseSensor, + touchSensor, + keyboardSensor, + ); + + const gridPxToCoords = useCallback( + (pixels: number) => + Math.max(0, Math.min(Math.round(pixels / cellWidth), gridSize - 1)) || 0, + [cellWidth, gridSize], + ); + + useEffect(() => { + if (dimensions.width === 0) return; + const newCellWidth = dimensions.width / gridSize - 1 / gridSize; + + setCellWidth(newCellWidth); + }, [gridSize, dimensions.width]); + + const handleDragStart = useCallback(function handleDragStart( + event: DragEndEvent, + ) { + setSelectedLight(Number(event.active.id)); + }, []); + + const handleDeleteLight = useCallback( + (index: number) => { + setValues({ + AnimationOptions: values.AnimationOptions, + Lights: values.Lights.filter((_, i) => i !== index), + }); + if (selectedLight === index) { + setSelectedLight(null); + } + }, + [setValues, values, selectedLight], + ); + + const customColorOptions = values.AnimationOptions.customColors.map( + (color, index) => ({ + value: LEDColors.length + index, + label: `Custom ${index + 1}`, + color: rgbIntToHex(color), + }), + ); + const colorOptions = [...LEDColors, ...customColorOptions]; + + return ( +
+

+ This section allows you to visually arrange and configure the position + of each light. This is useful for mapping LEDs or light indicators to + their physical locations on your device or case. Drag lights on the grid + or adjust their coordinates manually to match your setup. +

+ + Changing layout configuration options can break your LED setup. + Proceed with caution. + +
+ + + { + setPreviewGpioPin( + parseInt((e.target as HTMLSelectElement).value), + ); + }} + > + {Array.from({ length: GPIO_PIN_LENGTH }).map((_, pinIndex) => ( + + ))} + + + + + + { + setPreviewCaseId(parseInt((e.target as HTMLSelectElement).value)); + }} + > + {Array.from({ length: MAX_CASE_LIGHTS }).map((_, caseIndex) => ( + + ))} + + + + + +

+ Run a chase animation from left to right and then top to bottom to + help verify correct grid positioning of the lights +

+ + + +

Turns off all the lights

+ + +
+
+ + +
+ {selectedLight !== null ? ( +
+

Light {selectedLight + 1}

+ + + + {values.Lights[selectedLight]?.lightType == 1 ? ( + <> + {Array.from({ length: MAX_CASE_LIGHTS }).map( + (_, caseIndex) => ( + + ), + )} + + ) : ( + <> + {Array.from({ length: GPIO_PIN_LENGTH }).map( + (_, pinIndex) => ( + + ), + )} + + )} + + {values.Lights[selectedLight]?.lightType == 1 ? ( +
+ + { + setFieldValue( + `AnimationOptions.profiles.${profileIndex}.caseStaticColors.${values.Lights[selectedLight].GPIOPinorCaseChainIndex}`, + selected?.value || 0, + ); + }} + /> +
+ ) : ( + <> +
+ + { + setFieldValue( + `AnimationOptions.profiles.${profileIndex}.notPressedStaticColors.${values.Lights[selectedLight].GPIOPinorCaseChainIndex}`, + selected?.value || 0, + ); + }} + /> +
+
+ + { + setFieldValue( + `AnimationOptions.profiles.${profileIndex}.pressedStaticColors.${values.Lights[selectedLight].GPIOPinorCaseChainIndex}`, + selected?.value || 0, + ); + }} + /> +
+ + )} + { + setFieldValue( + `Lights[${selectedLight}].lightType`, + parseInt(e.target.value), + ); + }} + name={`Lights[${selectedLight}].lightType`} + > + + + + + + +
+
+ +
+
+ +
+
+ +
+ ) : ( +
+

Select a Light

+

+ Click on a light in the grid to view and edit its properties. +

+
+ )} +
+ {/*
+ +
*/} + + +
+ { + const rect = e.currentTarget.getBoundingClientRect(); + + const gridX = Math.floor((e.clientX - rect.left) / cellWidth); + const gridY = Math.floor((e.clientY - rect.top) / cellWidth); + const clampedX = Math.max(0, Math.min(gridX, gridSize - 1)); + const clampedY = Math.max(0, Math.min(gridY, gridSize - 1)); + + setValues({ + AnimationOptions: values.AnimationOptions, + Lights: [ + ...values.Lights, + { + GPIOPinorCaseChainIndex: 0, + firstLedIndex: values.Lights.length, + lightType: 0, + numLedsOnLight: 1, + xCoord: clampedX, + yCoord: clampedY, + }, + ], + }); + setSelectedLight(values.Lights.length); + }} + > + + + + + + + + + + + + + + { + const activeId = Number(event.active.id); + setFieldValue(`Lights[${event.active.id}]`, { + ...values.Lights[activeId], + xCoord: gridPxToCoords( + values.Lights[activeId].xCoord * cellWidth + event.delta.x, + ), + yCoord: gridPxToCoords( + values.Lights[activeId].yCoord * cellWidth + event.delta.y, + ), + }); + }} + > + {values.Lights.map((light, index) => ( + + ))} + +
+ +
+
+ ); +} diff --git a/www/src/Pages/CustomThemePage/LightIndicator.tsx b/www/src/Pages/CustomThemePage/LightIndicator.tsx new file mode 100644 index 0000000000..392ef32310 --- /dev/null +++ b/www/src/Pages/CustomThemePage/LightIndicator.tsx @@ -0,0 +1,142 @@ +import { useDraggable } from '@dnd-kit/core'; +import { OverlayTrigger, Popover } from 'react-bootstrap'; +import { Light } from '../../Store/useLedStore'; + +type LightIndicatorProps = { + id: number; + cellWidth: number; + active: boolean; + error?: string; +} & Light; + +export const LightIcon = ({ + size, + active, + error, +}: { + size: number; + active: boolean; + error?: string; +}) => ( +
+ {active ? ( + + + + ) : ( + + + + )} +
+); + +export function LightIndicator({ + id, + active = false, + cellWidth, + yCoord, + xCoord, + numLedsOnLight, + firstLedIndex, + lightType, + GPIOPinorCaseChainIndex, + error, +}: LightIndicatorProps) { + const { attributes, isDragging, listeners, setNodeRef, transform } = + useDraggable({ id }); + + return ( +
+ {isDragging ? ( + + ) : ( + + Light {id + 1} + +
+
+ GPIO/Case: + + {lightType === 1 + ? GPIOPinorCaseChainIndex + 1 + : GPIOPinorCaseChainIndex} + +
+
+ Type: + + {lightType === 0 && 'ActionButton'} + {lightType === 1 && 'Case'} + {lightType === 2 && 'Turbo'} + {lightType === 3 && 'PlayerLight'} + +
+
+ LEDs: + {numLedsOnLight} +
+
+ Coords: + + ({xCoord}, {yCoord}) + +
+
+ First LED: + {firstLedIndex} +
+
+
+ + } + > +
+ +
+
+ )} +
+ ); +} diff --git a/www/src/Pages/CustomThemePage/index.tsx b/www/src/Pages/CustomThemePage/index.tsx new file mode 100644 index 0000000000..df3bc4d208 --- /dev/null +++ b/www/src/Pages/CustomThemePage/index.tsx @@ -0,0 +1,633 @@ +import { memo, useEffect, useState } from 'react'; +import { + Alert, + Button, + Col, + Form, + FormGroup, + FormCheck, + OverlayTrigger, + Row, + Tab, + Tabs, + Tooltip, +} from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import { + FieldArray, + FieldArrayRenderProps, + Formik, + useFormikContext, +} from 'formik'; +import * as yup from 'yup'; + +import useLedsPreview from '../../Hooks/useLedsPreview'; +import useLedStore, { + AnimationOptions, + Light, + MAX_ANIMATION_PROFILES, + MAX_CASE_LIGHTS, +} from '../../Store/useLedStore'; +import Section from '../../Components/Section'; +import FormControl from '../../Components/FormControl'; +import FormSelect from '../../Components/FormSelect'; +import InfoCircle from '../../Icons/InfoCircle'; +import { hexToInt, rgbIntToHex } from '../../Services/Utilities'; +import { + ANIMATION_NON_PRESSED_EFFECTS, + ANIMATION_PRESSED_EFFECTS, +} from '../../Data/Animations'; +import boards from '../../Data/Boards.json'; + +import LightCoordsSection from './LightCoordsSection'; +import ButtonLayoutPreview from './ButtonLayoutPreview'; +import ImportLayout from './ImportLayout'; + +const GPIO_PIN_LENGTH = + boards[import.meta.env.VITE_GP2040_BOARD as keyof typeof boards].maxPin + 1; + +const schema = yup.object({ + AnimationOptions: yup.object().shape({ + baseProfileIndex: yup.number().required('Selecting a profile is required'), + brightness: yup + .number() + .min(0, 'Brightness must be at least 0') + .max(10, 'Brightness cannot be more than 10') + .required('Brightness is required'), + idletimeout: yup + .number() + .min(0, 'Idle timout value must be at least 0') + .max(300, 'Idle timout value cannot be more than 300 seconds') + .required('Idle timout value is required'), + profiles: yup.array().of( + yup.object().shape({ + bEnabled: yup.number().required(), + bUseCaseLightsInPressedAnimations: yup.number().required(), + baseCaseEffect: yup.number().required(), + baseNonPressedEffect: yup.number().required(), + basePressedEffect: yup.number().required(), + buttonPressFadeOutTimeInMs: yup.number().required(), + buttonPressHoldTimeInMs: yup.number().required(), + caseStaticColors: yup.array().of(yup.number()).required(), + nonPressedSpecialColor: yup.number().required(), + notPressedStaticColors: yup.array().of(yup.number()).required(), + pressedSpecialColor: yup.number().required(), + pressedStaticColors: yup.array().of(yup.number()), + }), + ), + }), + Lights: yup + .array() + .of( + yup.object({ + GPIOPinorCaseChainIndex: yup.number().required(), + firstLedIndex: yup + .number() + .min(0, 'First LED index must be at least 0') + .required('First LED index is required'), + lightType: yup.number().required(), + numLedsOnLight: yup + .number() + .min(1, 'Number of LEDs on Light must be at least 1') + .required('Number of LEDs on Light is required'), + xCoord: yup.number().required(), + yCoord: yup.number().required(), + }), + ) + // TODO: lookup how to make this better with yup + .test('no-duplicate-coords', 'Overlapping light', function (lights) { + if (!lights) return true; + const coordMap = new Map(); + const overlaps = []; + + for (let i = 0; i < lights.length; i++) { + const key = `${lights[i].xCoord},${lights[i].yCoord}`; + if (coordMap.has(key)) { + overlaps.push(coordMap.get(key), i); + } else { + coordMap.set(key, i); + } + } + if (overlaps.length) { + const errors = Array.from(new Set(overlaps)).reduce( + (acc, i) => [ + ...acc, + new yup.ValidationError( + 'Overlapping light', + null, + `Lights.${i}.xCoord`, + ), + new yup.ValidationError( + 'Overlapping light', + null, + `Lights.${i}.yCoord`, + ), + ], + [] as yup.ValidationError[], + ); + throw new yup.ValidationError(errors); + } + return true; + }), +}); + +const emptyAnimationProfile = { + bEnabled: 1, + baseCaseEffect: 0, + baseNonPressedEffect: 0, + basePressedEffect: 0, + buttonPressFadeOutTimeInMs: 0, + buttonPressHoldTimeInMs: 0, + bUseCaseLightsInPressedAnimations: 0, + caseStaticColors: Array.from({ length: MAX_CASE_LIGHTS }, () => 1), + nonPressedSpecialColor: 0, + pressedSpecialColor: 0, + notPressedStaticColors: Array.from({ length: GPIO_PIN_LENGTH }, () => 0), + pressedStaticColors: Array.from({ length: GPIO_PIN_LENGTH }, () => 1), +}; + +const ColorPickerList = memo(function ColorPickerList({ + colors, + replace, +}: { + colors: number[]; + replace: FieldArrayRenderProps['replace']; +}) { + return ( +
+ {colors.map((color, index) => ( +
+ + replace(index, hexToInt((e.target as HTMLInputElement).value)) + } + /> +
+ ))} +
+ ); +}); + +const PreviewLedChanges = ({ + layouteMode, + selectedProfile, +}: { + layouteMode: boolean; + selectedProfile: number; +}) => { + const { values } = useFormikContext<{ + AnimationOptions: AnimationOptions; + Lights: Light[]; + }>(); + const { activateLedsProfile } = useLedsPreview(); + + useEffect(() => { + if (layouteMode) return; + const profile = values.AnimationOptions.profiles[selectedProfile]; + if (!profile) return; + activateLedsProfile(profile); + }, [values, layouteMode, selectedProfile]); + return null; +}; + +export default function CustomThemePage() { + const { t } = useTranslation(''); + const { fetchLedOptions, saveAnimationOptions, saveLightOptions } = + useLedStore(); + const AnimationOptions = useLedStore((state) => state.AnimationOptions); + const Lights = useLedStore((state) => state.Lights); + const loading = useLedStore((state) => state.loading); + const initialized = useLedStore((state) => state.initialized); + const [selectedProfile, setSelectedProfile] = useState( + AnimationOptions.baseProfileIndex, + ); + const [layouteMode, setLayouteMode] = useState(false); + const [saveMessage, setSaveMessage] = useState(''); + + const onSuccess = async ({ + AnimationOptions, + Lights, + }: { + AnimationOptions: AnimationOptions; + Lights: Light[]; + }) => { + try { + await saveAnimationOptions(AnimationOptions); + await saveLightOptions(Lights); + setSaveMessage(t('Common:saved-success-message')); + } catch (error) { + console.error(error); + } + }; + + useEffect(() => { + fetchLedOptions(); + }, []); + + useEffect(() => { + if (!initialized) return; + if (Lights.length === 0) { + setLayouteMode(true); + } + }, [Lights, initialized]); + + if (loading && !initialized) { + return ( +
+ +
+ ); + } + + return ( + + {({ + handleSubmit, + handleChange, + values, + errors, + setFieldValue, + setValues, + }) => ( +
+
+ + + setFieldValue( + 'AnimationOptions.baseProfileIndex', + parseInt(e.target.value), + ) + } + > + {values.AnimationOptions.profiles.map((_, profileIndex) => ( + + ))} + + + + + + + + {t('Leds:custom-color-label')} + ( + + )} + /> + + + ( + { + if (!eventKey) return; + if ('profile-add' === eventKey) { + arrayHelpers.push(emptyAnimationProfile); + setSelectedProfile( + values.AnimationOptions.profiles.length, + ); + } else { + setSelectedProfile( + parseInt(eventKey.replace('profile-', '')), + ); + } + }} + className="my-3 pb-0" + > + {values.AnimationOptions.profiles.map( + (profile, profileIndex) => ( + + + {t('Leds:switch-enabled-description')} + + } + > +
+ + +
+ + } + type="switch" + reverse + checked={Boolean(profile.bEnabled)} + onChange={() => + setFieldValue( + `AnimationOptions.profiles.${profileIndex}.bEnabled`, + Number(!profile.bEnabled), + ) + } + /> + + + setFieldValue( + `AnimationOptions.profiles.${profileIndex}.baseCaseEffect`, + parseInt(e.target.value), + ) + } + > + {Object.entries(ANIMATION_NON_PRESSED_EFFECTS).map( + ([key, value]) => ( + + ), + )} + + + setFieldValue( + `AnimationOptions.profiles.${profileIndex}.basePressedEffect`, + parseInt(e.target.value), + ) + } + > + {Object.entries(ANIMATION_PRESSED_EFFECTS).map( + ([key, value]) => ( + + ), + )} + + + + setFieldValue( + `AnimationOptions.profiles.${profileIndex}.baseNonPressedEffect`, + parseInt(e.target.value), + ) + } + > + {Object.entries(ANIMATION_NON_PRESSED_EFFECTS).map( + ([key, value]) => ( + + ), + )} + + + +
+ + {t(`Leds:switch-case-light-pressed-label`)} + + } + checked={Boolean( + profile.bUseCaseLightsInPressedAnimations, + )} + onChange={() => + setFieldValue( + `AnimationOptions.profiles.${profileIndex}.bUseCaseLightsInPressedAnimations`, + Number( + !profile.bUseCaseLightsInPressedAnimations, + ), + ) + } + /> +
+ + + +
+ + + setFieldValue( + `AnimationOptions.profiles.${profileIndex}.pressedSpecialColor`, + hexToInt((e.target as HTMLInputElement).value), + ) + } + /> + + setFieldValue( + `AnimationOptions.profiles.${profileIndex}.nonPressedSpecialColor`, + hexToInt((e.target as HTMLInputElement).value), + ) + } + /> + +
+ + + +

+ Layout mode allows for manual configuration of LED + positions and GPIO pins. +

+ setLayouteMode(e.target.checked)} + /> + + + + +
+
+ {layouteMode ? ( + + ) : ( + + )} +
+ ), + )} + + {values.AnimationOptions.profiles.length !== + MAX_ANIMATION_PROFILES && ( + + )} +
+ )} + /> +
+ + {saveMessage && {saveMessage}} + + + )} +
+ ); +} diff --git a/www/src/Pages/LEDConfigPage.jsx b/www/src/Pages/LEDConfigPage.jsx deleted file mode 100644 index eab70db97a..0000000000 --- a/www/src/Pages/LEDConfigPage.jsx +++ /dev/null @@ -1,721 +0,0 @@ -import { useContext, useEffect, useState } from 'react'; -import Button from 'react-bootstrap/Button'; -import Col from 'react-bootstrap/Col'; -import Form from 'react-bootstrap/Form'; -import Row from 'react-bootstrap/Row'; - -import DraggableListGroup from '../Components/DraggableListGroup'; - -import { Formik, useFormikContext } from 'formik'; -import * as yup from 'yup'; -import orderBy from 'lodash/orderBy'; -import { Trans, useTranslation } from 'react-i18next'; - -import { AppContext } from '../Contexts/AppContext'; -import ColorPicker from '../Components/ColorPicker'; -import Section from '../Components/Section'; -import FormControl from '../Components/FormControl'; -import FormSelect from '../Components/FormSelect'; -import { getButtonLabels } from '../Data/Buttons'; -import LEDColors from '../Data/LEDColors'; -import { hexToInt } from '../Services/Utilities'; -import WebApi from '../Services/WebApi'; -import { BUTTON_LAYOUTS } from '../Data/Buttons'; - -const LED_FORMATS = [ - { label: 'GRB', value: 0 }, - { label: 'RGB', value: 1 }, - { label: 'GRBW', value: 2 }, - { label: 'RGBW', value: 3 }, -]; - -const PLED_LABELS = [ - { 0: 'PLED #1 Pin', 1: 'PLED #1 Index' }, - { 0: 'PLED #2 Pin', 1: 'PLED #2 Index' }, - { 0: 'PLED #3 Pin', 1: 'PLED #3 Index' }, - { 0: 'PLED #4 Pin', 1: 'PLED #4 Index' }, -]; - -const CASE_TYPE = [ - { value: -1, label: 'Off' }, - { value: 0, label: 'Ambient' }, - { value: 1, label: 'Linked' }, -]; - -const defaultValue = { - brightnessMaximum: 255, - brightnessSteps: 5, - dataPin: -1, - ledFormat: 0, - ledLayout: 0, - ledsPerButton: 2, - pledType: -1, - pledPin1: -1, - pledPin2: -1, - pledPin3: -1, - pledPin4: -1, - pledIndex1: -1, - pledIndex2: -1, - pledIndex3: -1, - pledIndex4: -1, - pledColor: '#00ff00', - caseRGBType: 0, - caseRGBIndex: -1, - caseRGBCount: 0, - ledButtonMap: {}, -}; - -const schema = yup.object().shape({ - brightnessMaximum: yup - .number() - .required() - .positive() - .integer() - .min(0) - .max(255) - .label('Max Brightness'), - brightnessSteps: yup - .number() - .required() - .positive() - .integer() - .min(1) - .max(10) - .label('Brightness Steps'), - dataPin: yup.number().required().checkUsedPins(), - ledFormat: yup - .number() - .required() - .positive() - .integer() - .min(0) - .max(3) - .label('LED Format'), - ledLayout: yup - .number() - .required() - .positive() - .integer() - .min(0) - .max(38) - .label('LED Layout'), - ledsPerButton: yup - .number() - .required() - .positive() - .integer() - .min(1) - .label('LEDs Per Pixel'), - pledType: yup.number().required().label('Player LED Type'), - pledColor: yup.string().label('RGB Player LEDs').validateColor(), - pledPin1: yup - .number() - .label('PLED 1') - .validatePinWhenEqualTo('pledPins1', 'pledType', 0), - pledPin2: yup - .number() - .label('PLED 2') - .validatePinWhenEqualTo('pledPins2', 'pledType', 0), - pledPin3: yup - .number() - .label('PLED 3') - .validatePinWhenEqualTo('pledPins3', 'pledType', 0), - pledPin4: yup - .number() - .label('PLED 4') - .validatePinWhenEqualTo('pledPins4', 'pledType', 0), - pledIndex1: yup - .number() - .label('PLED Index 1') - .validateMinWhenEqualTo('pledType', 1, 0), - pledIndex2: yup - .number() - .label('PLED Index 2') - .validateMinWhenEqualTo('pledType', 1, 0), - pledIndex3: yup - .number() - .label('PLED Index 3') - .validateMinWhenEqualTo('pledType', 1, 0), - pledIndex4: yup - .number() - .label('PLED Index 4') - .validateMinWhenEqualTo('pledType', 1, 0), - turnOffWhenSuspended: yup.number().label('Turn Off When Suspended'), - caseRGBType: yup.number().required().label('Case RGB Type'), - caseRGBCount: yup - .number() - .required() - .positive() - .integer() - .min(0) - .max(100) - .label('Case RGB Count'), - caseRGBIndex: yup.number().label('Case RGB Index').min(-1).max(100), - ledButtonMap: yup.object(), -}); - -const createDataSource = (ledButtonMap, buttonLabelType, swapTpShareLabels) => { - let available = {}; - let assigned = {}; - - Object.keys(ledButtonMap).forEach((p) => { - if (ledButtonMap[p] === null) available[p] = ledButtonMap[p]; - else assigned[p] = ledButtonMap[p]; - }); - - const dataSources = [ - getLedButtons(buttonLabelType, available, true, swapTpShareLabels), - getLedButtons(buttonLabelType, assigned, true, swapTpShareLabels), - ]; - - return dataSources; -}; - -const getLedButtons = (buttonLabels, map, excludeNulls, swapTpShareLabels) => { - const current_buttons = getButtonLabels(buttonLabels, swapTpShareLabels); - return orderBy( - Object.keys(current_buttons) - .filter((p) => p !== 'label' && p !== 'value') - .filter((p) => (excludeNulls ? map[p] > -1 : true)) - .map((p) => { - return { id: p, label: current_buttons[p], value: map[p] }; - }), - 'value', - ); -}; - -const createLedMap = (ledButtons, clear) => { - if (!ledButtons) return; - return ledButtons.reduce( - (acc, btn) => ({ ...acc, [btn.id]: clear ? null : btn.value }), - {}, - ); -}; - -const FormContext = ({ - buttonLabelType, - ledButtonMap, - swapTpShareLabels, - setDataSources, -}) => { - const { setValues } = useFormikContext(); - const { setLoading } = useContext(AppContext); - - useEffect(() => { - async function fetchData() { - const data = await WebApi.getLedOptions(setLoading); - const dataSources = createDataSource( - data.ledButtonMap, - buttonLabelType, - swapTpShareLabels, - ); - setDataSources(dataSources); - setValues(data); - } - fetchData(); - }, []); - - useEffect(() => { - const dataSources = createDataSource( - ledButtonMap, - buttonLabelType, - swapTpShareLabels, - ); - setDataSources(dataSources); - }, [buttonLabelType, swapTpShareLabels]); - - return null; -}; - -export default function LEDConfigPage() { - const { buttonLabels, updateUsedPins } = useContext(AppContext); - const [saveMessage, setSaveMessage] = useState(''); - const [dataSources, setDataSources] = useState([[], []]); - const [colorPickerTarget, setColorPickerTarget] = useState(null); - const [showPicker, setShowPicker] = useState(false); - const [rgbLedStartIndex, setRgbLedStartIndex] = useState(0); - - const { buttonLabelType, swapTpShareLabels } = buttonLabels; - - const { t } = useTranslation(''); - - // Translate PLED labels - PLED_LABELS.map((p, n) => { - p[0] = t(`LedConfig:pled-pin-label`, { pin: ++n }); - p[1] = t(`LedConfig:pled-index-label`, { index: n }); - }); - - CASE_TYPE[0].label = t(`LedConfig:case.case-type-off`); - CASE_TYPE[1].label = t(`LedConfig:case.case-type-ambient`); - CASE_TYPE[2].label = t(`LedConfig:case.case-type-linked`); - - const ledOrderChanged = (setFieldValue, ledOrderArrays, ledsPerButton) => { - if (ledOrderArrays.length === 2) { - setRgbLedStartIndex(ledOrderArrays[1].length * (ledsPerButton || 0)); - setFieldValue('ledButtonMap', { - ...createLedMap(ledOrderArrays[0], true), - ...createLedMap(ledOrderArrays[1], false), - }); - } - }; - - const ledsPerButtonChanged = (e, handleChange) => { - const ledsPerButton = parseInt(e.target.value); - setRgbLedStartIndex(dataSources[1].length * (ledsPerButton || 0)); - handleChange(e); - }; - - const toggleRgbPledPicker = (e) => { - e.stopPropagation(); - setColorPickerTarget(e.target); - setShowPicker(!showPicker); - }; - - const onSuccess = async (values) => { - const data = { - ...values, - pledColor: hexToInt(values.pledColor || '#000000'), - }; - - const success = await WebApi.setLedOptions(data); - if (success) updateUsedPins(); - - // Need to recreate the DraggableList data source after save - const dataSources = createDataSource( - data.ledButtonMap, - buttonLabelType, - swapTpShareLabels, - ); - setDataSources(dataSources); - - setSaveMessage( - success - ? t('Common:saved-success-message') - : t('Common:saved-error-message'), - ); - }; - - const onSubmit = (e, handleSubmit) => { - e.preventDefault(); - setSaveMessage(''); - handleSubmit(); - }; - - return ( - - {({ - handleSubmit, - handleChange, - handleBlur, - values, - errors, - setFieldValue, - }) => ( -
onSubmit(e, handleSubmit)}> -
- - - - setFieldValue('ledFormat', parseInt(e.target.value)) - } - > - {LED_FORMATS.map((o, i) => ( - - ))} - - - setFieldValue('ledLayout', parseInt(e.target.value)) - } - > - {BUTTON_LAYOUTS.map((o, i) => ( - - ))} - - - - ledsPerButtonChanged(e, handleChange)} - min={1} - /> - - -
- { - setFieldValue( - 'turnOffWhenSuspended', - e.target.checked ? 1 : 0, - ); - }} - /> -
-
-
-
-

- {t('LedConfig:rgb-order.sub-header-text')} -

-

- {t('LedConfig:rgb-order.sub-header1-text')} -

- - ledOrderChanged(setFieldValue, a, values.ledsPerButton) - } - /> -
-
- - - - setFieldValue('pledType', parseInt(e.target.value)) - } - > - - - - - - - - -
-
- - - - setFieldValue('caseRGBType', parseInt(e.target.value)) - } - > - {CASE_TYPE.map((o, i) => ( - - ))} - - -

{t('LedConfig:case.sub-header-text')}

-
-
- - {saveMessage ? {saveMessage} : null} - - - )} -
- ); -} diff --git a/www/src/Pages/LEDConfigPage.tsx b/www/src/Pages/LEDConfigPage.tsx new file mode 100644 index 0000000000..a274e1292d --- /dev/null +++ b/www/src/Pages/LEDConfigPage.tsx @@ -0,0 +1,163 @@ +import { useContext, useEffect, useState } from 'react'; +import Button from 'react-bootstrap/Button'; +import Form from 'react-bootstrap/Form'; +import Row from 'react-bootstrap/Row'; + +import { Formik, useFormikContext } from 'formik'; +import * as yup from 'yup'; +import { useTranslation } from 'react-i18next'; + +import { AppContext } from '../Contexts/AppContext'; +import Section from '../Components/Section'; +import FormControl from '../Components/FormControl'; +import FormSelect from '../Components/FormSelect'; +import WebApi from '../Services/WebApi'; +import { FormCheck } from 'react-bootstrap'; + +const LED_FORMATS = [ + { label: 'GRB', value: 0 }, + { label: 'RGB', value: 1 }, + { label: 'GRBW', value: 2 }, + { label: 'RGBW', value: 3 }, +]; + +const defaultValue = { + dataPin: -1, + ledFormat: 0, + turnOffWhenSuspended: 0, + brightnessMaximum: 0, +}; + +const schema = yup.object().shape({ + dataPin: yup.number().required().checkUsedPins(), + ledFormat: yup + .number() + .required() + .positive() + .integer() + .min(0) + .max(3) + .label('LED Format'), + brightnessMaximum: yup + .number() + .required() + .positive() + .integer() + .min(1) + .max(100) + .label('Max Brightness'), + turnOffWhenSuspended: yup.number().label('Turn Off When Suspended'), +}); + +const FormContext = () => { + const { setValues } = useFormikContext(); + + useEffect(() => { + async function fetchData() { + const data = await WebApi.getLedOptions(); + setValues(data); + } + fetchData(); + }, [setValues]); + return null; +}; + +export default function LEDConfigPage() { + const { updateUsedPins } = useContext(AppContext); + const [saveMessage, setSaveMessage] = useState(''); + + const { t } = useTranslation(''); + + const onSuccess = async (values: typeof defaultValue) => { + const success = await WebApi.setLedOptions(values); + if (success) updateUsedPins(); + + setSaveMessage( + success + ? t('Common:saved-success-message') + : t('Common:saved-error-message'), + ); + }; + + return ( + + {({ handleSubmit, handleChange, values, errors, setFieldValue }) => ( +
+
+ + + + setFieldValue('ledFormat', parseInt(e.target.value)) + } + > + {LED_FORMATS.map((o, i) => ( + + ))} + + + + +
+ { + setFieldValue( + 'turnOffWhenSuspended', + e.target.checked ? 1 : 0, + ); + }} + /> +
+
+
+ + {saveMessage ? {saveMessage} : null} + + + )} +
+ ); +} diff --git a/www/src/Pages/PeripheralMappingPage.jsx b/www/src/Pages/PeripheralMappingPage.jsx index 60d30d1eed..69a63ecab1 100644 --- a/www/src/Pages/PeripheralMappingPage.jsx +++ b/www/src/Pages/PeripheralMappingPage.jsx @@ -146,174 +146,168 @@ export default function PeripheralMappingPage() { validationSchema={schema} initialValues={basePeripheralMapping} > - {({ errors, handleSubmit, setFieldValue, values }) => - console.log('errors', errors) || ( -
-
-
-

{t('PeripheralMapping:sub-header-text')}

- {PERIPHERAL_DEVICES.map((peripheral, i) => ( - - - {t(`PeripheralMapping:${peripheral.label}-label`)} - - - {peripheral.blocks.map((block, i) => ( -
-
- ( +
+ +
+

{t('PeripheralMapping:sub-header-text')}

+ {PERIPHERAL_DEVICES.map((peripheral, i) => ( + + + {t(`PeripheralMapping:${peripheral.label}-label`)} + + + {peripheral.blocks.map((block, i) => ( +
+
+ { + setFieldValue( + `peripheral.${block.label}.enabled`, + e.target.checked ? 1 : 0, + ); + }} + /> +
+ {Object.keys(block.pins).map((pin, i) => ( +
+ + {t( + `PeripheralMapping:pin-${pin.toLowerCase()}-label`, + )} + + { setFieldValue( - `peripheral.${block.label}.enabled`, - e.target.checked ? 1 : 0, + `peripheral.${block.label}.${pin}`, + e.target.value, ); }} - /> -
- {Object.keys(block.pins).map((pin, i) => ( -
- - {t( - `PeripheralMapping:pin-${pin.toLowerCase()}-label`, - )} - - { - setFieldValue( - `peripheral.${block.label}.${pin}`, - e.target.value, - ); - }} + + {pinLookup(block.pins[pin]).map((o, i2) => ( - {pinLookup(block.pins[pin]).map((o, i2) => ( - - ))} - -
- ))} - {Object.keys(peripheral.options).map((option, i) => ( -
- - {t( - `PeripheralMapping:option-${option.toLowerCase()}-label`, - )} - - +
+ ))} + {Object.keys(peripheral.options).map((option, i) => ( +
+ + {t( + `PeripheralMapping:option-${option.toLowerCase()}-label`, + )} + + { + setFieldValue( `peripheral.${block.label}.${option}`, - )} - value={ - values.peripheral[`${block.label}`][`${option}`] - } - onChange={(e) => { - setFieldValue( - `peripheral.${block.label}.${option}`, - e.target.value, - ); - }} - > - {peripheral.options[option].map((o, i2) => ( - - ))} - -
- ))} -
- ))} -
- ))} -
- - {saveMessage ? ( - {saveMessage} - ) : null} - - -
- ) - } + e.target.value, + ); + }} + > + {peripheral.options[option].map((o, i2) => ( + + ))} + +
+ ))} +
+ ))} +
+ ))} +
+ + {saveMessage ? {saveMessage} : null} + + +
+ )} ); } diff --git a/www/src/Pages/PinMapping.tsx b/www/src/Pages/PinMapping.tsx index de8609acd1..47fe6161d2 100644 --- a/www/src/Pages/PinMapping.tsx +++ b/www/src/Pages/PinMapping.tsx @@ -40,6 +40,7 @@ import './PinMapping.scss'; import { MultiValue, SingleValue } from 'react-select'; import InfoCircle from '../Icons/InfoCircle'; import WebApi from '../Services/WebApi'; +import useLedsPreview from '../Hooks/useLedsPreview'; type OptionType = { label: string; @@ -81,8 +82,8 @@ const options = Object.entries(BUTTON_ACTIONS) type: buttonMask ? 'customButtonMask' : dpadMask - ? 'customDpadMask' - : 'action', + ? 'customDpadMask' + : 'action', customButtonMask: buttonMask?.value || 0, customDpadMask: dpadMask?.value || 0, }; @@ -121,7 +122,7 @@ const getMultiValue = (pinData: MaskPayload) => { type === 'customButtonMask') || (pinData.customDpadMask & customDpadMask && type === 'customDpadMask'), - ) + ) : options.filter((option) => option.value === pinData.action); }; @@ -169,7 +170,7 @@ const PinSelectList = memo(function PinSelectList({ profileIndex: number; }) { const setProfilePin = useProfilesStore((state) => state.setProfilePin); - + const { activateLedsOnId, turnOffLeds } = useLedsPreview(); const pins = useProfilesStore( useShallow((state) => omit(state.profiles[profileIndex], ['profileLabel', 'enabled']), @@ -246,6 +247,7 @@ const PinSelectList = memo(function PinSelectList({ }, [buttonNames], ); + return (
{Object.entries(pins).map(([pin, pinData], index) => ( @@ -261,6 +263,8 @@ const PinSelectList = memo(function PinSelectList({ getOptionLabel={getOptionLabel} onChange={onChange(pin)} value={getMultiValue(pinData)} + onFocus={() => activateLedsOnId(index)} + onBlur={turnOffLeds} />
))} @@ -428,7 +432,7 @@ export default function PinMapping() { {loadingProfiles && (
- +
)}