diff options
Diffstat (limited to 'quantum/visualizer')
| -rw-r--r-- | quantum/visualizer/LICENSE.md | 29 | ||||
| -rw-r--r-- | quantum/visualizer/lcd_backlight.c | 89 | ||||
| -rw-r--r-- | quantum/visualizer/lcd_backlight.h | 47 | ||||
| -rw-r--r-- | quantum/visualizer/lcd_backlight_keyframes.c | 77 | ||||
| -rw-r--r-- | quantum/visualizer/lcd_backlight_keyframes.h | 30 | ||||
| -rw-r--r-- | quantum/visualizer/lcd_keyframes.c | 188 | ||||
| -rw-r--r-- | quantum/visualizer/lcd_keyframes.h | 39 | ||||
| -rw-r--r-- | quantum/visualizer/led_keyframes.c | 143 | ||||
| -rw-r--r-- | quantum/visualizer/led_keyframes.h | 44 | ||||
| -rw-r--r-- | quantum/visualizer/readme.md | 18 | ||||
| -rw-r--r-- | quantum/visualizer/resources/lcd_logo.c | 61 | ||||
| -rw-r--r-- | quantum/visualizer/resources/lcd_logo.png | bin | 0 -> 490 bytes | |||
| -rw-r--r-- | quantum/visualizer/resources/resources.h | 27 | ||||
| -rw-r--r-- | quantum/visualizer/visualizer.c | 502 | ||||
| -rw-r--r-- | quantum/visualizer/visualizer.h | 155 | ||||
| -rw-r--r-- | quantum/visualizer/visualizer.mk | 73 | ||||
| -rw-r--r-- | quantum/visualizer/visualizer_keyframes.c | 23 | ||||
| -rw-r--r-- | quantum/visualizer/visualizer_keyframes.h | 26 | 
18 files changed, 1571 insertions, 0 deletions
| diff --git a/quantum/visualizer/LICENSE.md b/quantum/visualizer/LICENSE.md new file mode 100644 index 0000000000..22d4c3f08b --- /dev/null +++ b/quantum/visualizer/LICENSE.md @@ -0,0 +1,29 @@ +The files in this project are licensed under the MIT license +It uses the following libraries +uGFX - with it's own license, see the license.html file in the uGFX subfolder for more information +tmk_core - is indirectly used and not included in the repository. It's licensed under the GPLv2 license +Chibios - which is used by tmk_core is licensed under GPLv3. + +Therefore the effective license for any project using the library is GPLv3 + +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. diff --git a/quantum/visualizer/lcd_backlight.c b/quantum/visualizer/lcd_backlight.c new file mode 100644 index 0000000000..6cd996f75e --- /dev/null +++ b/quantum/visualizer/lcd_backlight.c @@ -0,0 +1,89 @@ +/* +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 "lcd_backlight.h" +#include <math.h> + +static uint8_t current_hue = 0; +static uint8_t current_saturation = 0; +static uint8_t current_intensity = 0; +static uint8_t current_brightness = 0; + +void lcd_backlight_init(void) { +    lcd_backlight_hal_init(); +    lcd_backlight_color(current_hue, current_saturation, current_intensity); +} + +// This code is based on Brian Neltner's blogpost and example code +// "Why every LED light should be using HSI colorspace". +// http://blog.saikoled.com/post/43693602826/why-every-led-light-should-be-using-hsi +static void hsi_to_rgb(float h, float s, float i, uint16_t* r_out, uint16_t* g_out, uint16_t* b_out) { +    unsigned int r, g, b; +    h = fmodf(h, 360.0f); // cycle h around to 0-360 degrees +    h = 3.14159f * h / 180.0f; // Convert to radians. +    s = s > 0.0f ? (s < 1.0f ? s : 1.0f) : 0.0f; // clamp s and i to interval [0,1] +    i = i > 0.0f ? (i < 1.0f ? i : 1.0f) : 0.0f; + +    // Math! Thanks in part to Kyle Miller. +    if(h < 2.09439f) { +        r = 65535.0f * i/3.0f *(1.0f + s * cos(h) / cosf(1.047196667f - h)); +        g = 65535.0f * i/3.0f *(1.0f + s *(1.0f - cosf(h) / cos(1.047196667f - h))); +        b = 65535.0f * i/3.0f *(1.0f - s); +    } else if(h < 4.188787) { +        h = h - 2.09439; +        g = 65535.0f * i/3.0f *(1.0f + s * cosf(h) / cosf(1.047196667f - h)); +        b = 65535.0f * i/3.0f *(1.0f + s * (1.0f - cosf(h) / cosf(1.047196667f - h))); +        r = 65535.0f * i/3.0f *(1.0f - s); +    } else { +        h = h - 4.188787; +        b = 65535.0f*i/3.0f * (1.0f + s * cosf(h) / cosf(1.047196667f - h)); +        r = 65535.0f*i/3.0f * (1.0f + s * (1.0f - cosf(h) / cosf(1.047196667f - h))); +        g = 65535.0f*i/3.0f * (1.0f - s); +    } +    *r_out = r > 65535 ? 65535 : r; +    *g_out = g > 65535 ? 65535 : g; +    *b_out = b > 65535 ? 65535 : b; +} + +void lcd_backlight_color(uint8_t hue, uint8_t saturation, uint8_t intensity) { +    uint16_t r, g, b; +    float hue_f = 360.0f * (float)hue / 255.0f; +    float saturation_f = (float)saturation / 255.0f; +    float intensity_f = (float)intensity / 255.0f; +    intensity_f *= (float)current_brightness / 255.0f; +    hsi_to_rgb(hue_f, saturation_f, intensity_f, &r, &g, &b); +	current_hue = hue; +	current_saturation = saturation; +	current_intensity = intensity; +	lcd_backlight_hal_color(r, g, b); +} + +void lcd_backlight_brightness(uint8_t b) { +    current_brightness = b; +    lcd_backlight_color(current_hue, current_saturation, current_intensity); +} + +uint8_t lcd_get_backlight_brightness(void) { +	return current_brightness; +} diff --git a/quantum/visualizer/lcd_backlight.h b/quantum/visualizer/lcd_backlight.h new file mode 100644 index 0000000000..95d7a07b46 --- /dev/null +++ b/quantum/visualizer/lcd_backlight.h @@ -0,0 +1,47 @@ +/* +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 LCD_BACKLIGHT_H_ +#define LCD_BACKLIGHT_H_ +#include "stdint.h" + +// Helper macros for storing hue, staturation and intensity as unsigned integers +#define LCD_COLOR(hue, saturation, intensity) (hue << 16 | saturation << 8 | intensity) +#define LCD_HUE(color) ((color >> 16) & 0xFF) +#define LCD_SAT(color) ((color >> 8) & 0xFF) +#define LCD_INT(color) (color & 0xFF) + +static inline uint32_t change_lcd_color_intensity(uint32_t color, uint8_t new_intensity) { +    return (color & 0xFFFFFF00) | new_intensity; +} + +void lcd_backlight_init(void); +void lcd_backlight_color(uint8_t hue, uint8_t saturation, uint8_t intensity); +void lcd_backlight_brightness(uint8_t b); +uint8_t lcd_get_backlight_brightness(void); + +void lcd_backlight_hal_init(void); +void lcd_backlight_hal_color(uint16_t r, uint16_t g, uint16_t b); + +#endif /* LCD_BACKLIGHT_H_ */ diff --git a/quantum/visualizer/lcd_backlight_keyframes.c b/quantum/visualizer/lcd_backlight_keyframes.c new file mode 100644 index 0000000000..8436d4e3dd --- /dev/null +++ b/quantum/visualizer/lcd_backlight_keyframes.c @@ -0,0 +1,77 @@ +/* Copyright 2017 Fred Sundvik + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include "lcd_backlight_keyframes.h" + +bool backlight_keyframe_animate_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 backlight_keyframe_set_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; +} + +bool backlight_keyframe_disable(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)animation; +    (void)state; +    lcd_backlight_hal_color(0, 0, 0); +    return false; +} + +bool backlight_keyframe_enable(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)animation; +    (void)state; +    lcd_backlight_color(LCD_HUE(state->current_lcd_color), +        LCD_SAT(state->current_lcd_color), +        LCD_INT(state->current_lcd_color)); +    return false; +} diff --git a/quantum/visualizer/lcd_backlight_keyframes.h b/quantum/visualizer/lcd_backlight_keyframes.h new file mode 100644 index 0000000000..e1c125cf91 --- /dev/null +++ b/quantum/visualizer/lcd_backlight_keyframes.h @@ -0,0 +1,30 @@ +/* Copyright 2017 Fred Sundvik + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef QUANTUM_VISUALIZER_LCD_BACKLIGHT_KEYFRAMES_H_ +#define QUANTUM_VISUALIZER_LCD_BACKLIGHT_KEYFRAMES_H_ + +#include "visualizer.h" + +// Animates the LCD backlight color between the current color and the target color (of the state) +bool backlight_keyframe_animate_color(keyframe_animation_t* animation, visualizer_state_t* state); +// Sets the backlight color to the target color +bool backlight_keyframe_set_color(keyframe_animation_t* animation, visualizer_state_t* state); + +bool backlight_keyframe_disable(keyframe_animation_t* animation, visualizer_state_t* state); +bool backlight_keyframe_enable(keyframe_animation_t* animation, visualizer_state_t* state); + +#endif /* QUANTUM_VISUALIZER_LCD_BACKLIGHT_KEYFRAMES_H_ */ diff --git a/quantum/visualizer/lcd_keyframes.c b/quantum/visualizer/lcd_keyframes.c new file mode 100644 index 0000000000..82e4184d2c --- /dev/null +++ b/quantum/visualizer/lcd_keyframes.c @@ -0,0 +1,188 @@ +/* Copyright 2017 Fred Sundvik + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include "lcd_keyframes.h" +#include <string.h> +#include "action_util.h" +#include "led.h" +#include "resources/resources.h" + +bool lcd_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); +    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 lcd_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); +    return false; +} + +static void format_mods_bitmap_string(uint8_t mods, char* buffer) { +    *buffer = ' '; +    ++buffer; + +    for (int i = 0; i<8; i++) +    { +        uint32_t mask = (1u << i); +        if (mods & mask) { +            *buffer = '1'; +        } else { +            *buffer = '0'; +        } +        ++buffer; + +        if (i==3) { +            *buffer = ' '; +            ++buffer; +        } +    } +    *buffer = 0; +} + +bool lcd_keyframe_display_mods_bitmap(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)animation; + +    const char* title = "Modifier states"; +    const char* mods_header = " CSAG CSAG "; +    char status_buffer[12]; + +    gdispClear(White); +    gdispDrawString(0, 0, title, state->font_fixed5x8, Black); +    gdispDrawString(0, 10, mods_header, state->font_fixed5x8, Black); +    format_mods_bitmap_string(state->status.mods, status_buffer); +    gdispDrawString(0, 20, status_buffer, state->font_fixed5x8, Black); + +    return false; +} + +#define LED_STATE_STRING_SIZE sizeof("NUM CAPS SCRL COMP KANA") + +static void get_led_state_string(char* output, visualizer_state_t* state) { +    uint8_t pos = 0; + +    if (state->status.leds & (1u << USB_LED_NUM_LOCK)) { +       memcpy(output + pos, "NUM ", 4); +       pos += 4; +    } +    if (state->status.leds & (1u << USB_LED_CAPS_LOCK)) { +       memcpy(output + pos, "CAPS ", 5); +       pos += 5; +    } +    if (state->status.leds & (1u << USB_LED_SCROLL_LOCK)) { +       memcpy(output + pos, "SCRL ", 5); +       pos += 5; +    } +    if (state->status.leds & (1u << USB_LED_COMPOSE)) { +       memcpy(output + pos, "COMP ", 5); +       pos += 5; +    } +    if (state->status.leds & (1u << USB_LED_KANA)) { +       memcpy(output + pos, "KANA", 4); +       pos += 4; +    } +    output[pos] = 0; +} + +bool lcd_keyframe_display_led_states(keyframe_animation_t* animation, visualizer_state_t* state) +{ +    (void)animation; +    char output[LED_STATE_STRING_SIZE]; +    get_led_state_string(output, state); +    gdispClear(White); +    gdispDrawString(0, 10, output, state->font_dejavusansbold12, Black); +    return false; +} + +bool lcd_keyframe_display_layer_and_led_states(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)animation; +    gdispClear(White); +    uint8_t y = 10; +    if (state->status.leds) { +        char output[LED_STATE_STRING_SIZE]; +        get_led_state_string(output, state); +        gdispDrawString(0, 1, output, state->font_dejavusansbold12, Black); +        y = 17; +    } +    gdispDrawString(0, y, state->layer_text, state->font_dejavusansbold12, Black); +    return false; +} + +bool lcd_keyframe_draw_logo(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    (void)animation; +    // Read the uGFX documentation for information how to use the displays +    // http://wiki.ugfx.org/index.php/Main_Page +    gdispClear(White); + +    // You can use static variables for things that can't be found in the animation +    // or state structs, here we use the image + +    //gdispGBlitArea is a tricky function to use since it supports blitting part of the image +    // if you have full screen image, then just use 128 and 32 for both source and target dimensions +    gdispGBlitArea(GDISP, 0, 0, 128, 32, 0, 0, 128, (pixel_t*)resource_lcd_logo); + +    return false; +} + + +bool lcd_keyframe_disable(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)animation; +    (void)state; +    gdispSetPowerMode(powerOff); +    return false; +} + +bool lcd_keyframe_enable(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)animation; +    (void)state; +    gdispSetPowerMode(powerOn); +    return false; +} diff --git a/quantum/visualizer/lcd_keyframes.h b/quantum/visualizer/lcd_keyframes.h new file mode 100644 index 0000000000..2e912b4c73 --- /dev/null +++ b/quantum/visualizer/lcd_keyframes.h @@ -0,0 +1,39 @@ +/* Copyright 2017 Fred Sundvik + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef QUANTUM_VISUALIZER_LCD_KEYFRAMES_H_ +#define QUANTUM_VISUALIZER_LCD_KEYFRAMES_H_ + +#include "visualizer.h" + +// Displays the layer text centered vertically on the screen +bool lcd_keyframe_display_layer_text(keyframe_animation_t* animation, visualizer_state_t* state); +// Displays a bitmap (0/1) of all the currently active layers +bool lcd_keyframe_display_layer_bitmap(keyframe_animation_t* animation, visualizer_state_t* state); +// Displays a bitmap (0/1) of all the currently active mods +bool lcd_keyframe_display_mods_bitmap(keyframe_animation_t* animation, visualizer_state_t* state); +// Displays the keyboard led states (CAPS (Caps lock), NUM (Num lock), SCRL (Scroll lock), COMP (Compose), KANA) +bool lcd_keyframe_display_led_states(keyframe_animation_t* animation, visualizer_state_t* state); +// Displays both the layer text and the led states +bool lcd_keyframe_display_layer_and_led_states(keyframe_animation_t* animation, visualizer_state_t* state); +// Displays the QMK logo on the LCD screen +bool lcd_keyframe_draw_logo(keyframe_animation_t* animation, visualizer_state_t* state); + +bool lcd_keyframe_disable(keyframe_animation_t* animation, visualizer_state_t* state); +bool lcd_keyframe_enable(keyframe_animation_t* animation, visualizer_state_t* state); + + +#endif /* QUANTUM_VISUALIZER_LCD_KEYFRAMES_H_ */ diff --git a/quantum/visualizer/led_keyframes.c b/quantum/visualizer/led_keyframes.c new file mode 100644 index 0000000000..7e6e5d1ab9 --- /dev/null +++ b/quantum/visualizer/led_keyframes.c @@ -0,0 +1,143 @@ +/* +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 "gfx.h" +#include "math.h" +#include "led_keyframes.h" + +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 LED_NUM_ROWS +#define NUM_COLS LED_NUM_COLS + +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 = M_PI * 2.0f; +    float normalized_index = (1.0f - index / (num - 1.0f)) * two_pi; +    float x = t * two_pi + normalized_index; +    float v = 0.5 * (cosf(x) + 1.0f); +    return (uint8_t)(255.0f * v); +} + +bool led_keyframe_fade_in_all(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    keyframe_fade_all_leds_from_to(animation, 0, 255); +    return true; +} + +bool led_keyframe_fade_out_all(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    keyframe_fade_all_leds_from_to(animation, 255, 0); +    return true; +} + +bool led_keyframe_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 led_keyframe_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;i<NUM_ROWS;i++) { +        for (int j=0;j<NUM_COLS;j++) { +            dest[i*NUM_COLS + j] = gdispGGetPixelColor(LED_DISPLAY, j, i); +        } +    } +} +bool led_keyframe_crossfade(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    if (animation->first_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<NUM_ROWS;i++) { +        for (int j=0;j<NUM_COLS;j++) { +            color_t color  = LUMA2COLOR(fade_led_color(animation, crossfade_start_frame[i][j], crossfade_end_frame[i][j])); +            gdispGDrawPixel(LED_DISPLAY, j, i, color); +        } +    } +    return true; +} + +bool led_keyframe_mirror_orientation(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    (void)animation; +    gdispGSetOrientation(LED_DISPLAY, GDISP_ROTATE_180); +    return false; +} + +bool led_keyframe_normal_orientation(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    (void)animation; +    gdispGSetOrientation(LED_DISPLAY, GDISP_ROTATE_0); +    return false; +} + +bool led_keyframe_disable(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    (void)animation; +    gdispGSetPowerMode(LED_DISPLAY, powerOff); +    return false; +} + +bool led_keyframe_enable(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    (void)animation; +    gdispGSetPowerMode(LED_DISPLAY, powerOn); +    return false; +} diff --git a/quantum/visualizer/led_keyframes.h b/quantum/visualizer/led_keyframes.h new file mode 100644 index 0000000000..a59a4f37d1 --- /dev/null +++ b/quantum/visualizer/led_keyframes.h @@ -0,0 +1,44 @@ +/* +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 LED_KEYFRAMES_H +#define LED_KEYFRAMES_H + +#include "visualizer.h" + +bool led_keyframe_fade_in_all(keyframe_animation_t* animation, visualizer_state_t* state); +bool led_keyframe_fade_out_all(keyframe_animation_t* animation, visualizer_state_t* state); +bool led_keyframe_left_to_right_gradient(keyframe_animation_t* animation, visualizer_state_t* state); +bool led_keyframe_top_to_bottom_gradient(keyframe_animation_t* animation, visualizer_state_t* state); +bool led_keyframe_crossfade(keyframe_animation_t* animation, visualizer_state_t* state); +bool led_keyframe_mirror_orientation(keyframe_animation_t* animation, visualizer_state_t* state); +bool led_keyframe_normal_orientation(keyframe_animation_t* animation, visualizer_state_t* state); + +bool led_keyframe_disable(keyframe_animation_t* animation, visualizer_state_t* state); +bool led_keyframe_enable(keyframe_animation_t* animation, visualizer_state_t* state); + +extern keyframe_animation_t led_test_animation; + + +#endif /* LED_KEYFRAMES_H */ diff --git a/quantum/visualizer/readme.md b/quantum/visualizer/readme.md new file mode 100644 index 0000000000..545ba22707 --- /dev/null +++ b/quantum/visualizer/readme.md @@ -0,0 +1,18 @@ +# A visualization library for the TMK keyboard firmware + +This library is designed to work together with the [TMK keyboard firmware](https://github.com/tmk/tmk_keyboard). Currently it only works for [Chibios](http://www.chibios.org/) + flavors, but it would be possible to add support for other configurations as well. The LCD display functionality is provided by the [uGFX library](http://www.ugfx.org/).  + +## To use this library as a user +You can and should modify the visualizer\_user.c file. Check the comments in the file for more information. + +## To add this library to custom keyboard projects + +1. Add tmk_visualizer as a submodule to your project +1. Set VISUALIZER_DIR in the main keyboard project makefile to point to the submodule +1. Define LCD\_ENABLE and/or LCD\_BACKLIGHT\_ENABLE, to enable support +1. Include the visualizer.mk make file +1. Copy the files in the example\_integration folder to your keyboard project +1. All other files than the callback.c file are included automatically, so you will need to add callback.c to your makefile manually. If you already have a similar file in your project, you can just copy the functions instead of the whole file. +1. Edit the files to match your hardware. You might might want to read the Chibios and UGfx documentation, for more information. +1. If you enable LCD support you might also have to write a custom uGFX display driver, check the uGFX documentation for that. You probably also want to enable SPI support in your Chibios configuration. diff --git a/quantum/visualizer/resources/lcd_logo.c b/quantum/visualizer/resources/lcd_logo.c new file mode 100644 index 0000000000..d1a0ffa7f9 --- /dev/null +++ b/quantum/visualizer/resources/lcd_logo.c @@ -0,0 +1,61 @@ +/* Copyright 2017 Fred Sundvik + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include "resources.h" + + +// To generate an image array like this +// Ensure the image is 128 x 32 or smaller +// Convert the bitmap to a C array using a program like http://www.riuson.com/lcd-image-converter/ +// Ensure the the conversion process produces a monochrome format array - 1 bit/pixel, left to right, top to bottom +// Update array in the source code with the C array produced by the conversion program + +// The image below is generated from lcd_logo.png +const uint8_t resource_lcd_logo[512] = { +    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, +    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, +    0x00, 0xf8, 0xfe, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x38, 0x38, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x38, 0x38, 0x38, 0x06, 0x29, 0x41, 0x24, 0x52, 0x24, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x38, 0x38, 0x38, 0x09, 0x55, 0x42, 0xaa, 0xaa, 0xaa, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x38, 0x38, 0x38, 0x09, 0x55, 0x82, 0x28, 0xaa, 0xae, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x38, 0x38, 0x38, 0x09, 0x55, 0x43, 0x28, 0xaa, 0xaa, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x38, 0x38, 0x38, 0x0a, 0x55, 0x42, 0x28, 0xaa, 0xaa, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x38, 0x38, 0x38, 0x05, 0x45, 0x42, 0x28, 0x89, 0x4a, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x18, 0x38, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x1c, 0x38, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x0e, 0x38, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x03, 0xff, 0x80, 0x04, 0x45, 0x14, 0xa4, 0x92, 0x83, 0x52, 0x22, 0x22, 0x36, 0x00, 0x00, +    0x00, 0x00, 0x38, 0x00, 0x0a, 0xaa, 0xaa, 0xaa, 0xba, 0x84, 0x55, 0x55, 0x57, 0x45, 0x00, 0x00, +    0x00, 0x00, 0x38, 0x00, 0x08, 0xaa, 0xaa, 0xaa, 0x92, 0xb2, 0x55, 0x55, 0x42, 0x65, 0x00, 0x00, +    0x00, 0x00, 0x38, 0x00, 0x08, 0xaa, 0xaa, 0xaa, 0x92, 0x81, 0x56, 0x65, 0x42, 0x45, 0x00, 0x00, +    0x00, 0x00, 0x38, 0x00, 0x0a, 0xaa, 0xaa, 0xaa, 0x92, 0x81, 0x54, 0x45, 0x42, 0x45, 0x00, 0x00, +    0x00, 0x00, 0x38, 0x00, 0x04, 0x48, 0xa2, 0x4a, 0x89, 0x06, 0x24, 0x42, 0x41, 0x36, 0x00, 0x00, +    0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +    0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 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, 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, 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/quantum/visualizer/resources/lcd_logo.png b/quantum/visualizer/resources/lcd_logo.pngBinary files differ new file mode 100644 index 0000000000..6cf26fc678 --- /dev/null +++ b/quantum/visualizer/resources/lcd_logo.png diff --git a/quantum/visualizer/resources/resources.h b/quantum/visualizer/resources/resources.h new file mode 100644 index 0000000000..1ea27a5364 --- /dev/null +++ b/quantum/visualizer/resources/resources.h @@ -0,0 +1,27 @@ +/* Copyright 2017 Fred Sundvik + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef QUANTUM_VISUALIZER_RESOURCES_RESOURCES_H_ +#define QUANTUM_VISUALIZER_RESOURCES_RESOURCES_H_ + +#include <stdint.h> + +#ifdef LCD_ENABLE +extern const uint8_t resource_lcd_logo[]; +#endif + + +#endif /* QUANTUM_VISUALIZER_RESOURCES_RESOURCES_H_ */ diff --git a/quantum/visualizer/visualizer.c b/quantum/visualizer/visualizer.c new file mode 100644 index 0000000000..cc99d1e3b6 --- /dev/null +++ b/quantum/visualizer/visualizer.c @@ -0,0 +1,502 @@ +/* +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 "config.h" +#include "visualizer.h" +#include <string.h> +#ifdef PROTOCOL_CHIBIOS +#include "ch.h" +#endif + +#include "gfx.h" + +#ifdef LCD_BACKLIGHT_ENABLE +#include "lcd_backlight.h" +#endif + +//#define DEBUG_VISUALIZER + +#ifdef DEBUG_VISUALIZER +#include "debug.h" +#else +#include "nodebug.h" +#endif + +#ifdef SERIAL_LINK_ENABLE +#include "serial_link/protocol/transport.h" +#include "serial_link/system/serial_link.h" +#endif + +#include "action_util.h" + +// 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, +#ifdef BACKLIGHT_ENABLE +    .backlight_level = 0, +#endif +    .mods = 0xFF, +    .suspended = false, +#ifdef VISUALIZER_USER_DATA_SIZE +    .user_data = {0} +#endif +}; + +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->mods == status2->mods && +        status1->leds == status2->leds && +        status1->suspended == status2->suspended +#ifdef BACKLIGHT_ENABLE +        && status1->backlight_level == status2->backlight_level +#endif +#ifdef VISUALIZER_USER_DATA_SIZE +        && memcmp(status1->user_data, status2->user_data, VISUALIZER_USER_DATA_SIZE) == 0 +#endif +    ; +} + +static bool visualizer_enabled = false; + +#ifdef VISUALIZER_USER_DATA_SIZE +static uint8_t user_data[VISUALIZER_USER_DATA_SIZE]; +#endif + +#define MAX_SIMULTANEOUS_ANIMATIONS 4 +static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {}; + +#ifdef SERIAL_LINK_ENABLE +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; + +#ifdef LCD_DISPLAY_NUMBER +__attribute__((weak)) +GDisplay* get_lcd_display(void) { +    return gdispGetDisplay(LCD_DISPLAY_NUMBER); +} +#endif + +#ifdef LED_DISPLAY_NUMBER +__attribute__((weak)) +GDisplay* get_led_display(void) { +    return gdispGetDisplay(LED_DISPLAY_NUMBER); +} +#endif + +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;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) { +        if (animations[i] == animation) { +            return; +        } +        if (free_index == -1 && animations[i] == NULL) { +           free_index=i; +        } +    } +    if (free_index!=-1) { +        animations[free_index] = animation; +    } +} + +void stop_keyframe_animation(keyframe_animation_t* animation) { +    animation->current_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;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) { +        if (animations[i] == animation) { +            animations[i] = NULL; +            return; +        } +    } +} + +void stop_all_keyframe_animations(void) { +    for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) { +        if (animations[i]) { +            animations[i]->current_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 uint8_t get_num_running_animations(void) { +    uint8_t count = 0; +    for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) { +        count += animations[i] ? 1 : 0; +    } +    return count; +} + +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; +    } + +    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); +} + +// TODO: Optimize the stack size, this is probably way too big +static DECLARE_THREAD_STACK(visualizerThreadStack, 1024); +static DECLARE_THREAD_FUNCTION(visualizerThread, arg) { +    (void)arg; + +    GListener event_listener; +    geventListenerInit(&event_listener); +    geventAttachSource(&event_listener, (GSourceHandle)¤t_status, 0); + +    visualizer_keyboard_status_t initial_status = { +        .default_layer = 0xFFFFFFFF, +        .layer = 0xFFFFFFFF, +        .mods = 0xFF, +        .leds = 0xFFFFFFFF, +        .suspended = false, +    #ifdef VISUALIZER_USER_DATA_SIZE +        .user_data = {0}, +    #endif +    }; + +    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 + +    systemticks_t sleep_time = TIME_INFINITE; +    systemticks_t current_time = gfxSystemTicks(); +    bool force_update = true; + +    while(true) { +        systemticks_t new_time = gfxSystemTicks(); +        systemticks_t delta = new_time - current_time; +        current_time = new_time; +        bool enabled = visualizer_enabled; +        if (force_update || !same_status(&state.status, ¤t_status)) { +            force_update = false; +    #if BACKLIGHT_ENABLE +            if(current_status.backlight_level != state.status.backlight_level) { +                if (current_status.backlight_level != 0) { +                    gdispGSetPowerMode(LED_DISPLAY, powerOn); +                    uint16_t percent = (uint16_t)current_status.backlight_level * 100 / BACKLIGHT_LEVELS; +                    gdispGSetBacklight(LED_DISPLAY, percent); +                } +                else { +                    gdispGSetPowerMode(LED_DISPLAY, powerOff); +                } +            } +    #endif +            if (visualizer_enabled) { +                if (current_status.suspended) { +                    stop_all_keyframe_animations(); +                    visualizer_enabled = false; +                    state.status = current_status; +                    user_visualizer_suspend(&state); +                } +                else { +                    visualizer_keyboard_status_t prev_status = state.status; +                    state.status = current_status; +                    update_user_visualizer_state(&state, &prev_status); +                } +                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<MAX_SIMULTANEOUS_ANIMATIONS;i++) { +            if (animations[i]) { +                update_keyframe_animation(animations[i], &state, delta, &sleep_time); +            } +        } +#ifdef BACKLIGHT_ENABLE +        gdispGFlush(LED_DISPLAY); +#endif + +#ifdef LCD_ENABLE +        gdispGFlush(LCD_DISPLAY); +#endif + +#ifdef EMULATOR +        draw_emulator(); +#endif +        // Enable the visualizer when the startup or the suspend animation has finished +        if (!visualizer_enabled && state.status.suspended == false && get_num_running_animations() == 0) { +            visualizer_enabled = true; +            force_update = true; +            sleep_time = 0; +        } + +        systemticks_t after_update = gfxSystemTicks(); +        unsigned update_delta = after_update - current_time; +        if (sleep_time != TIME_INFINITE) { +            if (sleep_time > 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); +#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) { +    gfxInit(); + +  #ifdef LCD_BACKLIGHT_ENABLE +    lcd_backlight_init(); +  #endif + +  #ifdef SERIAL_LINK_ENABLE +    add_remote_objects(remote_objects, sizeof(remote_objects) / sizeof(remote_object_t*) ); +  #endif + +  #ifdef LCD_ENABLE +    LCD_DISPLAY = get_lcd_display(); +  #endif + +  #ifdef BACKLIGHT_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 +  gfxThreadCreate(visualizerThreadStack, sizeof(visualizerThreadStack), +                  VISUALIZER_THREAD_PRIORITY, visualizerThread, NULL); +} + +void update_status(bool changed) { +    if (changed) { +        GSourceListener* listener = geventGetSourceListener((GSourceHandle)¤t_status, NULL); +        if (listener) { +            geventSendEvent(listener); +        } +    } +#ifdef SERIAL_LINK_ENABLE +    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 +} + +uint8_t visualizer_get_mods() { +  uint8_t mods = get_mods(); + +#ifndef NO_ACTION_ONESHOT +  if (!has_oneshot_mods_timed_out()) { +    mods |= get_oneshot_mods(); +  } +#endif   +  return mods; +} + +#ifdef VISUALIZER_USER_DATA_SIZE +void visualizer_set_user_data(void* u) { +    memcpy(user_data, u, VISUALIZER_USER_DATA_SIZE); +} +#endif + +void visualizer_update(uint32_t default_state, uint32_t state, uint8_t mods, 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 SERIAL_LINK_ENABLE +    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, +            .mods = mods, +            .leds = leds, +#ifdef BACKLIGHT_ENABLE +            .backlight_level = current_status.backlight_level, +#endif +            .suspended = current_status.suspended, +        }; +#ifdef VISUALIZER_USER_DATA_SIZE +       memcpy(new_status.user_data, user_data, VISUALIZER_USER_DATA_SIZE); +#endif +        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); +} + +#ifdef BACKLIGHT_ENABLE +void backlight_set(uint8_t level) { +    current_status.backlight_level = level; +    update_status(true); +} +#endif diff --git a/quantum/visualizer/visualizer.h b/quantum/visualizer/visualizer.h new file mode 100644 index 0000000000..90ecdcbaea --- /dev/null +++ b/quantum/visualizer/visualizer.h @@ -0,0 +1,155 @@ +/* +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 <stdlib.h> +#include <stdint.h> +#include <stdbool.h> + +#include "config.h" +#include "gfx.h" + +#ifdef LCD_BACKLIGHT_ENABLE +#include "lcd_backlight.h" +#endif + +#ifdef BACKLIGHT_ENABLE +#include "backlight.h" +#endif + +// use this function to merge both real_mods and oneshot_mods in a uint16_t +uint8_t visualizer_get_mods(void); + +// 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, uint8_t mods, 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); + +// 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 +    uint8_t mods; +    bool suspended; +#ifdef BACKLIGHT_ENABLE +    uint8_t backlight_level; +#endif +#ifdef VISUALIZER_USER_DATA_SIZE +    uint8_t user_data[VISUALIZER_USER_DATA_SIZE]; +#endif +} 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); + +// The master can set userdata which will be transferred to the slave +#ifdef VISUALIZER_USER_DATA_SIZE +void visualizer_set_user_data(void* user_data); +#endif + +// These functions have to be implemented by the user +// Called regularly each time the state has changed (but not every scan loop) +void update_user_visualizer_state(visualizer_state_t* state, visualizer_keyboard_status_t* prev_status); +// Called when the computer goes to suspend, will also stop calling update_user_visualizer_state +void user_visualizer_suspend(visualizer_state_t* state); +// You have to start at least one animation as a response to the following two functions +// When the animation has finished the visualizer will resume normal operation and start calling the +// update_user_visualizer_state again +// Called when the keyboard boots up +void initialize_user_visualizer(visualizer_state_t* state); +// Called when the computer resumes from a suspend +void user_visualizer_resume(visualizer_state_t* state); + +#endif /* VISUALIZER_H */ diff --git a/quantum/visualizer/visualizer.mk b/quantum/visualizer/visualizer.mk new file mode 100644 index 0000000000..0f7d8636cf --- /dev/null +++ b/quantum/visualizer/visualizer.mk @@ -0,0 +1,73 @@ +# 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. + +SRC += $(VISUALIZER_DIR)/visualizer.c \ +	$(VISUALIZER_DIR)/visualizer_keyframes.c +EXTRAINCDIRS += $(GFXINC) $(VISUALIZER_DIR) +GFXLIB = $(LIB_PATH)/ugfx +VPATH += $(VISUALIZER_PATH) + +OPT_DEFS += -DVISUALIZER_ENABLE + +ifdef LCD_ENABLE +OPT_DEFS += -DLCD_ENABLE +ULIBS += -lm +endif + +ifeq ($(strip $(LCD_ENABLE)), yes) +SRC += $(VISUALIZER_DIR)/lcd_backlight.c +SRC += $(VISUALIZER_DIR)/lcd_keyframes.c +SRC += $(VISUALIZER_DIR)/lcd_backlight_keyframes.c +# Note, that the linker will strip out any resources that are not actually in use +SRC += $(VISUALIZER_DIR)/resources/lcd_logo.c +OPT_DEFS += -DLCD_BACKLIGHT_ENABLE +endif + +ifeq ($(strip $(BACKLIGHT_ENABLE)), yes) +SRC += $(VISUALIZER_DIR)/led_keyframes.c +endif + +include $(GFXLIB)/gfx.mk +GFXSRC := $(patsubst $(TOP_DIR)/%,%,$(GFXSRC)) +GFXDEFS := $(patsubst %,-D%,$(patsubst -D%,%,$(GFXDEFS))) + +ifneq ("$(wildcard $(KEYMAP_PATH)/visualizer.c)","") +    SRC += keyboards/$(KEYBOARD)/keymaps/$(KEYMAP)/visualizer.c +else  +    ifeq ("$(wildcard $(SUBPROJECT_PATH)/keymaps/$(KEYMAP)/visualizer.c)","") +        ifeq ("$(wildcard $(SUBPROJECT_PATH)/visualizer.c)","") +            ifeq ("$(wildcard $(KEYBOARD_PATH)/visualizer.c)","") +$(error "visualizer.c" not found") +            else +               SRC += keyboards/$(KEYBOARD)/visualizer.c +            endif +        else +            SRC += keyboards/$(KEYBOARD)/$(SUBPROJECT)/visualizer.c +        endif +    else +        SRC += keyboards/$(KEYBOARD)/$(SUBPROJECT)/keymaps/$(KEYMAP)/visualizer.c +    endif +endif + +ifdef EMULATOR +UINCDIR += $(TMK_DIR)/common +endif diff --git a/quantum/visualizer/visualizer_keyframes.c b/quantum/visualizer/visualizer_keyframes.c new file mode 100644 index 0000000000..8f6a7e15a4 --- /dev/null +++ b/quantum/visualizer/visualizer_keyframes.c @@ -0,0 +1,23 @@ +/* Copyright 2017 Fred Sundvik + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include "visualizer_keyframes.h" + +bool keyframe_no_operation(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)animation; +    (void)state; +    return false; +} diff --git a/quantum/visualizer/visualizer_keyframes.h b/quantum/visualizer/visualizer_keyframes.h new file mode 100644 index 0000000000..9ef7653c5e --- /dev/null +++ b/quantum/visualizer/visualizer_keyframes.h @@ -0,0 +1,26 @@ +/* Copyright 2017 Fred Sundvik + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef QUANTUM_VISUALIZER_VISUALIZER_KEYFRAMES_H_ +#define QUANTUM_VISUALIZER_VISUALIZER_KEYFRAMES_H_ + +#include "visualizer.h" + +// 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); + +#endif /* QUANTUM_VISUALIZER_VISUALIZER_KEYFRAMES_H_ */ | 
