|
| 1 | +#include QMK_KEYBOARD_H |
| 2 | +#include <math.h> |
| 3 | +#include <string.h> |
| 4 | + |
| 5 | +#include "keymap_st7565.h" |
| 6 | +#include "keymap_extra.h" |
| 7 | +#include "bongocat.h" |
| 8 | + |
| 9 | +#define STAT_BUF_SIZE 10 |
| 10 | + |
| 11 | +#define WPM_ANIM_START 20 // Animation idle below this WPM value |
| 12 | +#define WPM_TO_FRAME_TIME(wpm) (2565 - 537 * log(wpm)) // Formula to convert WPM to frame time |
| 13 | + |
| 14 | +ft_display_state_t ft_display_state = { |
| 15 | + .is_on = true, |
| 16 | + .leds = 0xFF, |
| 17 | + .layer = 0xFFFFFFFF, |
| 18 | + .swap_displays = false, |
| 19 | +}; |
| 20 | +static bool clear_display = true; |
| 21 | + |
| 22 | +void set_ft_display_state(ft_display_state_t* new_state) { |
| 23 | + if (!same_ft_display_state(&ft_display_state, new_state)) { |
| 24 | + memcpy(&ft_display_state, new_state, sizeof(ft_display_state)); |
| 25 | + if (ft_display_state.is_on != st7565_is_on()) { |
| 26 | + if (ft_display_state.is_on) { |
| 27 | + st7565_on(); |
| 28 | + } else { |
| 29 | + st7565_off(); |
| 30 | + } |
| 31 | + } |
| 32 | + clear_display = true; |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +bool same_ft_display_state(ft_display_state_t *a, ft_display_state_t *b) { |
| 37 | + if (a->is_on == b->is_on && |
| 38 | + a->leds == b->leds && |
| 39 | + a->layer == b->layer && |
| 40 | + a->layer == b->layer && |
| 41 | + a->swap_displays == b->swap_displays) { |
| 42 | + return true; |
| 43 | + } |
| 44 | + return false; |
| 45 | +} |
| 46 | + |
| 47 | +/* LCD backlight code copied from quantum/visualizer/lcd_backlight.c */ |
| 48 | +static uint8_t current_hue = 0; |
| 49 | +static uint8_t current_saturation = 0; |
| 50 | +static uint8_t current_intensity = 0; |
| 51 | +static uint8_t current_brightness = 255; |
| 52 | + |
| 53 | +// This code is based on Brian Neltner's blogpost and example code |
| 54 | +// "Why every LED light should be using HSI colorspace". |
| 55 | +// http://blog.saikoled.com/post/43693602826/why-every-led-light-should-be-using-hsi |
| 56 | +static void hsi_to_rgb(float h, float s, float i, uint16_t* r_out, uint16_t* g_out, uint16_t* b_out) { |
| 57 | + unsigned int r, g, b; |
| 58 | + h = fmodf(h, 360.0f); // cycle h around to 0-360 degrees |
| 59 | + h = 3.14159f * h / 180.0f; // Convert to radians. |
| 60 | + s = s > 0.0f ? (s < 1.0f ? s : 1.0f) : 0.0f; // clamp s and i to interval [0,1] |
| 61 | + i = i > 0.0f ? (i < 1.0f ? i : 1.0f) : 0.0f; |
| 62 | + |
| 63 | + // Math! Thanks in part to Kyle Miller. |
| 64 | + if (h < 2.09439f) { |
| 65 | + r = 65535.0f * i / 3.0f * (1.0f + s * cos(h) / cosf(1.047196667f - h)); |
| 66 | + g = 65535.0f * i / 3.0f * (1.0f + s * (1.0f - cosf(h) / cos(1.047196667f - h))); |
| 67 | + b = 65535.0f * i / 3.0f * (1.0f - s); |
| 68 | + } else if (h < 4.188787) { |
| 69 | + h = h - 2.09439; |
| 70 | + g = 65535.0f * i / 3.0f * (1.0f + s * cosf(h) / cosf(1.047196667f - h)); |
| 71 | + b = 65535.0f * i / 3.0f * (1.0f + s * (1.0f - cosf(h) / cosf(1.047196667f - h))); |
| 72 | + r = 65535.0f * i / 3.0f * (1.0f - s); |
| 73 | + } else { |
| 74 | + h = h - 4.188787; |
| 75 | + b = 65535.0f * i / 3.0f * (1.0f + s * cosf(h) / cosf(1.047196667f - h)); |
| 76 | + r = 65535.0f * i / 3.0f * (1.0f + s * (1.0f - cosf(h) / cosf(1.047196667f - h))); |
| 77 | + g = 65535.0f * i / 3.0f * (1.0f - s); |
| 78 | + } |
| 79 | + *r_out = r > 65535 ? 65535 : r; |
| 80 | + *g_out = g > 65535 ? 65535 : g; |
| 81 | + *b_out = b > 65535 ? 65535 : b; |
| 82 | +} |
| 83 | + |
| 84 | +static void lcd_backlight_color(uint8_t hue, uint8_t saturation, uint8_t intensity) { |
| 85 | + uint16_t r, g, b; |
| 86 | + float hue_f = 360.0f * (float)hue / 255.0f; |
| 87 | + float saturation_f = (float)saturation / 255.0f; |
| 88 | + float intensity_f = (float)intensity / 255.0f; |
| 89 | + intensity_f *= (float)current_brightness / 255.0f; |
| 90 | + hsi_to_rgb(hue_f, saturation_f, intensity_f, &r, &g, &b); |
| 91 | + current_hue = hue; |
| 92 | + current_saturation = saturation; |
| 93 | + current_intensity = intensity; |
| 94 | + ergodox_infinity_lcd_color(r, g, b); |
| 95 | +} |
| 96 | + |
| 97 | +static void lcd_backlight_brightness(uint8_t b) { |
| 98 | + current_brightness = b; |
| 99 | + lcd_backlight_color(current_hue, current_saturation, current_intensity); |
| 100 | +} |
| 101 | +/* End of copied LCD backlight code */ |
| 102 | + |
| 103 | +static uint8_t prev_brightness = 0; |
| 104 | + |
| 105 | +void st7565_on_user(void) { |
| 106 | + ft_display_state.is_on = true; |
| 107 | + lcd_backlight_brightness(prev_brightness); |
| 108 | +} |
| 109 | + |
| 110 | +void st7565_off_user(void) { |
| 111 | + ft_display_state.is_on = false; |
| 112 | + prev_brightness = current_brightness; |
| 113 | + lcd_backlight_brightness(0); |
| 114 | +} |
| 115 | + |
| 116 | +void set_hand_swap(bool do_swap) { |
| 117 | + ft_display_state.swap_displays = do_swap; |
| 118 | + clear_display = true; |
| 119 | +} |
| 120 | + |
| 121 | +static inline void write_led_state(uint8_t *row, uint8_t *col, uint8_t led, const char *text_P) { |
| 122 | + if (ft_display_state.leds & (1u << led)) { |
| 123 | + const size_t length = strlen_P(text_P); |
| 124 | + if (*col + length > st7565_max_chars()) { |
| 125 | + st7565_advance_page(true); |
| 126 | + st7565_write_P(PSTR(" "), false); |
| 127 | + *col = 2; |
| 128 | + *row += 1; |
| 129 | + } |
| 130 | + st7565_write_P(text_P, false); |
| 131 | + st7565_write_char(' ', false); |
| 132 | + *col += length + 1; |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | +void draw_right(const char *layer_text_P) { |
| 137 | + st7565_write_ln_P(layer_text_P, false); |
| 138 | + if (strlen_P(layer_text_P) < st7565_max_chars()) { |
| 139 | + st7565_advance_page(true); |
| 140 | + } |
| 141 | + |
| 142 | + st7565_write_char(0x2A, false); |
| 143 | + st7565_write_char(' ', false); |
| 144 | + uint8_t row = 2; |
| 145 | + uint8_t col = 2; |
| 146 | + write_led_state(&row, &col, USB_LED_NUM_LOCK, PSTR("Num")); |
| 147 | + write_led_state(&row, &col, USB_LED_CAPS_LOCK, PSTR("Caps")); |
| 148 | + write_led_state(&row, &col, USB_LED_SCROLL_LOCK, PSTR("Scroll")); |
| 149 | + write_led_state(&row, &col, USB_LED_COMPOSE, PSTR("Compose")); |
| 150 | + write_led_state(&row, &col, USB_LED_KANA, PSTR("Kana")); |
| 151 | + while (row++ <= 3) { |
| 152 | + st7565_advance_page(true); |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +void draw_left(void) { |
| 157 | + char stat_buf[STAT_BUF_SIZE]; |
| 158 | + |
| 159 | + const uint8_t* wpm_frame = bongocat[0]; |
| 160 | +#ifdef WPM_ENABLE |
| 161 | + uint8_t wpm = get_current_wpm(); |
| 162 | + |
| 163 | + st7565_advance_page(true); |
| 164 | + st7565_advance_page(true); |
| 165 | + st7565_write_P(PSTR("WPM: "), false); |
| 166 | + snprintf(stat_buf, STAT_BUF_SIZE, "%3u", wpm); |
| 167 | + st7565_write_ln(stat_buf, false); |
| 168 | + |
| 169 | + st7565_write_P(PSTR("Max: "), false); |
| 170 | + snprintf(stat_buf, STAT_BUF_SIZE, "%3u", get_max_wpm()); |
| 171 | + st7565_write_ln(stat_buf, false); |
| 172 | + |
| 173 | + static uint16_t wpm_anim_timer = 0; |
| 174 | + static uint8_t wpm_anim_at_frame = 1; |
| 175 | + if (wpm >= WPM_ANIM_START) { |
| 176 | + wpm_frame = bongocat[wpm_anim_at_frame]; |
| 177 | + |
| 178 | + if (timer_elapsed(wpm_anim_timer) >= WPM_TO_FRAME_TIME(wpm)) { |
| 179 | + wpm_anim_at_frame = 3 - wpm_anim_at_frame; |
| 180 | + wpm_anim_timer = timer_read(); |
| 181 | + } |
| 182 | + } |
| 183 | +#endif |
| 184 | + |
| 185 | +debug_enable = true; |
| 186 | + uint8_t x = ST7565_DISPLAY_WIDTH - BONGOCAT_WIDTH; |
| 187 | + uint8_t y = 0; |
| 188 | + for (uint8_t byte = 0; byte < BONGOCAT_LENGTH; byte++) { |
| 189 | + uint8_t pixels = pgm_read_byte(wpm_frame + byte); |
| 190 | + for (uint8_t mask = (1u << 7); mask > 0; mask >>= 1) { |
| 191 | + st7565_write_pixel(x, y, (pixels & mask) == 0); |
| 192 | + if (++x > ST7565_DISPLAY_WIDTH) { |
| 193 | + x = ST7565_DISPLAY_WIDTH - BONGOCAT_WIDTH; |
| 194 | + y++; |
| 195 | + break; |
| 196 | + } |
| 197 | + } |
| 198 | + } |
| 199 | + |
| 200 | +#ifdef LED_MATRIX_ENABLE |
| 201 | + if (ft_display_state.layer & (1 << FN_LAYER)) { |
| 202 | + st7565_set_cursor(0, 0); |
| 203 | + uint8_t brightness = led_matrix_get_val(); |
| 204 | + uint8_t backlight_level = 0; |
| 205 | + if (led_matrix_is_enabled()) { |
| 206 | + backlight_level = brightness * 100.0 / UINT8_MAX; |
| 207 | + } |
| 208 | + st7565_write_P(PSTR("Backlight: "), false); |
| 209 | + snprintf(stat_buf, STAT_BUF_SIZE, "%3u%%", backlight_level); |
| 210 | + st7565_write(stat_buf, false); |
| 211 | + } |
| 212 | +#endif |
| 213 | +} |
| 214 | + |
| 215 | +void st7565_task_user(void) { |
| 216 | + if (is_keyboard_master()) { |
| 217 | + ft_display_state.is_on = st7565_is_on(); |
| 218 | + ft_display_state.leds = host_keyboard_leds(); |
| 219 | + ft_display_state.layer = layer_state; |
| 220 | + } |
| 221 | + |
| 222 | + if (ft_display_state.is_on) { |
| 223 | +#ifdef LED_MATRIX_ENABLE |
| 224 | + lcd_backlight_brightness(led_matrix_get_val()); |
| 225 | +#endif |
| 226 | + |
| 227 | + const char *layer_text_P; |
| 228 | + if (ft_display_state.layer & (1 << FN_LAYER)) { |
| 229 | + lcd_backlight_color(170, 255, 255); |
| 230 | + if (ft_display_state.layer & (1 << SWAP_LAYER)) { |
| 231 | + layer_text_P = PSTR("Fn Swap On Space"); |
| 232 | + } else { |
| 233 | + layer_text_P = PSTR("Fn"); |
| 234 | + } |
| 235 | + } else if (ft_display_state.layer & (1 << SWAP_LAYER)) { |
| 236 | + lcd_backlight_color(128, 255, 255); |
| 237 | + layer_text_P = PSTR("Swap On Space"); |
| 238 | + } else { |
| 239 | + lcd_backlight_color(0, 0, 255); |
| 240 | + layer_text_P = PSTR("Default"); |
| 241 | + } |
| 242 | + |
| 243 | + bool is_left = is_keyboard_left(); |
| 244 | + if (ft_display_state.swap_displays) { is_left = !is_left; } |
| 245 | + |
| 246 | + if (clear_display) { |
| 247 | + st7565_clear(); |
| 248 | + clear_display = false; |
| 249 | + } |
| 250 | + |
| 251 | + if (is_left) { |
| 252 | + draw_left(); |
| 253 | + } else { |
| 254 | + draw_right(layer_text_P); |
| 255 | + } |
| 256 | + } |
| 257 | +} |
0 commit comments