qmk_userspace/users/default/hlc_tft_display/hlc_tft_display.c
2024-10-10 16:40:48 +02:00

344 lines
No EOL
13 KiB
C

// Copyright 2024 splitkb.com (support@splitkb.com)
// SPDX-License-Identifier: GPL-2.0-or-later
#include QMK_KEYBOARD_H
#include "halcyon.h"
#include "hlc_tft_display.h"
#include "config.h"
#include "qp.h"
#include "qp_comms.h"
#include "qp_surface.h"
#include "qp_st77xx_opcodes.h"
#include "split_util.h"
#include <time.h>
// Fonts mono2
#include "graphics/fonts/Retron2000-27.qff.h"
#include "graphics/fonts/Retron2000-underline-27.qff.h"
// Numbers mono2
#include "graphics/numbers/0.qgf.h"
#include "graphics/numbers/1.qgf.h"
#include "graphics/numbers/2.qgf.h"
#include "graphics/numbers/3.qgf.h"
#include "graphics/numbers/4.qgf.h"
#include "graphics/numbers/5.qgf.h"
#include "graphics/numbers/6.qgf.h"
#include "graphics/numbers/7.qgf.h"
#include "graphics/numbers/8.qgf.h"
#include "graphics/numbers/9.qgf.h"
#include "graphics/numbers/undef.qgf.h"
static const char *caps = "Caps";
static const char *num = "Num";
static const char *scroll = "Scroll";
static painter_font_handle_t Retron27;
static painter_font_handle_t Retron27_underline;
static painter_image_handle_t layer_number;
backlight_config_t backlight_config;
static uint16_t lcd_surface_fb[135*240];
bool backlight_off = false;
int color_value = 0;
led_t last_led_usb_state = {0};
layer_state_t last_layer_state = {0};
#define GRID_WIDTH 27
#define GRID_HEIGHT 48
#define CELL_SIZE 4 // Cell size excluding outline
#define OUTLINE_SIZE 1
// Define the probability factor for initial alive cells
#define INITIAL_ALIVE_PROBABILITY 0.2 // 20% chance of being alive
bool grid[GRID_HEIGHT][GRID_WIDTH]; // Current state
bool new_grid[GRID_HEIGHT][GRID_WIDTH]; // Next state
bool changed_grid[GRID_HEIGHT][GRID_WIDTH]; // Tracks changed cells
void init_grid() {
// Initialize grid with alive cells
for (int y = 0; y < GRID_HEIGHT; y++) {
for (int x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = (rand() < INITIAL_ALIVE_PROBABILITY * RAND_MAX); // Use probability factor
changed_grid[y][x] = true; // Mark all as changed initially
}
}
}
void draw_grid() {
uint8_t hue = 0; // Hue for alive cells
uint8_t sat = 0; // Saturation for alive cells
uint8_t val_dead = 0; // Brightness for dead cells
for (int y = 0; y < GRID_HEIGHT; y++) {
for (int x = 0; x < GRID_WIDTH; x++) {
if (changed_grid[y][x]) { // Only update changed cells
uint16_t left = x * (CELL_SIZE + OUTLINE_SIZE);
uint16_t top = y * (CELL_SIZE + OUTLINE_SIZE);
uint16_t right = left + CELL_SIZE + OUTLINE_SIZE;
uint16_t bottom = top + CELL_SIZE + OUTLINE_SIZE;
// Draw the outline
qp_rect(lcd_surface, left, top, right, bottom, hue, sat, val_dead, true);
// Draw the filled cell inside the outline if it's alive
if (grid[y][x]) {
switch (color_value) {
case 0:
qp_rect(lcd_surface, left + OUTLINE_SIZE, top + OUTLINE_SIZE, right - OUTLINE_SIZE, bottom - OUTLINE_SIZE, HSV_LAYER_0, true);
break;
case 1:
qp_rect(lcd_surface, left + OUTLINE_SIZE, top + OUTLINE_SIZE, right - OUTLINE_SIZE, bottom - OUTLINE_SIZE, HSV_LAYER_1, true);
break;
case 2:
qp_rect(lcd_surface, left + OUTLINE_SIZE, top + OUTLINE_SIZE, right - OUTLINE_SIZE, bottom - OUTLINE_SIZE, HSV_LAYER_2, true);
break;
case 3:
qp_rect(lcd_surface, left + OUTLINE_SIZE, top + OUTLINE_SIZE, right - OUTLINE_SIZE, bottom - OUTLINE_SIZE, HSV_LAYER_3, true);
break;
case 4:
qp_rect(lcd_surface, left + OUTLINE_SIZE, top + OUTLINE_SIZE, right - OUTLINE_SIZE, bottom - OUTLINE_SIZE, HSV_LAYER_4, true);
break;
case 5:
qp_rect(lcd_surface, left + OUTLINE_SIZE, top + OUTLINE_SIZE, right - OUTLINE_SIZE, bottom - OUTLINE_SIZE, HSV_LAYER_5, true);
break;
case 6:
qp_rect(lcd_surface, left + OUTLINE_SIZE, top + OUTLINE_SIZE, right - OUTLINE_SIZE, bottom - OUTLINE_SIZE, HSV_LAYER_6, true);
break;
case 7:
qp_rect(lcd_surface, left + OUTLINE_SIZE, top + OUTLINE_SIZE, right - OUTLINE_SIZE, bottom - OUTLINE_SIZE, HSV_LAYER_7, true);
break;
default:
qp_rect(lcd_surface, left + OUTLINE_SIZE, top + OUTLINE_SIZE, right - OUTLINE_SIZE, bottom - OUTLINE_SIZE, HSV_LAYER_UNDEF, true);
}
}
}
}
}
}
void update_grid() {
for (int y = 0; y < GRID_HEIGHT; y++) {
for (int x = 0; x < GRID_WIDTH; x++) {
int alive_neighbors = 0;
// Count alive neighbors
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dy == 0 && dx == 0) continue; // Skip the current cell
int ny = y + dy;
int nx = x + dx;
if (ny >= 0 && ny < GRID_HEIGHT && nx >= 0 && nx < GRID_WIDTH) {
alive_neighbors += grid[ny][nx];
}
}
}
// Apply the rules of the Game of Life
if (grid[y][x]) {
// Any live cell with two or three live neighbours survives.
new_grid[y][x] = (alive_neighbors == 2 || alive_neighbors == 3);
} else {
// Any dead cell with exactly three live neighbours becomes a live cell.
new_grid[y][x] = (alive_neighbors == 3);
}
// Track changed cells
changed_grid[y][x] = (grid[y][x] != new_grid[y][x]);
}
}
// Copy new grid state to current grid
for (int y = 0; y < GRID_HEIGHT; y++) {
for (int x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = new_grid[y][x];
}
}
}
// Function to add a cluster of cells at a random position
void add_cell_cluster() {
int cluster_size = 3; // Size of the cluster (3x3)
int x = rand() % (GRID_WIDTH - cluster_size);
int y = rand() % (GRID_HEIGHT - cluster_size);
for (int dy = 0; dy < cluster_size; dy++) {
for (int dx = 0; dx < cluster_size; dx++) {
bool is_alive = rand() % 2; // Randomly choose between 0 and 1
grid[y + dy][x + dx] = is_alive; // Set the cell to be alive
changed_grid[y + dy][x + dx] = true; // Mark the cell as changed
}
}
}
void update_display(void) {
static bool first_run_led = false;
static bool first_run_layer = false;
if( first_run_layer == false) {
// Load fonts
Retron27 = qp_load_font_mem(font_Retron2000_27);
Retron27_underline = qp_load_font_mem(font_Retron2000_underline_27);
}
if(last_led_usb_state.raw != host_keyboard_led_state().raw || first_run_led == false) {
led_t led_usb_state = host_keyboard_led_state();
led_usb_state.caps_lock ? qp_drawtext_recolor(lcd_surface, 5, LCD_HEIGHT - Retron27->line_height * 3 - 15, Retron27_underline, caps, HSV_CAPS_ON, HSV_BLACK) : qp_drawtext_recolor(lcd_surface, 5, LCD_HEIGHT - Retron27->line_height * 3 - 15, Retron27, caps, HSV_CAPS_OFF, HSV_BLACK);
led_usb_state.num_lock ? qp_drawtext_recolor(lcd_surface, 5, LCD_HEIGHT - Retron27->line_height * 2 - 10, Retron27_underline, num, HSV_NUM_ON, HSV_BLACK) : qp_drawtext_recolor(lcd_surface, 5, LCD_HEIGHT - Retron27->line_height * 2 - 10, Retron27, num, HSV_NUM_OFF, HSV_BLACK);
led_usb_state.scroll_lock ? qp_drawtext_recolor(lcd_surface, 5, LCD_HEIGHT - Retron27->line_height - 5, Retron27_underline, scroll, HSV_SCROLL_ON, HSV_BLACK) : qp_drawtext_recolor(lcd_surface, 5, LCD_HEIGHT - Retron27->line_height - 5, Retron27, scroll, HSV_SCROLL_OFF, HSV_BLACK);
last_led_usb_state = led_usb_state;
first_run_led = true;
}
if(last_layer_state != layer_state || first_run_layer == false) {
switch (get_highest_layer(layer_state|default_layer_state)) {
case 0:
layer_number = qp_load_image_mem(gfx_0);
qp_drawimage_recolor(lcd_surface, 5, 5, layer_number, HSV_LAYER_0, HSV_BLACK);
break;
case 1:
layer_number = qp_load_image_mem(gfx_1);
qp_drawimage_recolor(lcd_surface, 5, 5, layer_number, HSV_LAYER_1, HSV_BLACK);
break;
case 2:
layer_number = qp_load_image_mem(gfx_2);
qp_drawimage_recolor(lcd_surface, 5, 5, layer_number, HSV_LAYER_2, HSV_BLACK);
break;
case 3:
layer_number = qp_load_image_mem(gfx_3);
qp_drawimage_recolor(lcd_surface, 5, 5, layer_number, HSV_LAYER_3, HSV_BLACK);
break;
case 4:
layer_number = qp_load_image_mem(gfx_4);
qp_drawimage_recolor(lcd_surface, 5, 5, layer_number, HSV_LAYER_4, HSV_BLACK);
break;
case 5:
layer_number = qp_load_image_mem(gfx_5);
qp_drawimage_recolor(lcd_surface, 5, 5, layer_number, HSV_LAYER_5, HSV_BLACK);
break;
case 6:
layer_number = qp_load_image_mem(gfx_6);
qp_drawimage_recolor(lcd_surface, 5, 5, layer_number, HSV_LAYER_6, HSV_BLACK);
break;
case 7:
layer_number = qp_load_image_mem(gfx_7);
qp_drawimage_recolor(lcd_surface, 5, 5, layer_number, HSV_LAYER_7, HSV_BLACK);
break;
default:
layer_number = qp_load_image_mem(gfx_undef);
qp_drawimage_recolor(lcd_surface, 5, 5, layer_number, HSV_LAYER_UNDEF, HSV_BLACK);
}
qp_close_image(layer_number);
last_layer_state = layer_state;
first_run_layer = true;
}
}
// Quantum function
void suspend_power_down_kb(void) {
// backlight_suspend(); Disabled as it gives some weird behavior when rebooting the host
qp_power(lcd, false);
suspend_power_down_user();
}
// Quantum function
void suspend_wakeup_init_kb(void) {
qp_power(lcd, true);
// backlight_wakeup(); Disabled as it gives some weird behavior when rebooting the host
suspend_wakeup_init_user();
}
// Timeout handling
void backlight_wakeup(void) {
backlight_off = false;
backlight_enable();
}
// Timeout handling
void backlight_suspend(void) {
backlight_off = true;
backlight_disable();
}
// Called from halcyon.c
bool module_post_init_kb(void) {
setPinOutput(LCD_RST_PIN);
writePinHigh(LCD_RST_PIN);
// Initialise the LCD
lcd = qp_st7789_make_spi_device(LCD_WIDTH, LCD_HEIGHT, LCD_CS_PIN, LCD_DC_PIN, LCD_RST_PIN, LCD_SPI_DIVISOR, LCD_SPI_MODE);
qp_init(lcd, LCD_ROTATION);
qp_set_viewport_offsets(lcd, LCD_OFFSET_X, LCD_OFFSET_Y);
// Initialise surface
lcd_surface = qp_make_rgb565_surface(LCD_WIDTH, LCD_HEIGHT, lcd_surface_fb);
qp_init(lcd_surface, LCD_ROTATION);
// Turn on the LCD and clear the display
qp_power(lcd, true);
qp_rect(lcd, 0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1, HSV_BLACK, true);
qp_flush(lcd);
// Turn on backlight
backlight_enable();
if(!module_post_init_user()) { return false; }
return true;
}
// Called from halcyon.c
bool display_module_housekeeping_task_kb(bool second_display) {
// Backlight feature
if (backlight_off && last_input_activity_elapsed() <= QUANTUM_PAINTER_DISPLAY_TIMEOUT) {
backlight_wakeup();
}
if (!backlight_off && last_input_activity_elapsed() > QUANTUM_PAINTER_DISPLAY_TIMEOUT) {
backlight_suspend();
}
if(!display_module_housekeeping_task_user(second_display)) { return false; }
if(second_display) {
static uint32_t last_draw = 0;
static bool second_display_set = false;
static uint32_t previous_matrix_activity_time = 0;
if(!second_display_set) {
srand(time(NULL));
init_grid();
color_value = rand() % 8;
second_display_set = true;
}
if (timer_elapsed32(last_draw) >= 100) { // Throttle to 10 fps
draw_grid();
update_grid();
if (previous_matrix_activity_time != last_matrix_activity_time()) {
color_value = rand() % 8;
add_cell_cluster();
previous_matrix_activity_time = last_matrix_activity_time();
}
last_draw = timer_read32();
}
}
// Update display information (layers, numlock, etc.)
if(!second_display) {
update_display();
}
// Move surface to lcd
qp_surface_draw(lcd_surface, lcd, 0, 0, 0);
return true;
}