working mnemonic input
This commit is contained in:
parent
c5a254627a
commit
b1cacb869c
|
@ -7,6 +7,16 @@
|
|||
#define KEYPAD_GPIO_COL_OFF GPIO_KEYPAD_COL_0
|
||||
|
||||
#define KEYPAD_DEBOUNCE_THRESHOLD 10
|
||||
#define KEYPAD_LONG_PRESS_THRESHOLD 100
|
||||
|
||||
static inline void keypad_report_key(keypad_key_t key, bool is_long) {
|
||||
g_ui_ctx.keypad.last_key = key;
|
||||
g_ui_ctx.keypad.last_key_long = is_long;
|
||||
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
xTaskNotifyIndexedFromISR(APP_TASK(ui), UI_NOTIFICATION_IDX, UI_KEY_EVT, eSetBits, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
|
||||
void keypad_scan_tick() {
|
||||
for (int i = 0; i < KEYPAD_COLS; i++) {
|
||||
|
@ -16,17 +26,17 @@ void keypad_scan_tick() {
|
|||
uint32_t duration = g_ui_ctx.keypad.matrix_state[key];
|
||||
g_ui_ctx.keypad.matrix_state[key] = 0;
|
||||
|
||||
if (duration > KEYPAD_DEBOUNCE_THRESHOLD) {
|
||||
g_ui_ctx.keypad.last_key = key;
|
||||
g_ui_ctx.keypad.last_key_duration = duration;
|
||||
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
xTaskNotifyIndexedFromISR(APP_TASK(ui), UI_NOTIFICATION_IDX, UI_KEY_EVT, eSetBits, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
if (duration > KEYPAD_DEBOUNCE_THRESHOLD && duration < KEYPAD_LONG_PRESS_THRESHOLD) {
|
||||
keypad_report_key(key, false);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
g_ui_ctx.keypad.matrix_state[key]++;
|
||||
if (g_ui_ctx.keypad.matrix_state[key] >= KEYPAD_LONG_PRESS_THRESHOLD && g_ui_ctx.keypad.matrix_state[key] < (KEYPAD_LONG_PRESS_THRESHOLD * 2)) {
|
||||
g_ui_ctx.keypad.matrix_state[key] = KEYPAD_LONG_PRESS_THRESHOLD * 2;
|
||||
keypad_report_key(key, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define _KEYPAD_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define KEYPAD_ROWS 4
|
||||
#define KEYPAD_COLS 3
|
||||
|
@ -29,7 +30,7 @@ typedef enum {
|
|||
|
||||
typedef struct {
|
||||
uint32_t matrix_state[KEYPAD_ROWS * KEYPAD_COLS];
|
||||
uint32_t last_key_duration;
|
||||
bool last_key_long;
|
||||
keypad_key_t last_key;
|
||||
uint8_t current_row;
|
||||
} keypad_t;
|
||||
|
|
|
@ -243,6 +243,20 @@ hal_err_t screen_draw_char(const screen_text_ctx_t* ctx, char c) {
|
|||
return screen_draw_glyph(ctx, screen_lookup_glyph(ctx->font, c));
|
||||
}
|
||||
|
||||
hal_err_t screen_draw_chars(screen_text_ctx_t* ctx, const char* str, int len) {
|
||||
while(len--) {
|
||||
const glyph_t* glyph = screen_lookup_glyph(ctx->font, *(str++));
|
||||
|
||||
if (screen_draw_glyph(ctx, glyph) != HAL_SUCCESS) {
|
||||
return HAL_FAIL;
|
||||
}
|
||||
|
||||
ctx->x += glyph->xAdvance;
|
||||
}
|
||||
|
||||
return HAL_SUCCESS;
|
||||
}
|
||||
|
||||
hal_err_t screen_draw_string(screen_text_ctx_t* ctx, const char* str) {
|
||||
char c;
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ hal_err_t screen_draw_glyph(const screen_text_ctx_t* ctx, const glyph_t* glyph);
|
|||
|
||||
// High level API
|
||||
hal_err_t screen_draw_char(const screen_text_ctx_t* ctx, char c);
|
||||
hal_err_t screen_draw_chars(screen_text_ctx_t* ctx, const char* str, int len);
|
||||
hal_err_t screen_draw_string(screen_text_ctx_t* ctx, const char* str);
|
||||
hal_err_t screen_draw_text(screen_text_ctx_t* ctx, uint16_t max_x, uint16_t max_y, const uint8_t* text, size_t len);
|
||||
hal_err_t screen_fill_area(const screen_area_t* area, uint16_t color);
|
||||
|
|
157
app/ui/input.c
157
app/ui/input.c
|
@ -6,6 +6,7 @@
|
|||
#include "theme.h"
|
||||
#include "crypto/bip39.h"
|
||||
#include "crypto/util.h"
|
||||
#include "keypad/keypad.h"
|
||||
#include "ui/ui.h"
|
||||
#include "ui/ui_internal.h"
|
||||
|
||||
|
@ -14,8 +15,22 @@
|
|||
|
||||
#define KEY_BACKSPACE 0x08
|
||||
#define KEY_RETURN 0x0d
|
||||
#define KEY_ESCAPE 0x1b
|
||||
|
||||
#define WORD_MAX_LEN 8
|
||||
|
||||
#define KEYBOARD_TOP_Y (SCREEN_HEIGHT - (TH_KEYBOARD_KEY_SIZE * 3))
|
||||
|
||||
#define KEYBOARD_ROW1_LEN 10
|
||||
#define KEYBOARD_ROW2_LEN 9
|
||||
#define KEYBOARD_ROW3_LEN 7
|
||||
|
||||
#define KEYBOARD_ROW1_LIMIT KEYBOARD_ROW1_LEN
|
||||
#define KEYBOARD_ROW2_LIMIT (KEYBOARD_ROW1_LIMIT + KEYBOARD_ROW2_LEN)
|
||||
#define KEYBOARD_ROW3_LIMIT (KEYBOARD_ROW2_LIMIT + KEYBOARD_ROW3_LEN)
|
||||
|
||||
|
||||
|
||||
const char KEYPAD_TO_DIGIT[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', DIG_INV, '0', DIG_INV, DIG_INV, DIG_INV};
|
||||
const char KEYBOARD_MAP[] = {
|
||||
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',
|
||||
|
@ -144,8 +159,116 @@ app_err_t input_pin() {
|
|||
}
|
||||
}
|
||||
|
||||
static inline void input_keyboard_render_key(char c, uint16_t x, uint16_t y, bool selected) {
|
||||
screen_area_t key_area = { .x = x, .y = y, .width = TH_KEYBOARD_KEY_SIZE, .height = TH_KEYBOARD_KEY_SIZE };
|
||||
screen_text_ctx_t ctx = { .font = TH_FONT_TEXT, .fg = TH_COLOR_TEXT_FG, .y = y };
|
||||
|
||||
const glyph_t* glyph = screen_lookup_glyph(ctx.font, c);
|
||||
ctx.bg = selected ? TH_KEYBOARD_KEY_SELECTED_BG : TH_KEYBOARD_KEY_BG;
|
||||
ctx.x = x + ((TH_KEYBOARD_KEY_SIZE - glyph->width) / 2);
|
||||
|
||||
screen_fill_area(&key_area, ctx.bg);
|
||||
screen_draw_glyph(&ctx, glyph);
|
||||
}
|
||||
|
||||
static inline void input_keyboard_render(int idx) {
|
||||
int i = 0;
|
||||
|
||||
while (i < KEYBOARD_ROW1_LIMIT) {
|
||||
input_keyboard_render_key(KEYBOARD_MAP[i], (i * TH_KEYBOARD_KEY_SIZE), KEYBOARD_TOP_Y, idx == i);
|
||||
i++;
|
||||
}
|
||||
|
||||
while (i < KEYBOARD_ROW2_LIMIT) {
|
||||
input_keyboard_render_key(KEYBOARD_MAP[i], ((i - 10) * TH_KEYBOARD_KEY_SIZE), (KEYBOARD_TOP_Y + TH_KEYBOARD_KEY_SIZE), idx == i);
|
||||
i++;
|
||||
}
|
||||
|
||||
screen_area_t padding = {
|
||||
.x = KEYBOARD_ROW2_LEN * TH_KEYBOARD_KEY_SIZE,
|
||||
.y = KEYBOARD_TOP_Y + TH_KEYBOARD_KEY_SIZE,
|
||||
.width = TH_KEYBOARD_KEY_SIZE,
|
||||
.height = TH_KEYBOARD_KEY_SIZE
|
||||
};
|
||||
|
||||
screen_fill_area(&padding, TH_KEYBOARD_KEY_BG);
|
||||
|
||||
while (i < KEYBOARD_ROW3_LIMIT) {
|
||||
input_keyboard_render_key(KEYBOARD_MAP[i], ((i - 19) * TH_KEYBOARD_KEY_SIZE), (KEYBOARD_TOP_Y + (TH_KEYBOARD_KEY_SIZE * 2)), idx == i);
|
||||
i++;
|
||||
}
|
||||
|
||||
padding.x = KEYBOARD_ROW3_LEN * TH_KEYBOARD_KEY_SIZE;
|
||||
padding.y = KEYBOARD_TOP_Y + (TH_KEYBOARD_KEY_SIZE * 2);
|
||||
padding.width = (TH_KEYBOARD_KEY_SIZE * 3);
|
||||
|
||||
screen_fill_area(&padding, TH_KEYBOARD_KEY_BG);
|
||||
}
|
||||
|
||||
static char input_keyboard(int *idx) {
|
||||
return KEY_RETURN;
|
||||
while(1) {
|
||||
input_keyboard_render(*idx);
|
||||
|
||||
switch(ui_wait_keypress(portMAX_DELAY)) {
|
||||
case KEYPAD_KEY_UP:
|
||||
if (*idx >= KEYBOARD_ROW2_LIMIT) {
|
||||
*idx -= KEYBOARD_ROW2_LEN;
|
||||
} else if (*idx >= KEYBOARD_ROW1_LIMIT) {
|
||||
*idx -= KEYBOARD_ROW1_LEN;
|
||||
}
|
||||
break;
|
||||
case KEYPAD_KEY_LEFT:
|
||||
if ((*idx > KEYBOARD_ROW2_LIMIT) ||
|
||||
((*idx > KEYBOARD_ROW1_LIMIT) && (*idx < KEYBOARD_ROW2_LIMIT)) ||
|
||||
((*idx > 0) && (*idx < KEYBOARD_ROW1_LIMIT))) {
|
||||
(*idx)--;
|
||||
}
|
||||
break;
|
||||
case KEYPAD_KEY_RIGHT:
|
||||
if ((*idx < (KEYBOARD_ROW1_LIMIT - 1)) ||
|
||||
((*idx < (KEYBOARD_ROW2_LIMIT - 1)) && (*idx >= KEYBOARD_ROW1_LIMIT)) ||
|
||||
((*idx < (KEYBOARD_ROW3_LIMIT - 1)) && (*idx >= KEYBOARD_ROW2_LIMIT))) {
|
||||
(*idx)++;
|
||||
}
|
||||
break;
|
||||
case KEYPAD_KEY_DOWN:
|
||||
if (*idx < KEYBOARD_ROW1_LIMIT) {
|
||||
*idx = APP_MIN(*idx + KEYBOARD_ROW1_LEN, (KEYBOARD_ROW2_LIMIT - 1));
|
||||
} else if (*idx < KEYBOARD_ROW2_LIMIT) {
|
||||
*idx = APP_MIN(*idx + KEYBOARD_ROW2_LEN, (KEYBOARD_ROW3_LIMIT - 1));
|
||||
}
|
||||
break;
|
||||
case KEYPAD_KEY_BACK:
|
||||
return g_ui_ctx.keypad.last_key_long ? KEY_ESCAPE : KEY_BACKSPACE;
|
||||
case KEYPAD_KEY_CONFIRM:
|
||||
return g_ui_ctx.keypad.last_key_long ? KEY_RETURN : KEYBOARD_MAP[*idx];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void input_render_text_field(char* str, uint16_t y, int len, int suggestion_len) {
|
||||
screen_text_ctx_t ctx = {
|
||||
.font = TH_FONT_TEXT,
|
||||
.fg = TH_TEXT_FIELD_FG,
|
||||
.bg = TH_TEXT_FIELD_BG,
|
||||
.x = TH_TEXT_FIELD_MARGIN,
|
||||
.y = y
|
||||
};
|
||||
|
||||
screen_area_t field_area = {
|
||||
.x = TH_TEXT_FIELD_MARGIN,
|
||||
.y = y,
|
||||
.width = SCREEN_WIDTH - (TH_TEXT_FIELD_MARGIN * 2),
|
||||
.height = TH_TEXT_FIELD_HEIGHT
|
||||
};
|
||||
|
||||
screen_fill_area(&field_area, ctx.bg);
|
||||
|
||||
screen_draw_chars(&ctx, str, len);
|
||||
ctx.fg = TH_TEXT_FIELD_SUGGESTION_FG;
|
||||
screen_draw_chars(&ctx, &str[len], suggestion_len);
|
||||
}
|
||||
|
||||
static void input_mnemonic_title(uint8_t i) {
|
||||
|
@ -153,31 +276,43 @@ static void input_mnemonic_title(uint8_t i) {
|
|||
int base_len = strlen(base_title);
|
||||
int buf_len = base_len + 4;
|
||||
uint8_t title_buf[buf_len];
|
||||
char* title = (char *) u32toa(i, title_buf, buf_len);
|
||||
char* title = (char *) u32toa(i + 1, title_buf, buf_len);
|
||||
title -= base_len;
|
||||
memcpy(title, base_title, base_len);
|
||||
dialog_title(title);
|
||||
}
|
||||
|
||||
static void input_mnemonic_render(char* word, int len, uint16_t idx) {
|
||||
static void input_mnemonic_render(const char* word, int len, uint16_t idx) {
|
||||
int suggestion_len;
|
||||
|
||||
if (idx != UINT16_MAX) {
|
||||
word = BIP39_WORDLIST_ENGLISH[idx];
|
||||
suggestion_len = strlen(word) - len;
|
||||
} else {
|
||||
suggestion_len = 0;
|
||||
}
|
||||
|
||||
input_render_text_field(word, TH_TITLE_HEIGHT + TH_TEXT_FIELD_MARGIN, len, suggestion_len);
|
||||
}
|
||||
|
||||
static uint16_t input_mnemonic_lookup(char* word, int len, uint16_t idx) {
|
||||
if (len == 0) {
|
||||
return UINT16_MAX;
|
||||
}
|
||||
|
||||
while (idx < BIP39_WORD_COUNT) {
|
||||
int cmp = strncmp(word, BIP39_WORDLIST_ENGLISH[idx], len);
|
||||
|
||||
if (!cmp) {
|
||||
break;
|
||||
if (cmp == 0) {
|
||||
return idx;
|
||||
} else if (cmp < 0) {
|
||||
idx++;
|
||||
return UINT16_MAX;
|
||||
} else {
|
||||
idx = UINT16_MAX;
|
||||
break;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
return idx;
|
||||
return UINT16_MAX;
|
||||
}
|
||||
|
||||
static uint16_t input_mnemonic_get_word(int i) {
|
||||
|
@ -201,6 +336,8 @@ static uint16_t input_mnemonic_get_word(int i) {
|
|||
len--;
|
||||
idx = input_mnemonic_lookup(word, len, 0);
|
||||
}
|
||||
} else if (c == KEY_ESCAPE) {
|
||||
//TODO: implement backtracking
|
||||
} else if (len < WORD_MAX_LEN) {
|
||||
word[len++] = c;
|
||||
idx = input_mnemonic_lookup(word, len, (idx == UINT16_MAX ? 0 : idx));
|
||||
|
@ -209,6 +346,8 @@ static uint16_t input_mnemonic_get_word(int i) {
|
|||
}
|
||||
|
||||
app_err_t input_mnemonic() {
|
||||
dialog_footer(TH_TITLE_HEIGHT);
|
||||
|
||||
for (int i = 0; i < g_ui_cmd.params.input_mnemo.len; i++) {
|
||||
g_ui_cmd.params.input_mnemo.indexes[i] = input_mnemonic_get_word(i);
|
||||
}
|
||||
|
|
|
@ -64,4 +64,14 @@
|
|||
#define TH_PIN_FIELD_VERTICAL_MARGIN 16
|
||||
#define TH_PIN_FIELD_DIGIT_MARGIN TH_DEF_LEFT_MARGIN
|
||||
|
||||
#define TH_KEYBOARD_KEY_SIZE 32
|
||||
#define TH_KEYBOARD_KEY_BG SCREEN_RGB(37, 41, 56)
|
||||
#define TH_KEYBOARD_KEY_SELECTED_BG SCREEN_RGB(8, 183, 156)
|
||||
|
||||
#define TH_TEXT_FIELD_BG SCREEN_RGB(37, 41, 56)
|
||||
#define TH_TEXT_FIELD_FG TH_COLOR_FG
|
||||
#define TH_TEXT_FIELD_SUGGESTION_FG SCREEN_RGB(127, 127, 127)
|
||||
#define TH_TEXT_FIELD_MARGIN 8
|
||||
#define TH_TEXT_FIELD_HEIGHT 30
|
||||
|
||||
#endif
|
||||
|
|
|
@ -172,7 +172,7 @@ core_evt_t ui_read_mnemonic(uint16_t* indexes, uint32_t* len) {
|
|||
return CORE_EVT_UI_CANCELLED;
|
||||
}
|
||||
|
||||
g_ui_cmd.type = UI_CMD_INPUT_PIN;
|
||||
g_ui_cmd.type = UI_CMD_INPUT_MNEMO;
|
||||
g_ui_cmd.params.input_mnemo.indexes = indexes;
|
||||
g_ui_cmd.params.input_mnemo.len = *len;
|
||||
|
||||
|
|
Loading…
Reference in New Issue