From dbd8b8aca83200c1fbd56319cbf1b57abcc34eb8 Mon Sep 17 00:00:00 2001 From: d0k3 Date: Wed, 8 May 2019 23:15:00 +0200 Subject: [PATCH] Included software keyboard in testing --- arm9/source/common/hid.c | 4 +- arm9/source/common/hid.h | 2 +- arm9/source/common/swkbd.c | 323 +++++++++++++++++++++++++++++++++++++ arm9/source/common/swkbd.h | 83 ++++++++++ arm9/source/godmode.c | 12 +- 5 files changed, 418 insertions(+), 6 deletions(-) create mode 100644 arm9/source/common/swkbd.c create mode 100644 arm9/source/common/swkbd.h diff --git a/arm9/source/common/hid.c b/arm9/source/common/hid.c index 0850bd7..c73a9bc 100644 --- a/arm9/source/common/hid.c +++ b/arm9/source/common/hid.c @@ -106,7 +106,7 @@ bool HID_SetCalibrationData(const HID_CalibrationData *calibs, int point_cnt, in return true; } -TouchBox* TouchBoxGet(u32* id, const u16 x, const u16 y, const TouchBox* tbs, const u32 tbn) { +const TouchBox* TouchBoxGet(u32* id, const u16 x, const u16 y, const TouchBox* tbs, const u32 tbn) { // check if inside touchbox for (u32 i = 0; !tbn || (i < tbn); i++) { const TouchBox* tb = tbs + i; @@ -114,7 +114,7 @@ TouchBox* TouchBoxGet(u32* id, const u16 x, const u16 y, const TouchBox* tbs, co if ((x >= tb->x) && (y >= tb->y) && (x < tb->x + tb->w) && (y < tb->y + tb->h)) { if (id) *id = tb->id; - return tb; + return tb; // we know const is discarded here } } diff --git a/arm9/source/common/hid.h b/arm9/source/common/hid.h index 74abbf3..76e9dbd 100644 --- a/arm9/source/common/hid.h +++ b/arm9/source/common/hid.h @@ -35,7 +35,7 @@ typedef struct { } TouchBox; // abstraction for HID_ReadTouchState, also returns touchbox id (if any) -TouchBox* TouchBoxGet(u32* id, const u16 x, const u16 y, const TouchBox* tbs, const u32 tbn); +const TouchBox* TouchBoxGet(u32* id, const u16 x, const u16 y, const TouchBox* tbs, const u32 tbn); u32 InputWait(u32 timeout_sec); bool CheckButton(u32 button); diff --git a/arm9/source/common/swkbd.c b/arm9/source/common/swkbd.c new file mode 100644 index 0000000..91df18a --- /dev/null +++ b/arm9/source/common/swkbd.c @@ -0,0 +1,323 @@ +#include + +#include "swkbd.h" +#include "ui.h" +#include "timer.h" +#include "hid.h" + + +static inline char to_uppercase(char c) { + if ((c >= 'a') && (c <= 'z')) c += ('A'-'a'); + return c; +} + +static bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout) { + // count # of rows + u32 n_rows = 0; + for (u32 i = 0;; i++) { + if (layout[i] == 0) { + n_rows++; + if (layout[i+1] == 0) break; + } + } + + // set p_y start position + u32 height = (n_rows) ? (n_rows * SWKBD_STDKEY_HEIGHT) + ((n_rows-1) * SWKDB_KEY_SPACING) : 0; + u32 p_y = SCREEN_HEIGHT - height - SWKBD_STDKEY_HEIGHT - SWKDB_KEY_SPACING; + + // set up the textbox + TouchBox* txtbox = swkbd; + txtbox->x = (SCREEN_WIDTH_BOT - SWKBD_TEXTBOX_WIDTH) / 2; + txtbox->y = p_y - 30; + txtbox->w = SWKBD_TEXTBOX_WIDTH; + txtbox->h = 30; + txtbox->id = KEY_TXTBOX; + + // set button positions + TouchBox* tb = swkbd + 1; + for (u32 l = 0, k = 0; layout[l] != 0; ) { + // calculate width of current row + u32 n_keys = layout[l++]; + u32 width = (n_keys * SWKBD_STDKEY_WIDTH) + ((n_keys-1) * SWKDB_KEY_SPACING); + for (u32 i = 0; layout[l+i] != 0; i++) + width = width - SWKBD_STDKEY_WIDTH + layout[l+i]; + + // set p_x start position + if (width > SCREEN_WIDTH_BOT) return false; + u32 p_x = (SCREEN_WIDTH_BOT - width) / 2; + + // set up touchboxes + for (u32 i = 0; i < n_keys; i++) { + tb->id = keys[k++]; + tb->x = p_x; + tb->y = p_y; + tb->w = ((tb->id >= 0x80) || (tb->id == (u32) ' ')) ? layout[l++] : SWKBD_STDKEY_WIDTH; + tb->h = SWKBD_STDKEY_HEIGHT; + + p_x += tb->w + SWKDB_KEY_SPACING; + tb++; + } + + // next row + if (layout[l++] != 0) { + ShowPrompt(false, "Oh shit %lu %lu", k, l); + return false; // error!!!! THIS HAS TO GO! + } + p_y += SWKBD_STDKEY_HEIGHT + SWKDB_KEY_SPACING; + } + + // set last touchbox zero (so the end can be detected) + memset(tb, 0, sizeof(TouchBox)); + + return true; +} + +static void DrawKey(const TouchBox* key, const bool pressed, const u32 uppercase) { + const char* keystrs[] = { SWKBD_KEYSTR }; + const u32 color = (pressed) ? COLOR_SWKBD_PRESSED : + (key->id == KEY_ENTER) ? COLOR_SWKBD_ENTER : + ((key->id == KEY_CAPS) && (uppercase > 1)) ? COLOR_SWKBD_CAPS : + COLOR_SWKBD_NORMAL; + + // don't even try to draw the textbox + if (key->id == KEY_TXTBOX) return; + + char keystr[16]; + if (key->id >= 0x80) snprintf(keystr, 16, "%s", keystrs[key->id - 0x80]); + else { + keystr[0] = (uppercase) ? to_uppercase(key->id) : key->id; + keystr[1] = 0; + } + + const u32 width = GetDrawStringWidth(keystr); + const u32 f_offs_x = (key->w - width) / 2; + const u32 f_offs_y = (key->h - FONT_HEIGHT_EXT) / 2; + + DrawRectangle(BOT_SCREEN, key->x, key->y, key->w, key->h, color); + DrawString(BOT_SCREEN, keystr, key->x + f_offs_x, key->y + f_offs_y, COLOR_SWKBD_CHARS, color, false); +} + +static void DrawKeyBoardBox(TouchBox* swkbd, u32 color) { + // we need to make sure to skip the textbox here(first entry) + + // calculate / draw keyboard box + u16 x0 = SCREEN_WIDTH_BOT, x1 = 0; + u16 y0 = SCREEN_HEIGHT, y1 = 0; + for (TouchBox* tb = swkbd + 1; tb->id != 0; tb++) { + if (tb->x < x0) x0 = tb->x; + if (tb->y < y0) y0 = tb->y; + if ((tb->x + tb->w) > x1) x1 = tb->x + tb->w; + if ((tb->y + tb->h) > y1) y1 = tb->y + tb->h; + } + DrawRectangle(BOT_SCREEN, 0, y0-1, SCREEN_WIDTH_BOT, y1-y0+2, COLOR_STD_BG); + DrawRectangle(BOT_SCREEN, x0-1, y0-1, x1-x0+2, y1-y0+2, color); +} + +static void DrawKeyBoard(TouchBox* swkbd, const u32 uppercase) { + // we need to make sure to skip the textbox here(first entry) + + // draw keyboard + for (TouchBox* tb = swkbd + 1; tb->id != 0; tb++) { + DrawKey(tb, false, uppercase); + } +} + +static void DrawTextBox(const TouchBox* txtbox, const char* inputstr, const u32 cursor, u32* scroll) { + const u32 input_shown = (txtbox->w / FONT_WIDTH_EXT) - 2; + const u32 inputstr_size = strlen(inputstr); // we rely on a zero terminated string + const u16 x = txtbox->x; + const u16 y = txtbox->y; + + // fix scroll + if (cursor < *scroll) *scroll = cursor; + else if (cursor - *scroll > input_shown) *scroll = cursor - input_shown; + + // draw input string + DrawStringF(BOT_SCREEN, x, y, COLOR_STD_FONT, COLOR_STD_BG, "%c%-*.*s%-*.*s%c", + (*scroll) ? '<' : '|', + (inputstr_size > input_shown) ? input_shown : inputstr_size, + (inputstr_size > input_shown) ? input_shown : inputstr_size, + inputstr + *scroll, + (inputstr_size > input_shown) ? 0 : input_shown - inputstr_size, + (inputstr_size > input_shown) ? 0 : input_shown - inputstr_size, + "", + (inputstr_size - (s32) *scroll > input_shown) ? '>' : '|' + ); + + // draw cursor + DrawStringF(BOT_SCREEN, x-(FONT_WIDTH_EXT/2), y+10, COLOR_STD_FONT, COLOR_STD_BG, "%-*.*s^%-*.*s", + 1 + cursor - *scroll, + 1 + cursor - *scroll, + "", + input_shown - (cursor - *scroll), + input_shown - (cursor - *scroll), + "" + ); +} + +static void MoveTextBoxCursor(const TouchBox* txtbox, const char* inputstr, const u32 max_size, u32* cursor, u32* scroll) { + const u32 input_shown = (txtbox->w / FONT_WIDTH_EXT) - 2; + const u64 scroll_cooldown = 144; + u64 timer = timer_start(); + u32 id = 0; + u16 x, y; + + // process touch input + while(HID_ReadTouchState(&x, &y)) { + const TouchBox* tb = TouchBoxGet(&id, x, y, txtbox, 0); + if (id == KEY_TXTBOX) { + u16 x_tb = x - tb->x; + u16 cpos = (x_tb < (FONT_WIDTH_EXT/2)) ? 0 : (x_tb - (FONT_WIDTH_EXT/2)) / FONT_WIDTH_EXT; + u32 cursor_next = *scroll + ((cpos <= input_shown) ? cpos : input_shown); + // move cursor to position pointed to + if (*cursor != cursor_next) { + if (cursor_next < max_size) *cursor = cursor_next; + DrawTextBox(txtbox, inputstr, *cursor, scroll); + timer = timer_start(); + } + // move beyound visible field + if (timer_msec(timer) >= scroll_cooldown) { + if ((cpos == 0) && (*scroll > 0)) + (*scroll)--; + else if ((cpos >= input_shown) && (*cursor < (max_size-1))) + (*scroll)++; + } + } + } +} + +static char KeyboardWait(TouchBox* swkbd, bool uppercase) { + u32 id = 0; + u16 x, y; + + // wait for touch input (handle key input, too) + while (true) { + u32 pressed = InputWait(0); + if (pressed & BUTTON_B) return KEY_ESCAPE; + else if (pressed & BUTTON_A) return KEY_ENTER; + else if (pressed & BUTTON_X) return KEY_BKSPC; + else if (pressed & BUTTON_Y) return KEY_INSERT; + else if (pressed & BUTTON_R1) return KEY_CAPS; + else if (pressed & BUTTON_RIGHT) return KEY_RIGHT; + else if (pressed & BUTTON_LEFT) return KEY_LEFT; + else if (pressed & BUTTON_TOUCH) break; + } + + // process touch input + while(HID_ReadTouchState(&x, &y)) { + const TouchBox* tb = TouchBoxGet(&id, x, y, swkbd, 0); + if (tb) { + if (id == KEY_TXTBOX) break; // immediately break on textbox + DrawKey(tb, true, uppercase); + while(HID_ReadTouchState(&x, &y) && (tb == TouchBoxGet(NULL, x, y, swkbd, 0))); + DrawKey(tb, false, uppercase); + } + } + + return (uppercase) ? to_uppercase((char) id) : (char) id; +} + +bool ShowKeyboard(char* inputstr, const u32 max_size, const char *format, ...) { + static const char keys_alphabet[] = { SWKBD_KEYS_ALPHABET }; + static const char keys_special[] = { SWKBD_KEYS_SPECIAL }; + static const char keys_numpad[] = { SWKBD_KEYS_NUMPAD }; + static const u8 layout_alphabet[] = { SWKBD_LAYOUT_ALPHABET }; + static const u8 layout_special[] = { SWKBD_LAYOUT_SPECIAL }; + static const u8 layout_numpad[] = { SWKBD_LAYOUT_NUMPAD }; + TouchBox swkbd_alphabet[64]; + TouchBox swkbd_special[32]; + TouchBox swkbd_numpad[32]; + TouchBox* textbox = swkbd_alphabet; // always use this textbox + + // generate keyboards + if (!BuildKeyboard(swkbd_alphabet, keys_alphabet, layout_alphabet)) return false; + if (!BuildKeyboard(swkbd_special, keys_special, layout_special)) return false; + if (!BuildKeyboard(swkbd_numpad, keys_numpad, layout_numpad)) return false; + + // (instructional) text + char str[512] = { 0 }; // arbitrary limit, should be more than enough + va_list va; + va_start(va, format); + vsnprintf(str, 512, format, va); + va_end(va); + u32 str_width = GetDrawStringWidth(str); + if (str_width < (24 * FONT_WIDTH_EXT)) str_width = 24 * FONT_WIDTH_EXT; + u32 str_x = (str_width >= SCREEN_WIDTH_BOT) ? 0 : (SCREEN_WIDTH_BOT - str_width) / 2; + ClearScreen(BOT_SCREEN, COLOR_STD_BG); + DrawStringF(BOT_SCREEN, str_x, 20, COLOR_STD_FONT, COLOR_STD_BG, "%s", str); + + // handle keyboard + u32 uppercase = 0; // 1 -> uppercase once, 2 -> uppercase always + u32 scroll = 0; + u32 cursor = 0; + u32 inputstr_size = strnlen(inputstr, max_size); + TouchBox* swkbd_prev = NULL; + TouchBox* swkbd = swkbd_alphabet; + bool ret = false; + while (true) { + // draw keyboard if required + if (swkbd != swkbd_prev) { + DrawKeyBoardBox(swkbd, COLOR_SWKBD_BOX); + DrawKeyBoard(swkbd, uppercase); + DrawTextBox(textbox, inputstr, cursor, &scroll); + swkbd_prev = swkbd; + } + + // handle user input + char key = KeyboardWait(swkbd, uppercase); + if (key == KEY_INSERT) key = ' '; // impromptu replacement + if (key == KEY_TXTBOX) { + MoveTextBoxCursor(textbox, inputstr, max_size, &cursor, &scroll); + } else if (key == KEY_CAPS) { + uppercase = (uppercase + 1) % 3; + DrawKeyBoard(swkbd, uppercase); + continue; + } else if (key == KEY_ENTER) { + ret = true; + break; + } else if (key == KEY_ESCAPE) { + break; + } else if (key == KEY_BKSPC) { + if (cursor) { + memmove(inputstr + cursor - 1, inputstr + cursor, max_size - cursor); + cursor--; + inputstr_size--; + } + } else if (key == KEY_LEFT) { + if (cursor) cursor--; + } else if (key == KEY_RIGHT) { + if (cursor < (max_size-1)) cursor++; + } else if (key == KEY_ALPHA) { + swkbd = swkbd_alphabet; + } else if (key == KEY_SPECIAL) { + swkbd = swkbd_special; + } else if (key == KEY_NUMPAD) { + swkbd = swkbd_numpad; + } else if (key && (key < 0x80)) { + if ((cursor < (max_size-1)) && (inputstr_size < max_size)) { + // pad string (if cursor beyound string size) + while (inputstr_size < cursor) { + inputstr[inputstr_size++] = ' '; + inputstr[inputstr_size] = '\0'; + } + // make room + if (inputstr_size < (max_size-1)) { // only if there is still room + memmove(inputstr + cursor + 1, inputstr + cursor, max_size - cursor - 1); + inputstr_size++; + } + // insert char + inputstr[cursor++] = key; + } + if (uppercase == 1) { + uppercase = 0; + DrawKeyBoard(swkbd, uppercase); + } + } + + // update text + DrawTextBox(textbox, inputstr, cursor, &scroll); + } + + ClearScreen(BOT_SCREEN, COLOR_STD_BG); + return ret; +} \ No newline at end of file diff --git a/arm9/source/common/swkbd.h b/arm9/source/common/swkbd.h new file mode 100644 index 0000000..88fabb0 --- /dev/null +++ b/arm9/source/common/swkbd.h @@ -0,0 +1,83 @@ +#pragma once + +#include "common.h" + +// special key ids +enum { + KEY_DUMMY = 0x80, + KEY_BKSPC = 0x81, + KEY_INSERT = 0x82, + KEY_ENTER = 0x83, + KEY_CAPS = 0x84, + KEY_SPECIAL = 0x85, + KEY_NUMPAD = 0x86, + KEY_ALPHA = 0x87, + KEY_LEFT = 0x88, + KEY_RIGHT = 0x89, + KEY_ESCAPE = 0x8A, + KEY_TXTBOX = 0xFF +}; + +// special key strings +#define SWKBD_KEYSTR "", "DEL", "INS", "SUBMIT", "CAPS", "#$@", "123", "ABC", "\x1b", "\x1a", "ESC" + +#define COLOR_SWKBD_NORMAL COLOR_GREY +#define COLOR_SWKBD_PRESSED COLOR_LIGHTGREY +#define COLOR_SWKBD_BOX COLOR_DARKGREY +#define COLOR_SWKBD_TEXTBOX COLOR_DARKGREY +#define COLOR_SWKBD_CHARS COLOR_BLACK +#define COLOR_SWKBD_ENTER COLOR_TINTEDBLUE +#define COLOR_SWKBD_CAPS COLOR_TINTEDYELLOW + +#define SWKBD_TEXTBOX_WIDTH 240 +#define SWKBD_STDKEY_WIDTH 18 +#define SWKBD_STDKEY_HEIGHT 20 +#define SWKDB_KEY_SPACING 1 + +#define SWKBD_KEYS_ALPHABET \ + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '+', '-', KEY_BKSPC, \ + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '&', KEY_ENTER, \ + 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '(', ')', '[', ']', \ + 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '_', '#', '!', \ + KEY_CAPS, ' ', KEY_NUMPAD, KEY_SPECIAL, KEY_LEFT, KEY_RIGHT + +#define SWKBD_KEYS_SPECIAL \ + '(', ')', '{', '}', '[', ']', \ + '.', ',', '?', '!', '`', '\'', \ + '^', '*', '+', '-', '_', '=', \ + '@', '#', '$', '%', '&', '~', \ + KEY_ALPHA, ' ', KEY_BKSPC + +#define SWKBD_KEYS_NUMPAD \ + '7', '8', '9', 'F', 'E', \ + '4', '5', '6', 'D', 'C', \ + '3', '2', '1', 'B', 'A', \ + '0', '.', '_', KEY_LEFT, KEY_RIGHT, \ + KEY_ALPHA, ' ', KEY_BKSPC + +// offset, num of keys in row, width of special keys (...), 0 +#define SWKBD_LAYOUT_ALPHABET \ + 13, 32, 0, \ + 12, 51, 0, \ + 13, 0, \ + 12, 0, \ + 6, 32, 123, 32, 32, 18, 18, 0, \ + 0 + +#define SWKBD_LAYOUT_SPECIAL \ + 6, 0, \ + 6, 0, \ + 6, 0, \ + 6, 0, \ + 3, 32, 46, 32, 0, \ + 0 + +#define SWKBD_LAYOUT_NUMPAD \ + 5, 0, \ + 5, 0, \ + 5, 0, \ + 5, 18, 18, 0, \ + 3, 30, 34, 30, 0, \ + 0 + +bool ShowKeyboard(char* inputstr, u32 max_size, const char *format, ...); diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index 326703c..b8fd581 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -4,6 +4,7 @@ #include "support.h" #include "ui.h" #include "hid.h" +#include "swkbd.h" #include "touchcal.h" #include "fs.h" #include "utils.h" @@ -2048,7 +2049,7 @@ u32 GodMode(int entrypoint) { InitSDCardFS(); AutoEmuNandBase(true); - InitNandCrypto(entrypoint != ENTRY_B9S); + InitNandCrypto(true); // (entrypoint != ENTRY_B9S); InitExtFS(); CalibrateTouchFromFlash(); // !!! this may need some further checking @@ -2528,8 +2529,8 @@ u32 GodMode(int entrypoint) { char loadpath[256]; if ((user_select == more) && (HomeMoreMenu(current_path) == 0)) break; // more... menu else if (user_select == test) { - const char* testopts[2] = { "Calibrate touchscreen", "Touchscreen playground" }; - u32 testsel = ShowSelectPrompt(2, testopts, "Testing menu.\nSelect action:", buttonstr); + const char* testopts[3] = { "Calibrate touchscreen", "Touchscreen playground", "Software keyboard" }; + u32 testsel = ShowSelectPrompt(3, testopts, "Testing menu.\nSelect action:", buttonstr); if (testsel == 1) { ShowPrompt(false, "Touchscreen calibration %s!", (ShowTouchCalibrationDialog()) ? "success" : "failed"); @@ -2538,6 +2539,11 @@ u32 GodMode(int entrypoint) { // ShowTouchPlayground(); Paint9(); break; + } else if (testsel == 3) { + char inputstr[64] = { 0 }; + if (ShowKeyboard(inputstr, 64, "Want to test the swkbd?\nEnter anything you want below:")) + ShowPrompt(false, "You entered: %s", inputstr); + break; } } else if (user_select == scripts) { if (!CheckSupportDir(SCRIPTS_DIR)) {