Working menu!
This commit is contained in:
parent
748ca757b9
commit
4e3ca2e19b
17 changed files with 1268 additions and 290 deletions
|
@ -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"
|
||||
)
|
||||
|
|
76
main/appfs_wrapper.c
Normal file
76
main/appfs_wrapper.c
Normal file
|
@ -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();
|
||||
}
|
||||
|
25
main/button_wrapper.c
Normal file
25
main/button_wrapper.c
Normal file
|
@ -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);
|
||||
}
|
|
@ -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";
|
||||
|
||||
|
|
25
main/graphics_wrapper.c
Normal file
25
main/graphics_wrapper.c
Normal file
|
@ -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);
|
||||
}
|
BIN
main/include/.graphics_wrapper.h.kate-swp
Normal file
BIN
main/include/.graphics_wrapper.h.kate-swp
Normal file
Binary file not shown.
17
main/include/appfs_wrapper.h
Normal file
17
main/include/appfs_wrapper.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sdkconfig.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <esp_system.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_log.h>
|
||||
#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);
|
|
@ -1,6 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
typedef struct _button_message {
|
||||
uint8_t button;
|
||||
bool state;
|
||||
} button_message_t;
|
15
main/include/button_wrapper.h
Normal file
15
main/include/button_wrapper.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sdkconfig.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include "hardware.h"
|
||||
|
||||
typedef struct _button_message {
|
||||
uint8_t button;
|
||||
bool state;
|
||||
} button_message_t;
|
||||
|
||||
void button_init(PCA9555* aPca9555, xQueueHandle aQueue);
|
12
main/include/graphics_wrapper.h
Normal file
12
main/include/graphics_wrapper.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sdkconfig.h>
|
||||
#include <esp_system.h>
|
||||
|
||||
#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);
|
|
@ -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);
|
||||
|
|
147
main/include/pax_keyboard.h
Normal file
147
main/include/pax_keyboard.h
Normal file
|
@ -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 <pax_gfx.h>
|
||||
#include <pax_shapes.h>
|
||||
#include <pax_fonts.h>
|
||||
|
||||
// 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
|
3
main/include/system_wrapper.h
Normal file
3
main/include/system_wrapper.h
Normal file
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
void restart();
|
380
main/main.c
380
main/main.c
|
@ -5,7 +5,6 @@
|
|||
#include <freertos/task.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <esp_system.h>
|
||||
//#include <esp_spi_flash.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_log.h>
|
||||
#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!");
|
||||
}
|
||||
|
||||
|
||||
|
|
49
main/menu.c
49
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);
|
||||
}
|
||||
|
|
781
main/pax_keyboard.c
Normal file
781
main/pax_keyboard.c
Normal file
|
@ -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 <pax_keyboard.h>
|
||||
#include <esp_timer.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
|
||||
/* ==== 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;
|
||||
}
|
||||
}
|
17
main/system_wrapper.c
Normal file
17
main/system_wrapper.c
Normal file
|
@ -0,0 +1,17 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sdkconfig.h>
|
||||
#include <esp_system.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#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();
|
||||
}
|
Loading…
Reference in a new issue