From: Fred Sundvik Date: Wed, 6 Jul 2016 17:30:58 +0000 (+0300) Subject: Merge commit '73d890a2c9c34b905cd5e74e7146fdd4578dcb96' into add_visualizer X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=6c296557909501b71fe344ce379e74094cf77c8e;p=qmk_firmware.git Merge commit '73d890a2c9c34b905cd5e74e7146fdd4578dcb96' into add_visualizer --- 6c296557909501b71fe344ce379e74094cf77c8e diff --cc quantum/visualizer/led_test.c index 000000000,000000000..c2ea30b55 new file mode 100644 --- /dev/null +++ b/quantum/visualizer/led_test.c @@@ -1,0 -1,0 +1,170 @@@ ++/* ++The MIT License (MIT) ++ ++Copyright (c) 2016 Fred Sundvik ++ ++Permission is hereby granted, free of charge, to any person obtaining a copy ++of this software and associated documentation files (the "Software"), to deal ++in the Software without restriction, including without limitation the rights ++to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++copies of the Software, and to permit persons to whom the Software is ++furnished to do so, subject to the following conditions: ++ ++The above copyright notice and this permission notice shall be included in all ++copies or substantial portions of the Software. ++ ++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ++SOFTWARE. ++*/ ++#include "led_test.h" ++#include "gfx.h" ++#include "math.h" ++ ++#define CROSSFADE_TIME 1000 ++#define GRADIENT_TIME 3000 ++ ++keyframe_animation_t led_test_animation = { ++ .num_frames = 14, ++ .loop = true, ++ .frame_lengths = { ++ gfxMillisecondsToTicks(1000), // fade in ++ gfxMillisecondsToTicks(1000), // no op (leds on) ++ gfxMillisecondsToTicks(1000), // fade out ++ gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade ++ gfxMillisecondsToTicks(GRADIENT_TIME), // left to rigt (outside in) ++ gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade ++ gfxMillisecondsToTicks(GRADIENT_TIME), // top_to_bottom ++ 0, // mirror leds ++ gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade ++ gfxMillisecondsToTicks(GRADIENT_TIME), // left_to_right (mirrored, so inside out) ++ gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade ++ gfxMillisecondsToTicks(GRADIENT_TIME), // top_to_bottom ++ 0, // normal leds ++ gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade ++ ++ }, ++ .frame_functions = { ++ keyframe_fade_in_all_leds, ++ keyframe_no_operation, ++ keyframe_fade_out_all_leds, ++ keyframe_led_crossfade, ++ keyframe_led_left_to_right_gradient, ++ keyframe_led_crossfade, ++ keyframe_led_top_to_bottom_gradient, ++ keyframe_mirror_led_orientation, ++ keyframe_led_crossfade, ++ keyframe_led_left_to_right_gradient, ++ keyframe_led_crossfade, ++ keyframe_led_top_to_bottom_gradient, ++ keyframe_normal_led_orientation, ++ keyframe_led_crossfade, ++ }, ++}; ++ ++static uint8_t fade_led_color(keyframe_animation_t* animation, int from, int to) { ++ int frame_length = animation->frame_lengths[animation->current_frame]; ++ int current_pos = frame_length - animation->time_left_in_frame; ++ int delta = to - from; ++ int luma = (delta * current_pos) / frame_length; ++ luma += from; ++ return luma; ++} ++ ++static void keyframe_fade_all_leds_from_to(keyframe_animation_t* animation, uint8_t from, uint8_t to) { ++ uint8_t luma = fade_led_color(animation, from, to); ++ color_t color = LUMA2COLOR(luma); ++ gdispGClear(LED_DISPLAY, color); ++} ++ ++// TODO: Should be customizable per keyboard ++#define NUM_ROWS 7 ++#define NUM_COLS 7 ++ ++static uint8_t crossfade_start_frame[NUM_ROWS][NUM_COLS]; ++static uint8_t crossfade_end_frame[NUM_ROWS][NUM_COLS]; ++ ++static uint8_t compute_gradient_color(float t, float index, float num) { ++ const float two_pi = 2.0f * PI; ++ float normalized_index = (1.0f - index / (num - 1)) * two_pi; ++ float x = t * two_pi + normalized_index; ++ float v = 0.5 * (cosf(x) + 1.0f); ++ return (uint8_t)(255.0f * v); ++} ++ ++bool keyframe_fade_in_all_leds(keyframe_animation_t* animation, visualizer_state_t* state) { ++ (void)state; ++ keyframe_fade_all_leds_from_to(animation, 0, 255); ++ return true; ++} ++ ++bool keyframe_fade_out_all_leds(keyframe_animation_t* animation, visualizer_state_t* state) { ++ (void)state; ++ keyframe_fade_all_leds_from_to(animation, 255, 0); ++ return true; ++} ++ ++bool keyframe_led_left_to_right_gradient(keyframe_animation_t* animation, visualizer_state_t* state) { ++ (void)state; ++ float frame_length = animation->frame_lengths[animation->current_frame]; ++ float current_pos = frame_length - animation->time_left_in_frame; ++ float t = current_pos / frame_length; ++ for (int i=0; i< NUM_COLS; i++) { ++ uint8_t color = compute_gradient_color(t, i, NUM_COLS); ++ gdispGDrawLine(LED_DISPLAY, i, 0, i, NUM_ROWS - 1, LUMA2COLOR(color)); ++ } ++ return true; ++} ++ ++bool keyframe_led_top_to_bottom_gradient(keyframe_animation_t* animation, visualizer_state_t* state) { ++ (void)state; ++ float frame_length = animation->frame_lengths[animation->current_frame]; ++ float current_pos = frame_length - animation->time_left_in_frame; ++ float t = current_pos / frame_length; ++ for (int i=0; i< NUM_ROWS; i++) { ++ uint8_t color = compute_gradient_color(t, i, NUM_ROWS); ++ gdispGDrawLine(LED_DISPLAY, 0, i, NUM_COLS - 1, i, LUMA2COLOR(color)); ++ } ++ return true; ++} ++ ++static void copy_current_led_state(uint8_t* dest) { ++ for (int i=0;ifirst_update_of_frame) { ++ copy_current_led_state(&crossfade_start_frame[0][0]); ++ run_next_keyframe(animation, state); ++ copy_current_led_state(&crossfade_end_frame[0][0]); ++ } ++ for (int i=0;i ++#ifdef PROTOCOL_CHIBIOS ++#include "ch.h" ++#endif + +#ifdef LCD_ENABLE +#include "gfx.h" +#endif + +#ifdef LCD_BACKLIGHT_ENABLE +#include "lcd_backlight.h" +#endif + +//#define DEBUG_VISUALIZER + +#ifdef DEBUG_VISUALIZER +#include "debug.h" +#else +#include "nodebug.h" +#endif + +#ifdef USE_SERIAL_LINK +#include "serial_link/protocol/transport.h" +#include "serial_link/system/serial_link.h" +#endif + +// Define this in config.h +#ifndef VISUALIZER_THREAD_PRIORITY +#define "Visualizer thread priority not defined" +#endif + + +static visualizer_keyboard_status_t current_status = { + .layer = 0xFFFFFFFF, + .default_layer = 0xFFFFFFFF, + .leds = 0xFFFFFFFF, + .suspended = false, +}; + +static bool same_status(visualizer_keyboard_status_t* status1, visualizer_keyboard_status_t* status2) { + return status1->layer == status2->layer && + status1->default_layer == status2->default_layer && + status1->leds == status2->leds && + status1->suspended == status2->suspended; +} + - static event_source_t layer_changed_event; +static bool visualizer_enabled = false; + +#define MAX_SIMULTANEOUS_ANIMATIONS 4 +static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {}; + +#ifdef USE_SERIAL_LINK +MASTER_TO_ALL_SLAVES_OBJECT(current_status, visualizer_keyboard_status_t); + +static remote_object_t* remote_objects[] = { + REMOTE_OBJECT(current_status), +}; + +#endif + ++GDisplay* LCD_DISPLAY = 0; ++GDisplay* LED_DISPLAY = 0; ++ ++__attribute__((weak)) ++GDisplay* get_lcd_display(void) { ++ return gdispGetDisplay(0); ++} ++ ++__attribute__((weak)) ++GDisplay* get_led_display(void) { ++ return gdispGetDisplay(1); ++} + +void start_keyframe_animation(keyframe_animation_t* animation) { + animation->current_frame = -1; + animation->time_left_in_frame = 0; + animation->need_update = true; + int free_index = -1; + for (int i=0;icurrent_frame = animation->num_frames; + animation->time_left_in_frame = 0; + animation->need_update = true; ++ animation->first_update_of_frame = false; ++ animation->last_update_of_frame = false; + for (int i=0;icurrent_frame = animations[i]->num_frames; + animations[i]->time_left_in_frame = 0; + animations[i]->need_update = true; ++ animations[i]->first_update_of_frame = false; ++ animations[i]->last_update_of_frame = false; + animations[i] = NULL; + } + } +} + - static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systime_t delta, systime_t* sleep_time) { ++static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systemticks_t delta, systemticks_t* sleep_time) { ++ // TODO: Clean up this messy code + dprintf("Animation frame%d, left %d, delta %d\n", animation->current_frame, + animation->time_left_in_frame, delta); + if (animation->current_frame == animation->num_frames) { + animation->need_update = false; + return false; + } + if (animation->current_frame == -1) { + animation->current_frame = 0; + animation->time_left_in_frame = animation->frame_lengths[0]; + animation->need_update = true; ++ animation->first_update_of_frame = true; + } else { + animation->time_left_in_frame -= delta; + while (animation->time_left_in_frame <= 0) { + int left = animation->time_left_in_frame; + if (animation->need_update) { + animation->time_left_in_frame = 0; ++ animation->last_update_of_frame = true; + (*animation->frame_functions[animation->current_frame])(animation, state); ++ animation->last_update_of_frame = false; + } + animation->current_frame++; + animation->need_update = true; ++ animation->first_update_of_frame = true; + if (animation->current_frame == animation->num_frames) { + if (animation->loop) { + animation->current_frame = 0; + } + else { + stop_keyframe_animation(animation); + return false; + } + } + delta = -left; + animation->time_left_in_frame = animation->frame_lengths[animation->current_frame]; + animation->time_left_in_frame -= delta; + } + } + if (animation->need_update) { + animation->need_update = (*animation->frame_functions[animation->current_frame])(animation, state); ++ animation->first_update_of_frame = false; + } + - int wanted_sleep = animation->need_update ? 10 : animation->time_left_in_frame; - if ((unsigned)wanted_sleep < *sleep_time) { ++ systemticks_t wanted_sleep = animation->need_update ? gfxMillisecondsToTicks(10) : (unsigned)animation->time_left_in_frame; ++ if (wanted_sleep < *sleep_time) { + *sleep_time = wanted_sleep; + } + + return true; +} + ++void run_next_keyframe(keyframe_animation_t* animation, visualizer_state_t* state) { ++ int next_frame = animation->current_frame + 1; ++ if (next_frame == animation->num_frames) { ++ next_frame = 0; ++ } ++ keyframe_animation_t temp_animation = *animation; ++ temp_animation.current_frame = next_frame; ++ temp_animation.time_left_in_frame = animation->frame_lengths[next_frame]; ++ temp_animation.first_update_of_frame = true; ++ temp_animation.last_update_of_frame = false; ++ temp_animation.need_update = false; ++ visualizer_state_t temp_state = *state; ++ (*temp_animation.frame_functions[next_frame])(&temp_animation, &temp_state); ++} ++ +bool keyframe_no_operation(keyframe_animation_t* animation, visualizer_state_t* state) { + (void)animation; + (void)state; + return false; +} + +#ifdef LCD_BACKLIGHT_ENABLE +bool keyframe_animate_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) { + int frame_length = animation->frame_lengths[animation->current_frame]; + int current_pos = frame_length - animation->time_left_in_frame; + uint8_t t_h = LCD_HUE(state->target_lcd_color); + uint8_t t_s = LCD_SAT(state->target_lcd_color); + uint8_t t_i = LCD_INT(state->target_lcd_color); + uint8_t p_h = LCD_HUE(state->prev_lcd_color); + uint8_t p_s = LCD_SAT(state->prev_lcd_color); + uint8_t p_i = LCD_INT(state->prev_lcd_color); + + uint8_t d_h1 = t_h - p_h; //Modulo arithmetic since we want to wrap around + int d_h2 = t_h - p_h; + // Chose the shortest way around + int d_h = abs(d_h2) < d_h1 ? d_h2 : d_h1; + int d_s = t_s - p_s; + int d_i = t_i - p_i; + + int hue = (d_h * current_pos) / frame_length; + int sat = (d_s * current_pos) / frame_length; + int intensity = (d_i * current_pos) / frame_length; + //dprintf("%X -> %X = %X\n", p_h, t_h, hue); + hue += p_h; + sat += p_s; + intensity += p_i; + state->current_lcd_color = LCD_COLOR(hue, sat, intensity); + lcd_backlight_color( + LCD_HUE(state->current_lcd_color), + LCD_SAT(state->current_lcd_color), + LCD_INT(state->current_lcd_color)); + + return true; +} + +bool keyframe_set_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state) { + (void)animation; + state->prev_lcd_color = state->target_lcd_color; + state->current_lcd_color = state->target_lcd_color; + lcd_backlight_color( + LCD_HUE(state->current_lcd_color), + LCD_SAT(state->current_lcd_color), + LCD_INT(state->current_lcd_color)); + return false; +} +#endif // LCD_BACKLIGHT_ENABLE + +#ifdef LCD_ENABLE +bool keyframe_display_layer_text(keyframe_animation_t* animation, visualizer_state_t* state) { + (void)animation; + gdispClear(White); + gdispDrawString(0, 10, state->layer_text, state->font_dejavusansbold12, Black); + gdispFlush(); + return false; +} + +static void format_layer_bitmap_string(uint16_t default_layer, uint16_t layer, char* buffer) { + for (int i=0; i<16;i++) + { + uint32_t mask = (1u << i); + if (default_layer & mask) { + if (layer & mask) { + *buffer = 'B'; + } else { + *buffer = 'D'; + } + } else if (layer & mask) { + *buffer = '1'; + } else { + *buffer = '0'; + } + ++buffer; + + if (i==3 || i==7 || i==11) { + *buffer = ' '; + ++buffer; + } + } + *buffer = 0; +} + +bool keyframe_display_layer_bitmap(keyframe_animation_t* animation, visualizer_state_t* state) { + (void)animation; + const char* layer_help = "1=On D=Default B=Both"; + char layer_buffer[16 + 4]; // 3 spaces and one null terminator + gdispClear(White); + gdispDrawString(0, 0, layer_help, state->font_fixed5x8, Black); + format_layer_bitmap_string(state->status.default_layer, state->status.layer, layer_buffer); + gdispDrawString(0, 10, layer_buffer, state->font_fixed5x8, Black); + format_layer_bitmap_string(state->status.default_layer >> 16, state->status.layer >> 16, layer_buffer); + gdispDrawString(0, 20, layer_buffer, state->font_fixed5x8, Black); + gdispFlush(); + return false; +} +#endif // LCD_ENABLE + +bool keyframe_disable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) { + (void)animation; + (void)state; +#ifdef LCD_ENABLE + gdispSetPowerMode(powerOff); +#endif +#ifdef LCD_BACKLIGHT_ENABLE + lcd_backlight_hal_color(0, 0, 0); +#endif + return false; +} + +bool keyframe_enable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state) { + (void)animation; + (void)state; +#ifdef LCD_ENABLE + gdispSetPowerMode(powerOn); +#endif + return false; +} + +bool enable_visualization(keyframe_animation_t* animation, visualizer_state_t* state) { + (void)animation; + (void)state; + dprint("User visualizer inited\n"); + visualizer_enabled = true; + return false; +} + +// TODO: Optimize the stack size, this is probably way too big - static THD_WORKING_AREA(visualizerThreadStack, 1024); - static THD_FUNCTION(visualizerThread, arg) { ++static DECLARE_THREAD_STACK(visualizerThreadStack, 1024); ++static DECLARE_THREAD_FUNCTION(visualizerThread, arg) { + (void)arg; + - event_listener_t event_listener; - chEvtRegister(&layer_changed_event, &event_listener, 0); ++ GListener event_listener; ++ geventListenerInit(&event_listener); ++ geventAttachSource(&event_listener, (GSourceHandle)¤t_status, 0); + + visualizer_keyboard_status_t initial_status = { + .default_layer = 0xFFFFFFFF, + .layer = 0xFFFFFFFF, + .leds = 0xFFFFFFFF, + .suspended = false, + }; + + visualizer_state_t state = { + .status = initial_status, + .current_lcd_color = 0, +#ifdef LCD_ENABLE + .font_fixed5x8 = gdispOpenFont("fixed_5x8"), + .font_dejavusansbold12 = gdispOpenFont("DejaVuSansBold12") +#endif + }; + initialize_user_visualizer(&state); + state.prev_lcd_color = state.current_lcd_color; + +#ifdef LCD_BACKLIGHT_ENABLE + lcd_backlight_color( + LCD_HUE(state.current_lcd_color), + LCD_SAT(state.current_lcd_color), + LCD_INT(state.current_lcd_color)); +#endif + - systime_t sleep_time = TIME_INFINITE; - systime_t current_time = chVTGetSystemTimeX(); ++ systemticks_t sleep_time = TIME_INFINITE; ++ systemticks_t current_time = gfxSystemTicks(); + + while(true) { - systime_t new_time = chVTGetSystemTimeX(); - systime_t delta = new_time - current_time; ++ systemticks_t new_time = gfxSystemTicks(); ++ systemticks_t delta = new_time - current_time; + current_time = new_time; + bool enabled = visualizer_enabled; + if (!same_status(&state.status, ¤t_status)) { + if (visualizer_enabled) { + if (current_status.suspended) { + stop_all_keyframe_animations(); + visualizer_enabled = false; + state.status = current_status; + user_visualizer_suspend(&state); + } + else { + state.status = current_status; + update_user_visualizer_state(&state); + } + state.prev_lcd_color = state.current_lcd_color; + } + } + if (!enabled && state.status.suspended && current_status.suspended == false) { + // Setting the status to the initial status will force an update + // when the visualizer is enabled again + state.status = initial_status; + state.status.suspended = false; + stop_all_keyframe_animations(); + user_visualizer_resume(&state); + state.prev_lcd_color = state.current_lcd_color; + } + sleep_time = TIME_INFINITE; + for (int i=0;i update_delta) { + sleep_time -= update_delta; + } + else { + sleep_time = 0; + } + } + dprintf("Update took %d, last delta %d, sleep_time %d\n", update_delta, delta, sleep_time); - chEvtWaitOneTimeout(EVENT_MASK(0), sleep_time); ++#ifdef PROTOCOL_CHIBIOS ++ // The gEventWait function really takes milliseconds, even if the documentation says ticks. ++ // Unfortunately there's no generic ugfx conversion from system time to milliseconds, ++ // so let's do it in a platform dependent way. ++ ++ // On windows the system ticks is the same as milliseconds anyway ++ if (sleep_time != TIME_INFINITE) { ++ sleep_time = ST2MS(sleep_time); ++ } ++#endif ++ geventEventWait(&event_listener, sleep_time); + } +#ifdef LCD_ENABLE + gdispCloseFont(state.font_fixed5x8); + gdispCloseFont(state.font_dejavusansbold12); +#endif ++ ++ return 0; +} + +void visualizer_init(void) { +#ifdef LCD_ENABLE + gfxInit(); +#endif + +#ifdef LCD_BACKLIGHT_ENABLE + lcd_backlight_init(); +#endif + +#ifdef USE_SERIAL_LINK + add_remote_objects(remote_objects, sizeof(remote_objects) / sizeof(remote_object_t*) ); +#endif ++ ++#ifdef LCD_ENABLE ++ LCD_DISPLAY = get_lcd_display(); ++#endif ++#ifdef LED_ENABLE ++ LED_DISPLAY = get_led_display(); ++#endif ++ + // We are using a low priority thread, the idea is to have it run only + // when the main thread is sleeping during the matrix scanning - chEvtObjectInit(&layer_changed_event); - (void)chThdCreateStatic(visualizerThreadStack, sizeof(visualizerThreadStack), ++ gfxThreadCreate(visualizerThreadStack, sizeof(visualizerThreadStack), + VISUALIZER_THREAD_PRIORITY, visualizerThread, NULL); +} + +void update_status(bool changed) { + if (changed) { - chEvtBroadcast(&layer_changed_event); ++ GSourceListener* listener = geventGetSourceListener((GSourceHandle)¤t_status, NULL); ++ if (listener) { ++ geventSendEvent(listener); ++ } + } +#ifdef USE_SERIAL_LINK + static systime_t last_update = 0; + systime_t current_update = chVTGetSystemTimeX(); + systime_t delta = current_update - last_update; + if (changed || delta > MS2ST(10)) { + last_update = current_update; + visualizer_keyboard_status_t* r = begin_write_current_status(); + *r = current_status; + end_write_current_status(); + } +#endif +} + +void visualizer_update(uint32_t default_state, uint32_t state, uint32_t leds) { + // Note that there's a small race condition here, the thread could read + // a state where one of these are set but not the other. But this should + // not really matter as it will be fixed during the next loop step. + // Alternatively a mutex could be used instead of the volatile variables + + bool changed = false; +#ifdef USE_SERIAL_LINK + if (is_serial_link_connected ()) { + visualizer_keyboard_status_t* new_status = read_current_status(); + if (new_status) { + if (!same_status(¤t_status, new_status)) { + changed = true; + current_status = *new_status; + } + } + } + else { +#else + { +#endif + visualizer_keyboard_status_t new_status = { + .layer = state, + .default_layer = default_state, + .leds = leds, + .suspended = current_status.suspended, + }; + if (!same_status(¤t_status, &new_status)) { + changed = true; + current_status = new_status; + } + } + update_status(changed); +} + +void visualizer_suspend(void) { + current_status.suspended = true; + update_status(true); +} + +void visualizer_resume(void) { + current_status.suspended = false; + update_status(true); +} diff --cc quantum/visualizer/visualizer.h index 22798cda6,000000000..45cfa9aa9 mode 100644,000000..100644 --- a/quantum/visualizer/visualizer.h +++ b/quantum/visualizer/visualizer.h @@@ -1,131 -1,0 +1,149 @@@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Fred Sundvik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef VISUALIZER_H +#define VISUALIZER_H +#include +#include +#include + +#ifdef LCD_ENABLE +#include "gfx.h" +#endif + +#ifdef LCD_BACKLIGHT_ENABLE +#include "lcd_backlight.h" +#endif + +// This need to be called once at the start +void visualizer_init(void); +// This should be called at every matrix scan +void visualizer_update(uint32_t default_state, uint32_t state, uint32_t leds); +// This should be called when the keyboard goes to suspend state +void visualizer_suspend(void); +// This should be called when the keyboard wakes up from suspend state +void visualizer_resume(void); + - // If you need support for more than 8 keyframes per animation, you can change this - #define MAX_VISUALIZER_KEY_FRAMES 8 ++// These functions are week, so they can be overridden by the keyboard ++// if needed ++GDisplay* get_lcd_display(void); ++GDisplay* get_led_display(void); ++ ++// For emulator builds, this function need to be implemented ++#ifdef EMULATOR ++void draw_emulator(void); ++#endif ++ ++// If you need support for more than 16 keyframes per animation, you can change this ++#define MAX_VISUALIZER_KEY_FRAMES 16 + +struct keyframe_animation_t; + +typedef struct { + uint32_t layer; + uint32_t default_layer; + uint32_t leds; // See led.h for available statuses + bool suspended; +} visualizer_keyboard_status_t; + +// The state struct is used by the various keyframe functions +// It's also used for setting the LCD color and layer text +// from the user customized code +typedef struct visualizer_state_t { + // The user code should primarily be modifying these + uint32_t target_lcd_color; + const char* layer_text; + + // The user visualizer(and animation functions) can read these + visualizer_keyboard_status_t status; + + // These are used by the animation functions + uint32_t current_lcd_color; + uint32_t prev_lcd_color; +#ifdef LCD_ENABLE + font_t font_fixed5x8; + font_t font_dejavusansbold12; +#endif +} visualizer_state_t; + +// Any custom keyframe function should have this signature +// return true to get continuous updates, otherwise you will only get one +// update per frame +typedef bool (*frame_func)(struct keyframe_animation_t*, visualizer_state_t*); + +// Represents a keyframe animation, so fields are internal to the system +// while others are meant to be initialized by the user code +typedef struct keyframe_animation_t { + // These should be initialized + int num_frames; + bool loop; + int frame_lengths[MAX_VISUALIZER_KEY_FRAMES]; + frame_func frame_functions[MAX_VISUALIZER_KEY_FRAMES]; + + // Used internally by the system, and can also be read by + // keyframe update functions + int current_frame; + int time_left_in_frame; ++ bool first_update_of_frame; ++ bool last_update_of_frame; + bool need_update; + +} keyframe_animation_t; + ++extern GDisplay* LCD_DISPLAY; ++extern GDisplay* LED_DISPLAY; ++ +void start_keyframe_animation(keyframe_animation_t* animation); +void stop_keyframe_animation(keyframe_animation_t* animation); ++// This runs the next keyframe, but does not update the animation state ++// Useful for crossfades for example ++void run_next_keyframe(keyframe_animation_t* animation, visualizer_state_t* state); + +// Some predefined keyframe functions that can be used by the user code +// Does nothing, useful for adding delays +bool keyframe_no_operation(keyframe_animation_t* animation, visualizer_state_t* state); +// Animates the LCD backlight color between the current color and the target color (of the state) +bool keyframe_animate_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state); +// Sets the backlight color to the target color +bool keyframe_set_backlight_color(keyframe_animation_t* animation, visualizer_state_t* state); +// Displays the layer text centered vertically on the screen +bool keyframe_display_layer_text(keyframe_animation_t* animation, visualizer_state_t* state); +// Displays a bitmap (0/1) of all the currently active layers +bool keyframe_display_layer_bitmap(keyframe_animation_t* animation, visualizer_state_t* state); + +bool keyframe_disable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state); +bool keyframe_enable_lcd_and_backlight(keyframe_animation_t* animation, visualizer_state_t* state); + +// Call this once, when the initial animation has finished, alternatively you can call it +// directly from the initalize_user_visualizer function (the animation can be null) +bool enable_visualization(keyframe_animation_t* animation, visualizer_state_t* state); + - // These two functions have to be implemented by the user ++// These functions have to be implemented by the user +void initialize_user_visualizer(visualizer_state_t* state); +void update_user_visualizer_state(visualizer_state_t* state); +void user_visualizer_suspend(visualizer_state_t* state); +void user_visualizer_resume(visualizer_state_t* state); + + +#endif /* VISUALIZER_H */ diff --cc quantum/visualizer/visualizer.mk index 13c5d3158,000000000..56525ffd9 mode 100644,000000..100644 --- a/quantum/visualizer/visualizer.mk +++ b/quantum/visualizer/visualizer.mk @@@ -1,41 -1,0 +1,61 @@@ +# The MIT License (MIT) +# +# Copyright (c) 2016 Fred Sundvik +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +GFXLIB = $(VISUALIZER_DIR)/ugfx ++SRC += $(VISUALIZER_DIR)/visualizer.c ++UINCDIR += $(GFXINC) $(VISUALIZER_DIR) ++ +ifdef LCD_ENABLE - include $(GFXLIB)/gfx.mk +UDEFS += -DLCD_ENABLE +ULIBS += -lm ++USE_UGFX = yes +endif - SRC += $(GFXSRC) $(VISUALIZER_DIR)/visualizer.c - UINCDIR += $(GFXINC) $(VISUALIZER_DIR) + +ifdef LCD_BACKLIGHT_ENABLE +SRC += $(VISUALIZER_DIR)/lcd_backlight.c ++ifndef EMULATOR +SRC += lcd_backlight_hal.c ++endif +UDEFS += -DLCD_BACKLIGHT_ENABLE +endif + ++ifdef LED_ENABLE ++SRC += $(VISUALIZER_DIR)/led_test.c ++UDEFS += -DLED_ENABLE ++USE_UGFX = yes ++endif ++ ++ifdef USE_UGFX ++include $(GFXLIB)/gfx.mk ++SRC += $(GFXSRC) ++UDEFS += $(patsubst %,-D%,$(patsubst -D%,%,$(GFXDEFS))) ++ULIBS += $(patsubst %,-l%,$(patsubst -l%,%,$(GFXLIBS))) ++endif ++ +ifndef VISUALIZER_USER +VISUALIZER_USER = visualizer_user.c +endif - SRC += $(VISUALIZER_USER) ++SRC += $(VISUALIZER_USER) ++ ++ifdef EMULATOR ++UINCDIR += $(TMK_DIR)/common ++endif