diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 598ff77..a156988 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( - SRCS "main.c" "menu.c" "fpga_test.c" + SRCS "main.c" "menu.c" "fpga_test.c" "pax_keyboard.c" "button_wrapper.c" "system_wrapper.c" "appfs_wrapper.c" "graphics_wrapper.c" INCLUDE_DIRS "." "include" ) diff --git a/main/appfs_wrapper.c b/main/appfs_wrapper.c new file mode 100644 index 0000000..f299f71 --- /dev/null +++ b/main/appfs_wrapper.c @@ -0,0 +1,76 @@ +#include "appfs_wrapper.h" +#include "esp_sleep.h" +#include "soc/rtc.h" +#include "soc/rtc_cntl_reg.h" + +esp_err_t appfs_init(void) { + return appfsInit(APPFS_PART_TYPE, APPFS_PART_SUBTYPE); +} + +uint8_t* load_file_to_ram(FILE* fd, size_t* fsize) { + fseek(fd, 0, SEEK_END); + *fsize = ftell(fd); + fseek(fd, 0, SEEK_SET); + uint8_t* file = malloc(*fsize); + if (file == NULL) return NULL; + fread(file, *fsize, 1, fd); + return file; +} + +/*void appfs_store_app(void) { + draw_message("Installing app..."); + esp_err_t res; + appfs_handle_t handle; + FILE* app_fd = fopen("/sd/gnuboy.bin", "rb"); + if (app_fd == NULL) { + draw_message("Failed to open gnuboy.bin"); + ESP_LOGE(TAG, "Failed to open gnuboy.bin"); + vTaskDelay(100 / portTICK_PERIOD_MS); + return; + } + size_t app_size; + uint8_t* app = load_file_to_ram(app_fd, &app_size); + if (app == NULL) { + draw_message("Failed to load app to RAM"); + ESP_LOGE(TAG, "Failed to load application into RAM"); + vTaskDelay(100 / portTICK_PERIOD_MS); + return; + } + + ESP_LOGI(TAG, "Application size %d", app_size); + + res = appfsCreateFile("gnuboy", app_size, &handle); + if (res != ESP_OK) { + draw_message("Failed to create on AppFS"); + ESP_LOGE(TAG, "Failed to create file on AppFS (%d)", res); + vTaskDelay(100 / portTICK_PERIOD_MS); + free(app); + return; + } + res = appfsWrite(handle, 0, app, app_size); + if (res != ESP_OK) { + draw_message("Failed to write to AppFS"); + ESP_LOGE(TAG, "Failed to write to file on AppFS (%d)", res); + vTaskDelay(100 / portTICK_PERIOD_MS); + free(app); + return; + } + free(app); + ESP_LOGI(TAG, "Application is now stored in AppFS"); + draw_message("App installed!"); + vTaskDelay(100 / portTICK_PERIOD_MS); + return; +}*/ + +void appfs_boot_app(int fd) { + if (fd<0 || fd>255) { + REG_WRITE(RTC_CNTL_STORE0_REG, 0); + } else { + REG_WRITE(RTC_CNTL_STORE0_REG, 0xA5000000|fd); + } + + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); + esp_sleep_enable_timer_wakeup(10); + esp_deep_sleep_start(); +} + diff --git a/main/button_wrapper.c b/main/button_wrapper.c new file mode 100644 index 0000000..6fa2f73 --- /dev/null +++ b/main/button_wrapper.c @@ -0,0 +1,25 @@ +#include "button_wrapper.h" + +xQueueHandle queue; + +void button_handler(uint8_t pin, bool value) { + button_message_t message; + message.button = pin; + message.state = value; + xQueueSend(queue, &message, portMAX_DELAY); +} + +void button_init(PCA9555* aPca9555, xQueueHandle aQueue) { + queue = aQueue; + pca9555_set_interrupt_handler(aPca9555, PCA9555_PIN_BTN_START, button_handler); + pca9555_set_interrupt_handler(aPca9555, PCA9555_PIN_BTN_SELECT, button_handler); + pca9555_set_interrupt_handler(aPca9555, PCA9555_PIN_BTN_MENU, button_handler); + pca9555_set_interrupt_handler(aPca9555, PCA9555_PIN_BTN_HOME, button_handler); + pca9555_set_interrupt_handler(aPca9555, PCA9555_PIN_BTN_JOY_LEFT, button_handler); + pca9555_set_interrupt_handler(aPca9555, PCA9555_PIN_BTN_JOY_PRESS, button_handler); + pca9555_set_interrupt_handler(aPca9555, PCA9555_PIN_BTN_JOY_DOWN, button_handler); + pca9555_set_interrupt_handler(aPca9555, PCA9555_PIN_BTN_JOY_UP, button_handler); + pca9555_set_interrupt_handler(aPca9555, PCA9555_PIN_BTN_JOY_RIGHT, button_handler); + pca9555_set_interrupt_handler(aPca9555, PCA9555_PIN_BTN_BACK, button_handler); + pca9555_set_interrupt_handler(aPca9555, PCA9555_PIN_BTN_ACCEPT, button_handler); +} diff --git a/main/fpga_test.c b/main/fpga_test.c index 113797f..7bdd0ad 100644 --- a/main/fpga_test.c +++ b/main/fpga_test.c @@ -8,7 +8,7 @@ #include "ili9341.h" #include "ice40.h" #include "hardware.h" -#include "button_message.h" +#include "button_wrapper.h" static const char *TAG = "fpga_test"; diff --git a/main/graphics_wrapper.c b/main/graphics_wrapper.c new file mode 100644 index 0000000..e018cac --- /dev/null +++ b/main/graphics_wrapper.c @@ -0,0 +1,25 @@ +#include "graphics_wrapper.h" + +void message_render(pax_buf_t *aBuffer, char* message, float aPosX, float aPosY, float aWidth, float aHeight) { + pax_col_t fgColor = 0xFFFF0000; + pax_col_t bgColor = 0xFFFFD4D4; + pax_clip(aBuffer, aPosX, aPosY, aWidth, aHeight); + pax_simple_rect(aBuffer, bgColor, aPosX, aPosY, aWidth, aHeight); + pax_outline_rect(aBuffer, fgColor, aPosX, aPosY, aWidth, aHeight); + pax_clip(aBuffer, aPosX + 1, aPosY + 1, aWidth - 2, aHeight - 2); + pax_draw_text(aBuffer, fgColor, NULL, 18, aPosX + 1, aPosY + 1, message); + pax_noclip(aBuffer); +} + +esp_err_t graphics_task(pax_buf_t* pax_buffer, ILI9341* ili9341, uint8_t* framebuffer, menu_t* menu, char* message) { + pax_background(pax_buffer, 0xCCCCCC); + if (menu != NULL) { + menu_render(pax_buffer, menu, 10, 10, 320-20, 240-20); + } + + if (message != NULL) { + message_render(pax_buffer, message, 20, 110, 320-40, 20); + } + + return ili9341_write(ili9341, framebuffer); +} diff --git a/main/include/.graphics_wrapper.h.kate-swp b/main/include/.graphics_wrapper.h.kate-swp new file mode 100644 index 0000000..d5b8275 Binary files /dev/null and b/main/include/.graphics_wrapper.h.kate-swp differ diff --git a/main/include/appfs_wrapper.h b/main/include/appfs_wrapper.h new file mode 100644 index 0000000..d85ea10 --- /dev/null +++ b/main/include/appfs_wrapper.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "appfs.h" + +esp_err_t appfs_init(void); +uint8_t* load_file_to_ram(FILE* fd, size_t* fsize); +void appfs_store_app(void); +void appfs_boot_app(int fd); diff --git a/main/include/button_message.h b/main/include/button_message.h deleted file mode 100644 index 7003afb..0000000 --- a/main/include/button_message.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -typedef struct _button_message { - uint8_t button; - bool state; -} button_message_t; diff --git a/main/include/button_wrapper.h b/main/include/button_wrapper.h new file mode 100644 index 0000000..58c1dc5 --- /dev/null +++ b/main/include/button_wrapper.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "hardware.h" + +typedef struct _button_message { + uint8_t button; + bool state; +} button_message_t; + +void button_init(PCA9555* aPca9555, xQueueHandle aQueue); diff --git a/main/include/graphics_wrapper.h b/main/include/graphics_wrapper.h new file mode 100644 index 0000000..87d585e --- /dev/null +++ b/main/include/graphics_wrapper.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +#include "pax_gfx.h" +#include "ili9341.h" +#include "menu.h" + + +esp_err_t graphics_task(pax_buf_t* pax_buffer, ILI9341* ili9341, uint8_t* framebuffer, menu_t* menu, char* message); diff --git a/main/include/menu.h b/main/include/menu.h index 398f0ea..8a721f6 100644 --- a/main/include/menu.h +++ b/main/include/menu.h @@ -37,6 +37,7 @@ bool menu_navigate_to(menu_t* aMenu, size_t aPosition); void menu_navigate_previous(menu_t* aMenu); void menu_navigate_next(menu_t* aMenu); size_t menu_get_position(menu_t* aMenu); +size_t menu_get_length(menu_t* aMenu); void* menu_get_callback_args(menu_t* aMenu, size_t aPosition); void menu_debug(menu_t* aMenu); void menu_render(pax_buf_t *aBuffer, menu_t *aMenu, float aPosX, float aPosY, float aWidth, float aHeight); diff --git a/main/include/pax_keyboard.h b/main/include/pax_keyboard.h new file mode 100644 index 0000000..da431b4 --- /dev/null +++ b/main/include/pax_keyboard.h @@ -0,0 +1,147 @@ +/* + MIT License + + Copyright (c) 2022 Julian Scheffers + + 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 PAX_KEYBOARD_H +#define PAX_KEYBOARD_H + +#include +#include +#include + +// A number of inputs supported by the PAX keyboard. +typedef enum { + // Represents no input being pressed. + PKB_NO_INPUT, + // Movement of the cursor. + PKB_UP, PKB_DOWN, PKB_LEFT, PKB_RIGHT, + // Delete to the left or the selection. Backspace key. + PKB_DELETE_BEFORE, + // Delete to the right or the selection. Delete key. + PKB_DELETE_AFTER, + // Switch between lower case, upper case and symbols. + PKB_MODESELECT, + // Enter a character. + PKB_CHARSELECT, + // The same thing as the shift key. + // Goes between PKB_LOWERCASE and PKB_UPPERCASE or PKB_NUMBERS and PKB_SYMBOLS. + PKB_SHIFT, +} pkb_input_t; + +// The type of keyboard currently selected. +typedef enum { + // Lowercase and ., + PKB_LOWERCASE, + // Uppercase and <> + PBK_UPPERCASE, + // Numbers and symbols 1/2 + PKB_NUMBERS, + // Symbols 2/2 + PKB_SYMBOLS, +} pkb_keyboard_t; + +// The PAX keyboard context used for drawing and alike. +typedef struct { + // Position on screen of the keyboard. + int x, y; + // Maximum size of the keyboard. + int width, height; + + // Content of the keyboard. + char *content; + // Size in bytes of capacity of the content buffer. + size_t content_cap; + + // Starting position of the selection in the text box. + int selection; + // Cursor position of the text box. + int cursor; + + // Cursor position of the keyboard. + int key_x, key_y; + // The currently held input. + pkb_input_t held; + // The time that holding the input started. + int64_t hold_start; + // The last time pkb_press was called. + int64_t last_press; + + // Whether the keyboard is multi-line. + bool multiline; + // Whether the keyboard is in insert mode. + bool insert; + // The board that is currently selected. + pkb_keyboard_t board_sel; + + // The font to use for the keyboard. + pax_font_t *kb_font; + // The font size to use for the keyboard. + float kb_font_size; + // The font to use for the text. + pax_font_t *text_font; + // The font size to use for the text. + float text_font_size; + // The text color to use. + pax_col_t text_col; + // The text color to use when a character is being held down. + pax_col_t sel_text_col; + // The selection color to use. + pax_col_t sel_col; + // The background color to use. + pax_col_t bg_col; + + // Whether something has changed since last draw. + bool dirty; + // Whether the text has changed since last draw. + bool text_dirty; + // Whether the keyboard has changed since last draw. + bool kb_dirty; + // Whether just the selected character has changed since last draw. + bool sel_dirty; + // Previous cursor position of the keyboard. + // Used for sel_dirty. + int last_key_x, last_key_y; + + // Indicates that the input has been accepted. + bool input_accepted; +} pkb_ctx_t; + +// Initialise the context with default settings. +void pkb_init (pax_buf_t *buf, pkb_ctx_t *ctx); +// Free any memory associated with the context. +void pkb_destroy(pkb_ctx_t *ctx); + +// Redraw the complete on-screen keyboard. +void pkb_render (pax_buf_t *buf, pkb_ctx_t *ctx); +// Redraw only the changed parts of the on-screen keyboard. +void pkb_redraw (pax_buf_t *buf, pkb_ctx_t *ctx); + +// The loop that allows input repeating. +void pkb_loop (pkb_ctx_t *ctx); + +// A pressing of the input. +void pkb_press (pkb_ctx_t *ctx, pkb_input_t input); +// A relealing of the input. +void pkb_release(pkb_ctx_t *ctx, pkb_input_t input); + +#endif //PAX_KEYBOARD_H diff --git a/main/include/system_wrapper.h b/main/include/system_wrapper.h new file mode 100644 index 0000000..a29f052 --- /dev/null +++ b/main/include/system_wrapper.h @@ -0,0 +1,3 @@ +#pragma once + +void restart(); diff --git a/main/main.c b/main/main.c index 43c48fa..a2d885d 100644 --- a/main/main.c +++ b/main/main.c @@ -5,7 +5,6 @@ #include #include #include -//#include #include #include #include "hardware.h" @@ -15,33 +14,24 @@ #include "appfs.h" #include "driver_framebuffer.h" -#include "esp_sleep.h" -#include "soc/rtc.h" -#include "soc/rtc_cntl_reg.h" - #include "rp2040.h" #include "fpga_test.h" #include "menu.h" -#include "button_message.h" +#include "button_wrapper.h" +#include "system_wrapper.h" +#include "graphics_wrapper.h" +#include "appfs_wrapper.h" static const char *TAG = "main"; -bool calibrate = true; -bool display_bno_value = false; -ILI9341* ili9341 = NULL; -ICE40* ice40 = NULL; -BNO055* bno055 = NULL; -RP2040* rp2040 = NULL; -uint8_t* framebuffer = NULL; -pax_buf_t* pax_buffer = NULL; -xQueueHandle buttonQueue; - typedef enum action { ACTION_NONE, ACTION_APPFS, ACTION_INSTALLER, + ACTION_SETTINGS, + ACTION_OTA, ACTION_FPGA } menu_action_t; @@ -50,160 +40,102 @@ typedef struct _menu_args { menu_action_t action; } menu_args_t; -void button_handler(uint8_t pin, bool value) { - button_message_t message; - message.button = pin; - message.state = value; - xQueueSend(buttonQueue, &message, portMAX_DELAY); -} - -void button_init() { - PCA9555* pca9555 = get_pca9555(); - pca9555_set_interrupt_handler(pca9555, PCA9555_PIN_BTN_START, button_handler); - pca9555_set_interrupt_handler(pca9555, PCA9555_PIN_BTN_SELECT, button_handler); - pca9555_set_interrupt_handler(pca9555, PCA9555_PIN_BTN_MENU, button_handler); - pca9555_set_interrupt_handler(pca9555, PCA9555_PIN_BTN_HOME, button_handler); - pca9555_set_interrupt_handler(pca9555, PCA9555_PIN_BTN_JOY_LEFT, button_handler); - pca9555_set_interrupt_handler(pca9555, PCA9555_PIN_BTN_JOY_PRESS, button_handler); - pca9555_set_interrupt_handler(pca9555, PCA9555_PIN_BTN_JOY_DOWN, button_handler); - pca9555_set_interrupt_handler(pca9555, PCA9555_PIN_BTN_JOY_UP, button_handler); - pca9555_set_interrupt_handler(pca9555, PCA9555_PIN_BTN_JOY_RIGHT, button_handler); - pca9555_set_interrupt_handler(pca9555, PCA9555_PIN_BTN_BACK, button_handler); - pca9555_set_interrupt_handler(pca9555, PCA9555_PIN_BTN_ACCEPT, button_handler); -} - -void restart() { - for (int i = 3; i >= 0; i--) { - printf("Restarting in %d seconds...\n", i); - vTaskDelay(1000 / portTICK_PERIOD_MS); +void menu_launcher(xQueueHandle buttonQueue, pax_buf_t* pax_buffer, ILI9341* ili9341, uint8_t* framebuffer, menu_action_t* menu_action, appfs_handle_t* appfs_fd) { + menu_t* menu = menu_alloc("Main menu"); + *appfs_fd = APPFS_INVALID_FD; + *menu_action = ACTION_NONE; + + while (1) { + *appfs_fd = appfsNextEntry(*appfs_fd); + if (*appfs_fd == APPFS_INVALID_FD) break; + const char* name = NULL; + appfsEntryInfo(*appfs_fd, &name, NULL); + menu_args_t* args = malloc(sizeof(menu_args_t)); + args->fd = *appfs_fd; + args->action = ACTION_APPFS; + menu_insert_item(menu, name, NULL, (void*) args, -1); } - printf("Restarting now.\n"); - fflush(stdout); - esp_restart(); -} + *appfs_fd = APPFS_INVALID_FD; -void message_render(pax_buf_t *aBuffer, char* message, float aPosX, float aPosY, float aWidth, float aHeight) { - pax_col_t fgColor = 0xFFFF0000; - pax_col_t bgColor = 0xFFFFD4D4; - pax_clip(aBuffer, aPosX, aPosY, aWidth, aHeight); - pax_simple_rect(aBuffer, bgColor, aPosX, aPosY, aWidth, aHeight); - pax_outline_rect(aBuffer, fgColor, aPosX, aPosY, aWidth, aHeight); - pax_clip(aBuffer, aPosX + 1, aPosY + 1, aWidth - 2, aHeight - 2); - pax_draw_text(aBuffer, fgColor, NULL, 18, aPosX + 1, aPosY + 1, message); - pax_noclip(aBuffer); -} + menu_args_t* install_args = malloc(sizeof(menu_args_t)); + install_args->action = ACTION_INSTALLER; + menu_insert_item(menu, "Hatchery", NULL, install_args, -1); + + menu_args_t* settings_args = malloc(sizeof(menu_args_t)); + settings_args->action = ACTION_SETTINGS; + menu_insert_item(menu, "System settings", NULL, settings_args, -1); + + menu_args_t* ota_args = malloc(sizeof(menu_args_t)); + ota_args->action = ACTION_OTA; + menu_insert_item(menu, "Firmware update", NULL, ota_args, -1); + menu_args_t* fpga_args = malloc(sizeof(menu_args_t)); + fpga_args->action = ACTION_FPGA; + menu_insert_item(menu, "FPGA", NULL, fpga_args, -1); -esp_err_t graphics_task(pax_buf_t* buffer, ILI9341* ili9341, uint8_t* framebuffer, menu_t* menu, char* message) { - pax_background(pax_buffer, 0xCCCCCC); - if (menu != NULL) { - menu_render(pax_buffer, menu, 10, 10, 320-20, 240-20); + for (uint8_t i = 0; i < 30; i++) { + menu_args_t* dummy_args = malloc(sizeof(menu_args_t)); + dummy_args->action = ACTION_NONE; + menu_insert_item(menu, "Dummy menu item", NULL, dummy_args, -1); + } + + bool render = true; + menu_args_t* menuArgs = NULL; + + while (1) { + button_message_t buttonMessage = {0}; + if (xQueueReceive(buttonQueue, &buttonMessage, 16 / portTICK_PERIOD_MS) == pdTRUE) { + uint8_t pin = buttonMessage.button; + bool value = buttonMessage.state; + switch(pin) { + case PCA9555_PIN_BTN_JOY_DOWN: + if (value) { + menu_navigate_next(menu); + render = true; + } + break; + case PCA9555_PIN_BTN_JOY_UP: + if (value) { + menu_navigate_previous(menu); + render = true; + } + break; + case PCA9555_PIN_BTN_ACCEPT: + if (value) { + menuArgs = menu_get_callback_args(menu, menu_get_position(menu)); + } + break; + default: + break; + } + } + + if (render) { + graphics_task(pax_buffer, ili9341, framebuffer, menu, NULL); + render = false; + } + + if (menuArgs != NULL) { + *appfs_fd = menuArgs->fd; + *menu_action = menuArgs->action; + graphics_task(pax_buffer, ili9341, framebuffer, menu, "Please wait..."); + break; + } } - if (message != NULL) { - message_render(pax_buffer, message, 20, 110, 320-40, 20); - } - - return ili9341_write(ili9341, framebuffer); -} - -esp_err_t draw_message(char* message) { - pax_background(pax_buffer, 0xFFFFFF); - pax_draw_text(pax_buffer, pax_col_rgb(0,0,0), PAX_FONT_DEFAULT, 18, 0, 0, message); - return ili9341_write(ili9341, framebuffer); -} - -esp_err_t appfs_init(void) { - return appfsInit(APPFS_PART_TYPE, APPFS_PART_SUBTYPE); -} - -uint8_t* load_file_to_ram(FILE* fd, size_t* fsize) { - fseek(fd, 0, SEEK_END); - *fsize = ftell(fd); - fseek(fd, 0, SEEK_SET); - uint8_t* file = malloc(*fsize); - if (file == NULL) return NULL; - fread(file, *fsize, 1, fd); - return file; -} - -void appfs_store_app(void) { - draw_message("Installing app..."); - esp_err_t res; - appfs_handle_t handle; - FILE* app_fd = fopen("/sd/gnuboy.bin", "rb"); - if (app_fd == NULL) { - draw_message("Failed to open gnuboy.bin"); - ESP_LOGE(TAG, "Failed to open gnuboy.bin"); - vTaskDelay(100 / portTICK_PERIOD_MS); - return; - } - size_t app_size; - uint8_t* app = load_file_to_ram(app_fd, &app_size); - if (app == NULL) { - draw_message("Failed to load app to RAM"); - ESP_LOGE(TAG, "Failed to load application into RAM"); - vTaskDelay(100 / portTICK_PERIOD_MS); - return; + for (size_t index = 0; index < menu_get_length(menu); index++) { + free(menu_get_callback_args(menu, index)); } - ESP_LOGI(TAG, "Application size %d", app_size); - - res = appfsCreateFile("gnuboy", app_size, &handle); - if (res != ESP_OK) { - draw_message("Failed to create on AppFS"); - ESP_LOGE(TAG, "Failed to create file on AppFS (%d)", res); - vTaskDelay(100 / portTICK_PERIOD_MS); - free(app); - return; - } - res = appfsWrite(handle, 0, app, app_size); - if (res != ESP_OK) { - draw_message("Failed to write to AppFS"); - ESP_LOGE(TAG, "Failed to write to file on AppFS (%d)", res); - vTaskDelay(100 / portTICK_PERIOD_MS); - free(app); - return; - } - free(app); - ESP_LOGI(TAG, "Application is now stored in AppFS"); - draw_message("App installed!"); - vTaskDelay(100 / portTICK_PERIOD_MS); - return; -} - -void appfs_boot_app(int fd) { - if (fd<0 || fd>255) { - REG_WRITE(RTC_CNTL_STORE0_REG, 0); - } else { - REG_WRITE(RTC_CNTL_STORE0_REG, 0xA5000000|fd); - } - - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); - esp_sleep_enable_timer_wakeup(10); - esp_deep_sleep_start(); -} - -void appfs_test(bool sdcard_ready) { - appfs_handle_t fd = appfsOpen("gnuboy"); - if (fd < 0) { - ESP_LOGW(TAG, "gnuboy not found in appfs"); - draw_message("gnuboy not found in fs!"); - /*if (sdcard_ready) { - appfs_store_app(); - appfs_test(false); // Recursive, but who cares :D - }*/ - } else { - draw_message("Booting gnuboy..."); - ESP_LOGE(TAG, "booting gnuboy from appfs (%d)", fd); - appfs_boot_app(fd); - } + menu_free(menu); } void app_main(void) { esp_err_t res; - buttonQueue = xQueueCreate(10, sizeof(button_message_t)); + /* Initialize memory */ + + xQueueHandle buttonQueue = xQueueCreate(10, sizeof(button_message_t)); if (buttonQueue == NULL) { ESP_LOGE(TAG, "Failed to allocate queue"); @@ -211,14 +143,14 @@ void app_main(void) { } - framebuffer = heap_caps_malloc(ILI9341_BUFFER_SIZE, MALLOC_CAP_8BIT); + uint8_t* framebuffer = heap_caps_malloc(ILI9341_BUFFER_SIZE, MALLOC_CAP_8BIT); if (framebuffer == NULL) { ESP_LOGE(TAG, "Failed to allocate framebuffer"); restart(); } memset(framebuffer, 0, ILI9341_BUFFER_SIZE); - pax_buffer = malloc(sizeof(pax_buf_t)); + pax_buf_t* pax_buffer = malloc(sizeof(pax_buf_t)); if (framebuffer == NULL) { ESP_LOGE(TAG, "Failed to allocate pax buffer"); restart(); @@ -228,6 +160,8 @@ void app_main(void) { pax_buf_init(pax_buffer, framebuffer, ILI9341_WIDTH, ILI9341_HEIGHT, PAX_BUF_16_565RGB); driver_framebuffer_init(framebuffer); + /* Initialize hardware */ + res = board_init(); if (res != ESP_OK) { @@ -235,13 +169,14 @@ void app_main(void) { restart(); } - ili9341 = get_ili9341(); - ice40 = get_ice40(); - bno055 = get_bno055(); - rp2040 = get_rp2040(); + ILI9341* ili9341 = get_ili9341(); + ICE40* ice40 = get_ice40(); + BNO055* bno055 = get_bno055(); + RP2040* rp2040 = get_rp2040(); + PCA9555* pca9555 = get_pca9555(); graphics_task(pax_buffer, ili9341, framebuffer, NULL, "Button init..."); - button_init(); + button_init(pca9555, buttonQueue); rp2040_set_led_mode(rp2040, true, true); @@ -259,113 +194,22 @@ void app_main(void) { if (sdcard_ready) { graphics_task(pax_buffer, ili9341, framebuffer, NULL, "SD card mounted"); - } else { - graphics_task(pax_buffer, ili9341, framebuffer, NULL, "No SD card"); } - menu_t* menu = menu_alloc("Launcher"); - - appfs_handle_t current_fd = APPFS_INVALID_FD; - - while (1) { - current_fd = appfsNextEntry(current_fd); - if (current_fd == APPFS_INVALID_FD) break; - - const char* name = NULL; - appfsEntryInfo(current_fd, &name, NULL); - menu_args_t* args = malloc(sizeof(menu_args_t)); - args->fd = current_fd; - args->action = ACTION_APPFS; - menu_insert_item(menu, name, NULL, (void*) args, -1); - } - - menu_args_t* fpga_args = malloc(sizeof(menu_args_t)); - fpga_args->action = ACTION_FPGA; - menu_insert_item(menu, "FPGA test", NULL, fpga_args, -1); - menu_args_t* install_args = malloc(sizeof(menu_args_t)); - install_args->action = ACTION_INSTALLER; - menu_insert_item(menu, "Install app...", NULL, install_args, -1); - - bool render = true; - menu_args_t* menuAction = NULL; - while (1) { - button_message_t buttonMessage = {0}; - if (xQueueReceive(buttonQueue, &buttonMessage, 16 / portTICK_PERIOD_MS) == pdTRUE) { - uint8_t pin = buttonMessage.button; - bool value = buttonMessage.state; - switch(pin) { - case PCA9555_PIN_BTN_JOY_LEFT: - printf("Joystick horizontal %s\n", value ? "left" : "center"); - break; - case PCA9555_PIN_BTN_JOY_PRESS: - printf("Joystick %s\n", value ? "pressed" : "released"); - break; - case PCA9555_PIN_BTN_JOY_DOWN: - printf("Joystick vertical %s\n", value ? "down" : "center"); - if (value) { - menu_navigate_next(menu); - render = true; - } - break; - case PCA9555_PIN_BTN_JOY_UP: - printf("Joystick vertical %s\n", value ? "up" : "center"); - if (value) { - menu_navigate_previous(menu); - render = true; - } - break; - case PCA9555_PIN_BTN_JOY_RIGHT: - printf("Joystick horizontal %s\n", value ? "right" : "center"); - break; - case PCA9555_PIN_BTN_HOME: - printf("Home button %s\n", value ? "pressed" : "released"); - break; - case PCA9555_PIN_BTN_MENU: - printf("Menu button %s\n", value ? "pressed" : "released"); - //if (value) reset_to_menu = true; - break; - case PCA9555_PIN_BTN_START: { - printf("Start button %s\n", value ? "pressed" : "released"); - break; - } - case PCA9555_PIN_BTN_SELECT: { - printf("Select button %s\n", value ? "pressed" : "released"); - break; - } - case PCA9555_PIN_BTN_BACK: - printf("Back button %s\n", value ? "pressed" : "released"); - break; - case PCA9555_PIN_BTN_ACCEPT: - printf("Accept button %s\n", value ? "pressed" : "released"); - if (value) { - menuAction = menu_get_callback_args(menu, menu_get_position(menu)); - printf("Position: %u\n", menu_get_position(menu)); - } - break; - default: - printf("Unknown button %d %s\n", pin, value ? "pressed" : "released"); - } - } - - if (render) { - graphics_task(pax_buffer, ili9341, framebuffer, menu, NULL); - render = false; - } - - if (menuAction != NULL) { - graphics_task(pax_buffer, ili9341, framebuffer, menu, "Please wait..."); - if (menuAction->action == ACTION_APPFS) { - appfs_boot_app(menuAction->fd); - } else if (menuAction->action == ACTION_FPGA) { - graphics_task(pax_buffer, ili9341, framebuffer, menu, "FPGA TEST"); - fpga_test(ili9341, ice40, buttonQueue); - }else if (menuAction->action == ACTION_INSTALLER) { - graphics_task(pax_buffer, ili9341, framebuffer, menu, "INSTALLER"); - appfs_store_app(); - } - menuAction = NULL; - render = true; + while (true) { + menu_action_t menu_action; + appfs_handle_t appfs_fd; + menu_launcher(buttonQueue, pax_buffer, ili9341, framebuffer, &menu_action, &appfs_fd); + if (menu_action == ACTION_APPFS) { + appfs_boot_app(appfs_fd); + } else if (menu_action == ACTION_FPGA) { + graphics_task(pax_buffer, ili9341, framebuffer, NULL, "FPGA TEST"); + fpga_test(ili9341, ice40, buttonQueue); + } else if (menu_action == ACTION_INSTALLER) { + graphics_task(pax_buffer, ili9341, framebuffer, NULL, "INSTALLER"); + //appfs_store_app(); } + graphics_task(pax_buffer, ili9341, framebuffer, NULL, "Loop!"); } diff --git a/main/menu.c b/main/menu.c index 2063c41..11ed627 100644 --- a/main/menu.c +++ b/main/menu.c @@ -22,9 +22,9 @@ menu_t* menu_alloc(const char* aTitle) { void _menu_free_item(menu_item_t* aMenuItem) { free(aMenuItem->label); - if (aMenuItem->callbackArgs != NULL) { - free(aMenuItem->callbackArgs); - } + //if (aMenuItem->callbackArgs != NULL) { + // free(aMenuItem->callbackArgs); + //} free(aMenuItem); } @@ -147,6 +147,10 @@ size_t menu_get_position(menu_t* aMenu) { return aMenu->position; } +size_t menu_get_length(menu_t* aMenu) { + return aMenu->length; +} + void* menu_get_callback_args(menu_t* aMenu, size_t aPosition) { menu_item_t* item = _menu_find_item(aMenu, aPosition); if (item == NULL) return NULL; @@ -174,17 +178,15 @@ void menu_debug(menu_t* aMenu) { } void menu_render(pax_buf_t *aBuffer, menu_t* aMenu, float aPosX, float aPosY, float aWidth, float aHeight) { - size_t itemOffset = 0; pax_col_t fgColor = 0xFF000000; pax_col_t bgColor = 0xFFFFFFFF; pax_col_t borderColor = 0xFF000000; pax_col_t titleColor = 0xFFFFFFFF; - float scroll = 0; + pax_col_t scrollbarBgColor = 0xFF555555; + pax_col_t scrollbarFgColor = 0xFFCCCCCC; + pax_col_t scrollbarSlColor = 0xFFFFFFFF; float entry_height = 18 + 2; - size_t maxItems = aBuffer->height / entry_height; - - size_t entry_offset = scroll / entry_height; - scroll -= entry_offset * entry_height; + size_t maxItems = aHeight / entry_height; float posY = aPosY; @@ -202,8 +204,11 @@ void menu_render(pax_buf_t *aBuffer, menu_t* aMenu, float aPosX, float aPosY, fl pax_clip(aBuffer, aPosX, posY, aWidth, aHeight); pax_outline_rect(aBuffer, borderColor, aPosX, aPosY, aWidth, aHeight); - - posY -= scroll; + size_t itemOffset = 0; + if (aMenu->position >= maxItems) { + itemOffset = aMenu->position - maxItems + 1; + } + for (size_t index = itemOffset; (index < itemOffset + maxItems) && (index < aMenu->length); index++) { menu_item_t* item = _menu_find_item(aMenu, index); if (item == NULL) { @@ -213,16 +218,32 @@ void menu_render(pax_buf_t *aBuffer, menu_t* aMenu, float aPosX, float aPosY, fl if (index == aMenu->position) { pax_clip(aBuffer, aPosX, posY, aWidth, entry_height); pax_simple_rect(aBuffer, fgColor, aPosX + 1, posY, aWidth - 2, entry_height); - pax_clip(aBuffer, aPosX + 1, posY + 1, aWidth - 2, entry_height - 2); + pax_clip(aBuffer, aPosX + 1, posY + 1, aWidth - 4, entry_height - 2); pax_draw_text(aBuffer, bgColor, NULL, entry_height - 2, aPosX + 1, posY + 1, item->label); } else { - pax_clip(aBuffer, aPosX, posY, aWidth, entry_height); + pax_clip(aBuffer, aPosX, posY, aWidth, entry_height - 1); pax_simple_rect(aBuffer, bgColor, aPosX + 1, posY, aWidth - 2, entry_height); - pax_clip(aBuffer, aPosX + 1, posY + 1, aWidth - 2, entry_height - 2); + pax_clip(aBuffer, aPosX + 1, posY + 1, aWidth - 4, entry_height - 2); pax_draw_text(aBuffer, fgColor, NULL, entry_height - 2, aPosX + 1, posY + 1, item->label); } posY += entry_height; } + + pax_clip(aBuffer, aPosX + aWidth - 5, aPosY + entry_height, 4, aHeight - 1 - entry_height); + + float fractionStart = itemOffset / (aMenu->length * 1.0); + float fractionSelected = aMenu->position / (aMenu->length * 1.0); + float fractionEnd = (itemOffset + maxItems) / (aMenu->length * 1.0); + if (fractionEnd > 1.0) fractionEnd = 1.0; + + float scrollbarHeight = aHeight - entry_height; + float scrollbarStart = scrollbarHeight * fractionStart; + float scrollbarSelected = scrollbarHeight * fractionSelected; + float scrollbarEnd = scrollbarHeight * fractionEnd; + + pax_simple_rect(aBuffer, scrollbarBgColor, aPosX + aWidth - 5, aPosY + entry_height - 1, 4, scrollbarHeight); + pax_simple_rect(aBuffer, scrollbarFgColor, aPosX + aWidth - 5, aPosY + entry_height - 1 + scrollbarStart, 4, scrollbarEnd - scrollbarStart); + pax_simple_rect(aBuffer, scrollbarSlColor, aPosX + aWidth - 5, aPosY + entry_height - 1 + scrollbarSelected, 4, scrollbarHeight / aMenu->length); pax_noclip(aBuffer); } diff --git a/main/pax_keyboard.c b/main/pax_keyboard.c new file mode 100644 index 0000000..38de147 --- /dev/null +++ b/main/pax_keyboard.c @@ -0,0 +1,781 @@ +/* + MIT License + + Copyright (c) 2022 Julian Scheffers + + 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 +#include +#include +#include + +/* ==== Miscellaneous ==== */ + +// Initialise the context with default settings. +void pkb_init(pax_buf_t *buf, pkb_ctx_t *ctx) { + // Allocate a bufffer. + char *buffer = malloc(4); + memset(buffer, 0, 4); + + // Some defaults. + *ctx = (pkb_ctx_t) { + // Position on screen of the keyboard. + .x = 0, + .y = 0, + // Maximum size of the keyboard. + .width = buf->width, + .height = buf->height, + + // Content of the keyboard. + .content = buffer, + // Size in bytes of capacity of the content buffer. + .content_cap = 4, + + // Starting position of the selection in the text box. + .selection = 0, + // Cursor position of the text box. + .cursor = 0, + + // Cursor position of the keyboard. + .key_x = 3, + .key_y = 1, + // The currently held input. + .held = PKB_NO_INPUT, + // The time that holding the input started. + .hold_start = 0, + // The last time pkb_press was called. + .last_press = 0, + + // Whether the keyboard is multi-line. + .multiline = false, + // Whether the keyboard is in insert mode. + .insert = false, + // The board that is currently selected. + .board_sel = PKB_LOWERCASE, + + // The font to use for the keyboard. + .kb_font = PAX_FONT_DEFAULT, + // The font size to use for the keyboard. + .kb_font_size = 27, + // The font to use for the text. + .text_font = PAX_FONT_DEFAULT, + // The font size to use for the text. + .text_font_size = 18, + // The text color to use. + .text_col = 0xffffffff, + // The text color to use when a character is being held down. + .sel_text_col = 0xff000000, + // The selection color to use. + .sel_col = 0xff007fff, + // The background color to use. + .bg_col = 0xff000000, + + // Whether something has changed since last draw. + .dirty = true, + // Whether the text has changed since last draw. + .text_dirty = true, + // Whether the keyboard has changed since last draw. + .kb_dirty = true, + // Whether just the selected character has changed since last draw. + .sel_dirty = true, + // Previous cursor position of the keyboard. + // Used for sel_dirty. + .last_key_x = 3, + .last_key_y = 1, + + // Indicates that the input has been accepted. + .input_accepted = false, + }; + + // TODO: Pick fancier text sizes. +} + +// Free any memory associated with the context. +void pkb_destroy(pkb_ctx_t *ctx) { + free(ctx->content); + ctx->content = NULL; + ctx->content_cap = 0; +} + +const char *uppercase_board[] = { + "QWERTYUIOP", + "ASDFGHJKL", + " ZXCVBNM ", + " < > ", + "<", ">", +}; +const char *lowercase_board[] = { + "qwertyuiop", + "asdfghjkl", + " zxcvbnm ", + " , . ", + ",", ".", +}; +const char *number_board[] = { + "1234567890", + "@#$_&-+()/", + " *\"';:!? ", + " = \\ ", + "=", "\\", +}; +const char *symbols_board[] = { + "()[]{}`~", + "/|\\+-_=", + " ^%<>'\" ", + " , . ", + ",", ".", +}; +const char **boards[] = { + lowercase_board, uppercase_board, + number_board, symbols_board, +}; + +/* ==== Special key art ==== */ + +// Art for the accept key. +// Expects x to be the horizontal center of the key. +static void pkb_art_accept(pax_buf_t *buf, pkb_ctx_t *ctx, float x, float y, float dx, bool selected) { + pax_col_t col = selected && ctx->held == PKB_CHARSELECT ? ctx->sel_text_col : ctx->text_col; + float scale = fminf(ctx->kb_font_size - 4, dx - 4); + + pax_push_2d (buf); + pax_apply_2d (buf, matrix_2d_translate(x-dx/2+2, y+2)); + pax_apply_2d (buf, matrix_2d_scale(scale, scale)); + + pax_draw_line(buf, col, 0.25, 0.5, 0.5, 1); + pax_draw_line(buf, col, 0.5, 1, 1, 0); + + pax_pop_2d (buf); +} + + +// Art for the backspace key. +// Expects x to be the horizontal center of the key. +static void pkb_art_bksp(pax_buf_t *buf, pkb_ctx_t *ctx, float x, float y, float dx, bool selected) { + pax_col_t col = selected && ctx->held == PKB_CHARSELECT ? ctx->sel_text_col : ctx->text_col; + float scale = fminf(ctx->kb_font_size - 4, dx - 4); + + pax_push_2d (buf); + pax_apply_2d (buf, matrix_2d_translate(x-dx/2+2, y+2)); + pax_apply_2d (buf, matrix_2d_scale(scale, scale)); + + // The stopper. + pax_draw_line(buf, col, 0, 0.25, 0, 0.75); + // The arrow. + pax_draw_line(buf, col, 0.1, 0.5, 0.35, 0.25); + pax_draw_line(buf, col, 0.1, 0.5, 0.35, 0.75); + pax_draw_line(buf, col, 0.1, 0.5, 1, 0.5); + + pax_pop_2d (buf); +} + +// Art for the shift key. +// Expects x to be the horizontal center of the key. +static void pkb_art_shift(pax_buf_t *buf, pkb_ctx_t *ctx, float x, float y, float dx, bool selected) { + bool active = ctx->board_sel & 1; + pax_col_t col = selected && ctx->held == PKB_CHARSELECT ? ctx->sel_text_col : ctx->text_col; + float scale = fminf(ctx->kb_font_size - 4, dx - 4); + + pax_push_2d (buf); + pax_apply_2d(buf, matrix_2d_translate(x, y+2)); + pax_apply_2d(buf, matrix_2d_scale(scale, scale)); + + if (active) { + // Filled in shift key. + pax_draw_tri (buf, col, -0.5, 0.5, 0, 0, 0.5, 0.5); + pax_draw_rect(buf, col, -0.25, 0.5, 0.5, 0.5); + } else { + // Outlined shift key. + pax_draw_line(buf, col, 0, 0, 0.5, 0.5); + pax_draw_line(buf, col, 0.5, 0.5, 0.25, 0.5); + pax_draw_line(buf, col, 0.25, 0.5, 0.25, 1); + pax_draw_line(buf, col, 0.25, 1, -0.25, 1); + pax_draw_line(buf, col, -0.25, 0.5, -0.25, 1); + pax_draw_line(buf, col, -0.5, 0.5, -0.25, 0.5); + pax_draw_line(buf, col, 0, 0, -0.5, 0.5); + } + + pax_pop_2d (buf); +} + +// Art for the letters/numbers key. +// Expects x to be the horizontal center of the key. +static void pkb_art_select(pax_buf_t *buf, pkb_ctx_t *ctx, float x, float y, float dx, bool selected) { + pax_col_t col = selected && ctx->held == PKB_CHARSELECT ? ctx->sel_text_col : ctx->text_col; + + // Pick the stuff to show. + char *short_str; + char *long_str; + if (ctx->board_sel == PKB_NUMBERS) { + // Show some symbols. + short_str = "%"; + long_str = "%&~"; + } else if (ctx->board_sel == PKB_SYMBOLS) { + // Show some letters. + short_str = "A"; + long_str = "Abc"; + } else { + // Show some numbers. + short_str = "#"; + long_str = "123"; + } + + // Calculate font size. + char *str = long_str; + pax_vec1_t dims = pax_text_size(ctx->kb_font, 0, str); + int font_size = 9;//(dx - 4) / dims.x * dims.y; + if (font_size < dims.y) { + str = short_str; + dims = pax_text_size(ctx->kb_font, 0, str); + font_size = 9;//(dx - 4) / dims.x * dims.y; + } + dims = pax_text_size(ctx->kb_font, font_size, str); + + // Now draw it. + pax_push_2d (buf); + pax_apply_2d (buf, matrix_2d_translate(x-dims.x/2, y+(ctx->kb_font_size-font_size)/2)); + pax_draw_text(buf, col, ctx->kb_font, font_size, 0, 0, str); + pax_pop_2d (buf); +} + +/* ==== Rendering ==== */ + +// Draw one key of the keyboard. +// Expects x to be the horizontal center of the key. +static void pkb_char(pax_buf_t *buf, pkb_ctx_t *ctx, float x, float y, float dx, char *text, bool selected) { + pax_vec1_t dims = pax_text_size(ctx->kb_font, ctx->kb_font_size, text); + + if (selected && ctx->held == PKB_CHARSELECT) { + // Infilll! + pax_draw_rect(buf, ctx->sel_col, x-dx/2, y, dx, ctx->kb_font_size); + pax_draw_text(buf, ctx->sel_text_col, ctx->kb_font, ctx->kb_font_size, x-dims.x*0.5, y, text); + } else { + pax_draw_text(buf, ctx->text_col, ctx->kb_font, ctx->kb_font_size, x-dims.x*0.5, y, text); + + // Outline? + if (selected) { + pax_push_2d(buf); + pax_apply_2d(buf, matrix_2d_translate(-dx/2, 0)); + pax_draw_line(buf, ctx->sel_col, x, y, x + dx - 1, y); + pax_draw_line(buf, ctx->sel_col, x, y+ctx->kb_font_size-1, x + dx - 1, y+ctx->kb_font_size-1); + pax_draw_line(buf, ctx->sel_col, x + dx - 1, y, x + dx - 1, y+ctx->kb_font_size-1); + pax_draw_line(buf, ctx->sel_col, x, y, x, y+ctx->kb_font_size-1); + pax_pop_2d(buf); + } + } +} + +// Draw one full row of the keyboard. +static void pkb_row(pax_buf_t *buf, pkb_ctx_t *ctx, int rownum, int selected, float dx, float y) { + // Calculate some stuff. + char *row = boards[ctx->board_sel][rownum]; + size_t len = strlen(row); + int x = ctx->x + (ctx->width - len * dx + dx) / 2; + char tmp[2] = {0,0}; + + // Show all of them. + for (int i = 0; i < len; i++) { + // Draw a KEY. + *tmp = row[i]; + pkb_char(buf, ctx, x, y, dx, tmp, selected == i); + + if (i == 0 && rownum == 2) { + // Draw shift key art. + pkb_art_shift(buf, ctx, x, y, dx, selected == i); + } else if (rownum == 2 && i == len - 1) { + // Draw the backspace key art. + pkb_art_bksp(buf, ctx, x, y, dx, selected == i); + } + + x += dx; + } +} + +// Draw a specific key in a row of the keyboard. +static void pkb_row_key(pax_buf_t *buf, pkb_ctx_t *ctx, int rownum, bool selected, float dx, float y, int keyno) { + // Calculate some stuff. + char *row = boards[ctx->board_sel][rownum]; + size_t len = strlen(row); + int x = ctx->x + (ctx->width - len * dx + dx) / 2 + dx * keyno; + char tmp[2] = {0,0}; + + // Show one of them. + *tmp = row[keyno]; + pax_draw_rect(buf, ctx->bg_col, x-dx/2, y, dx, ctx->kb_font_size); + pkb_char(buf, ctx, x, y, dx, tmp, selected); + + if (rownum == 2 && keyno == 0) { + // Draw the shift key art. + pkb_art_shift(buf, ctx, x, y, dx, selected); + } else if (rownum == 2 && keyno == len - 1) { + // Draw the backspace key art. + pkb_art_bksp(buf, ctx, x, y, dx, selected); + } +} + +// Draw just the board part. +static void pkb_render_keyb(pax_buf_t *buf, pkb_ctx_t *ctx, bool do_bg) { + // Draw background. + if (do_bg) { + pax_draw_rect(buf, ctx->bg_col, ctx->x, ctx->y + ctx->height - ctx->kb_font_size*4, ctx->width, ctx->kb_font_size*4); + } + + // Select the board to display. + char **board = boards[ctx->board_sel]; + float dx = ctx->width / 10; + float y = ctx->y + ctx->height - ctx->kb_font_size * 4; + + // Draw the first three rows. + for (int i = 0; i < 3; i ++) { + int sel = -1; + if (i == ctx->key_y) { + sel = ctx->key_x; + } + pkb_row(buf, ctx, i, sel, dx, y); + y += ctx->kb_font_size; + } + + // Spacebar row time. + bool space_sel = ctx->key_y == 3 && ctx->key_x > 1 && ctx->key_x < 7; + float x = ctx->x + (ctx->width - 8 * dx) / 2; + + // The thingy selector. + pkb_char(buf, ctx, x, y, dx, " ", ctx->key_y == 3 && ctx->key_x == 0); + pkb_art_select(buf, ctx, x, y, dx, ctx->key_y == 3 && ctx->key_x == 0); + x += 1.0 * dx; + // Left char. + pkb_char(buf, ctx, x, y, dx, board[4], ctx->key_y == 3 && ctx->key_x == 1); + x += 1.0 * dx; + + // SPACE. + if (space_sel && ctx->held == PKB_CHARSELECT) { + pax_draw_rect(buf, ctx->sel_col, x-dx/2, y, dx*5, ctx->kb_font_size); + pax_draw_rect(buf, ctx->sel_text_col, x, y + ctx->kb_font_size/3, dx*4, ctx->kb_font_size/3); + } else { + pax_draw_rect(buf, ctx->text_col, x, y + ctx->kb_font_size/3, dx*4, ctx->kb_font_size/3); + if (space_sel) { + // Outline rect? + pax_push_2d(buf); + pax_apply_2d(buf, matrix_2d_translate(-dx/2, 0)); + pax_draw_line(buf, ctx->sel_col, x, y, x + dx*5 - 1, y); + pax_draw_line(buf, ctx->sel_col, x, y+ctx->kb_font_size-1, x + dx*5 - 1, y+ctx->kb_font_size-1); + pax_draw_line(buf, ctx->sel_col, x + dx*5 - 1, y, x + dx*5 - 1, y+ctx->kb_font_size-1); + pax_draw_line(buf, ctx->sel_col, x, y, x, y+ctx->kb_font_size-1); + pax_pop_2d(buf); + } + } + + // Right char. + x += 5.0 * dx; + pkb_char(buf, ctx, x, y, dx, board[5], ctx->key_y == 3 && ctx->key_x == 7); + x += 1.0 * dx; + // The thingy acceptor. + pkb_char(buf, ctx, x, y, dx, " ", ctx->key_y == 3 && ctx->key_x == 8); + pkb_art_accept(buf, ctx, x, y, dx, ctx->key_y == 3 && ctx->key_x == 8); +} + +// Draw just the text part. +static void pkb_render_text(pax_buf_t *buf, pkb_ctx_t *ctx, bool do_bg) { + // Draw background. + if (do_bg) { + pax_draw_rect(buf, ctx->bg_col, ctx->x, ctx->y, ctx->width, ctx->height - ctx->kb_font_size*4); + } + if (ctx->key_y == -1) { + // Outline us. + pax_outline_rect(buf, ctx->sel_col, ctx->x, ctx->y, ctx->width, ctx->height - ctx->kb_font_size*4); + } + + // Some setup. + float x = ctx->x + 2; + float y = ctx->y + 2; + char tmp[2] = {0, 0}; + + // Draw everything. + for (int i = 0; i < strlen(ctx->content); i++) { + if (ctx->cursor == i) { + // The cursor in between the input. + pax_draw_line(buf, ctx->sel_col, x, y, x, y + ctx->text_font_size - 1); + } + + // The character of the input. + tmp[0] = ctx->content[i]; + pax_vec1_t dims = pax_text_size(ctx->text_font, ctx->text_font_size, tmp); + + if (x + dims.x > ctx->width - 2) { + // Word wrap. + x = 2; + y += ctx->text_font_size; + } + pax_draw_text(buf, ctx->text_col, ctx->text_font, ctx->text_font_size, x, y, tmp); + x += dims.x; + } + if (ctx->cursor == strlen(ctx->content)) { + // The cursor after the input. + pax_draw_line(buf, ctx->sel_col, x, y, x, y + ctx->text_font_size - 1); + } +} + +// Draw one specific key. +static void pkb_render_key(pax_buf_t *buf, pkb_ctx_t *ctx, int key_x, int key_y) { + if (key_y == -1) { + // If key_y is -1, the text box is selected to render. + pkb_render_text(buf, ctx, true); + return; + } + + // Select the board to display. + char **board = boards[ctx->board_sel]; + float dx = ctx->width / 10; + float y = ctx->y + ctx->height - ctx->kb_font_size * 4; + + if (key_y < 3) { + // Draw one of the first three rows. + y += ctx->kb_font_size * key_y; + pkb_row_key(buf, ctx, key_y, key_y == ctx->key_y && key_x == ctx->key_x, dx, y, key_x); + y += ctx->kb_font_size; + } else { + // Spacebar row time. + y += ctx->kb_font_size * 3; + bool space_sel = ctx->key_y == 3 && ctx->key_x > 1 && ctx->key_x < 7; + float x = ctx->x + (ctx->width - 8 * dx) / 2; + + // The thingy selector. + if (key_x == 0) { + pax_draw_rect(buf, ctx->bg_col, x-dx/2, y, dx, ctx->kb_font_size); + pkb_char(buf, ctx, x, y, dx, " ", ctx->key_y == 3 && ctx->key_x == 0); + pkb_art_select(buf, ctx, x, y, dx, ctx->key_y == 3 && ctx->key_x == 0); + } + x += 1.0 * dx; + if (key_x == 1) { + pax_draw_rect(buf, ctx->bg_col, x-dx/2, y, dx, ctx->kb_font_size); + pkb_char(buf, ctx, x, y, dx, board[4], ctx->key_y == 3 && ctx->key_x == 1); + } + x += 1.0 * dx; + + // SPACE. + if (space_sel && ctx->held == PKB_CHARSELECT) { + pax_draw_rect(buf, ctx->sel_col, x-dx/2, y, dx*5, ctx->kb_font_size); + pax_draw_rect(buf, ctx->sel_text_col, x, y + ctx->kb_font_size/3, dx*4, ctx->kb_font_size/3); + } else { + pax_draw_rect(buf, ctx->bg_col, x-dx/2, y, dx*5, ctx->kb_font_size); + pax_draw_rect(buf, ctx->text_col, x, y + ctx->kb_font_size/3, dx*4, ctx->kb_font_size/3); + if (space_sel) { + // Outline rect? + pax_push_2d(buf); + pax_apply_2d(buf, matrix_2d_translate(-dx/2, 0)); + pax_draw_line(buf, ctx->sel_col, x, y, x + dx*5 - 1, y); + pax_draw_line(buf, ctx->sel_col, x, y+ctx->kb_font_size-1, x + dx*5 - 1, y+ctx->kb_font_size-1); + pax_draw_line(buf, ctx->sel_col, x + dx*5 - 1, y, x + dx*5 - 1, y+ctx->kb_font_size-1); + pax_draw_line(buf, ctx->sel_col, x, y, x, y+ctx->kb_font_size-1); + pax_pop_2d(buf); + } + } + + // Right char. + x += 5.0 * dx; + if (key_x == 7) { + pax_draw_rect(buf, ctx->bg_col, x-dx/2, y, dx, ctx->kb_font_size); + pkb_char(buf, ctx, x, y, dx, board[5], ctx->key_y == 3 && ctx->key_x == 7); + } + x += 1.0 * dx; + // The thingy acceptor. + if (key_x == 8) { + pax_draw_rect(buf, ctx->bg_col, x-dx/2, y, dx, ctx->kb_font_size); + pkb_char(buf, ctx, x, y, dx, " ", ctx->key_y == 3 && ctx->key_x == 8); + pkb_art_accept(buf, ctx, x, y, dx, ctx->key_y == 3 && ctx->key_x == 8); + } + } +} + +// Redraw the complete on-screen keyboard. +void pkb_render(pax_buf_t *buf, pkb_ctx_t *ctx) { + if (matrix_2d_is_identity(buf->stack_2d.value) + && ctx->x == 0 && ctx->y == 0 + && ctx->width == buf->width + && ctx->height == buf->height) { + // We can just fill the entire screen. + pax_background(buf, ctx->bg_col); + } else { + // We'll need to fill a rectangle. + pax_draw_rect( + buf, ctx->bg_col, + ctx->x, ctx->y, + ctx->width, ctx->height + ); + } + + // Draw the board. + pkb_render_keyb(buf, ctx, false); + // Time to draw some text. + pkb_render_text(buf, ctx, false); + + // Mark as not dirty. + ctx->dirty = false; + ctx->kb_dirty = false; + ctx->sel_dirty = false; + ctx->text_dirty = false; + ctx->last_key_x = ctx->key_x; + ctx->last_key_y = ctx->key_y; +} + +// Redraw only the changed parts of the on-screen keyboard. +void pkb_redraw(pax_buf_t *buf, pkb_ctx_t *ctx) { + if (ctx->text_dirty) { + pkb_render_text(buf, ctx, true); + } + if (ctx->kb_dirty) { + pkb_render_keyb(buf, ctx, true); + } else if (ctx->sel_dirty) { + pkb_render_key(buf, ctx, ctx->last_key_x, ctx->last_key_y); + pkb_render_key(buf, ctx, ctx->key_x, ctx->key_y); + } + + // Mark as not dirty. + ctx->dirty = false; + ctx->kb_dirty = false; + ctx->sel_dirty = false; + ctx->text_dirty = false; + ctx->last_key_x = ctx->key_x; + ctx->last_key_y = ctx->key_y; +} + +/* ==== Text editing ==== */ + +// Handling of delete or backspace. +static void pkb_delete(pkb_ctx_t *ctx, bool is_backspace) { + size_t oldlen = strlen(ctx->content); + if (!is_backspace && ctx->cursor == oldlen) { + // No forward deleting at the end of the line. + return; + } else if (is_backspace && ctx->cursor == 0) { + // No backward deleting at the start of the line. + return; + } else if (!is_backspace) { + // Advanced backspace. + ctx->cursor ++; + } + + // Copy back everything including null terminator. + ctx->cursor --; + for (int i = ctx->cursor; i < oldlen; i++) { + ctx->content[i] = ctx->content[i+1]; + } + + // DECREMENT length. + if (oldlen * 2 < ctx->content_cap) { + // Not decrementing here is important as oldlen excludes the null terminator. + ctx->content_cap = oldlen; + ctx->content = realloc(ctx->content, ctx->content_cap); + } + ctx->text_dirty = true; +} + +// Handling of normal input. +static void pkb_append(pkb_ctx_t *ctx, char value) { + size_t oldlen = strlen(ctx->content); + if (oldlen + 2 >= ctx->content_cap) { + // Expand the buffer just a bit. + ctx->content_cap *= 2; + ctx->content = realloc(ctx->content, ctx->content_cap); + } + + // Copy over the remainder of the buffer. + // If there's no text this still copies the null terminator. + for (int i = oldlen; i >= ctx->cursor; i --) { + ctx->content[i + 1] = ctx->content[i]; + } + + // And finally insert at the character. + ctx->content[ctx->cursor] = value; + ctx->cursor ++; + ctx->text_dirty = true; +} + +/* ==== Input handling ==== */ + +// The loop that allows input repeating. +void pkb_loop(pkb_ctx_t *ctx) { + int64_t now = esp_timer_get_time(); + if (!ctx->held) return; + bool is_dir = (ctx->held >= PKB_UP) && (ctx->held <= PKB_RIGHT); + + if ((ctx->hold_start + 1000000 < now) || (is_dir && ctx->hold_start + 250000 < now)) { + // 8 repeats per second. + if (ctx->last_press + 125000 < now) { + pkb_press(ctx, ctx->held); + } + } +} + +// A pressing of the input. +void pkb_press(pkb_ctx_t *ctx, pkb_input_t input) { + char **board = boards[ctx->board_sel]; + ctx->last_press = esp_timer_get_time(); + switch (input) { + // Cursor movements. + size_t rowlen; + case PKB_UP: + ctx->key_y --; + if (ctx->key_y < -1) ctx->key_y = 3; + else if (ctx->key_y != -1) { + rowlen = strlen(board[ctx->key_y]); + if (ctx->key_x >= rowlen) ctx->key_x = rowlen - 1; + } + ctx->sel_dirty = true; + break; + + case PKB_DOWN: + ctx->key_y ++; + ctx->key_y %= 4; + rowlen = strlen(board[ctx->key_y]); + if (ctx->key_x >= rowlen) ctx->key_x = rowlen - 1; + ctx->sel_dirty = true; + break; + + case PKB_LEFT: + if (ctx->key_y == -1) { + if (ctx->cursor > 0) ctx->cursor --; + } else if (ctx->key_y == 3 && ctx->key_x > 1 && ctx->key_x < 7) { + ctx->key_x = 1; + } else { + ctx->key_x --; + if (ctx->key_x < 0) ctx->key_x = strlen(board[ctx->key_y]) - 1; + } + ctx->sel_dirty = true; + break; + + case PKB_RIGHT: + if (ctx->key_y == -1) { + if (ctx->cursor < strlen(ctx->content)) ctx->cursor ++; + } else if (ctx->key_y == 3 && ctx->key_x > 1 && ctx->key_x < 7) { + ctx->key_x = 7; + } else { + ctx->key_x ++; + ctx->key_x %= strlen(board[ctx->key_y]); + } + ctx->sel_dirty = true; + break; + + // Enter a character. + case PKB_CHARSELECT: + ctx->sel_dirty |= ctx->held != PKB_CHARSELECT; + if (ctx->key_y == 3) { + switch (ctx->key_x) { + case 0: + // Board selector. + if (ctx->board_sel == PKB_NUMBERS) ctx->board_sel = PKB_SYMBOLS; + else if (ctx->board_sel == PKB_SYMBOLS) ctx->board_sel = PKB_LOWERCASE; + else ctx->board_sel = PKB_NUMBERS; + ctx->kb_dirty = true; + break; + case 1: + // Magic. + pkb_append(ctx, *board[4]); + break; + default: + // Spacebar. + pkb_append(ctx, ' '); + break; + case 7: + // mAGIC. + pkb_append(ctx, *board[5]); + break; + case 8: + // Enter idk. + ctx->input_accepted = true; + break; + } + } else if (ctx->key_y == 2) { + if (ctx->key_x == 0) { + // cAPS LOCK KEY. + if (ctx->held == PKB_CHARSELECT) ctx->held = PKB_NO_INPUT; + ctx->board_sel ^= 1; + ctx->kb_dirty = true; + } else if (ctx->key_x == strlen(board[2])-1) { + // Backspace. + pkb_delete(ctx, true); + } else { + // nORMAL CHAR. + pkb_append(ctx, board[2][ctx->key_x]); + } + } else { + // Normal char. + pkb_append(ctx, board[ctx->key_y][ctx->key_x]); + } + break; + + // Shift key, the pressening. + case PKB_SHIFT: + ctx->board_sel |= 1; + ctx->kb_dirty = true; + break; + + // Next keyboard. + case PKB_MODESELECT: + ctx->board_sel ++; + ctx->board_sel %= 4; + rowlen = strlen(board[ctx->key_y]); + if (ctx->key_x >= rowlen) ctx->key_x = rowlen - 1; + ctx->kb_dirty = true; + break; + + // Backspace. + case PKB_DELETE_BEFORE: + pkb_delete(ctx, true); + break; + + // Delete. + case PKB_DELETE_AFTER: + pkb_delete(ctx, false); + break; + default: + break; + } + if (input != PKB_SHIFT && input != ctx->held) { + ctx->held = input; + ctx->hold_start = esp_timer_get_time(); + } + ctx->dirty = true; +} + +// A relealing of the input. +void pkb_release(pkb_ctx_t *ctx, pkb_input_t input) { + switch (input) { + // Shift key, the releasening. + case PKB_SHIFT: + ctx->dirty = true; + ctx->board_sel &= ~1; + ctx->kb_dirty = true; + break; + + // Unpress them char. + case PKB_CHARSELECT: + ctx->sel_dirty = true; + break; + + default: + break; + } + if (ctx->held == input) { + ctx->held = PKB_NO_INPUT; + ctx->dirty = true; + } +} diff --git a/main/system_wrapper.c b/main/system_wrapper.c new file mode 100644 index 0000000..e04109e --- /dev/null +++ b/main/system_wrapper.c @@ -0,0 +1,17 @@ +#include +#include +#include +#include +#include +#include +#include "system_wrapper.h" + +void restart() { + for (int i = 3; i >= 0; i--) { + printf("Restarting in %d seconds...\n", i); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + printf("Restarting now.\n"); + fflush(stdout); + esp_restart(); +}