791 lines
23 KiB
C
791 lines
23 KiB
C
/*
|
|
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, char* data) {
|
|
// Allocate a bufffer.
|
|
char *buffer = NULL;
|
|
|
|
if (data == NULL) {
|
|
buffer = malloc(4);
|
|
memset(buffer, 0, 4);
|
|
} else {
|
|
size_t length = strlen(data);
|
|
if (length < 4) length = 4;
|
|
buffer = malloc(length);
|
|
memset(buffer, 0, length);
|
|
strcpy(buffer, data);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|