Skip to content

Commit 86bbf7c

Browse files
authored
Merge pull request #7 from sandman21vs/scrolled-number-entry
Scroll a number in place instead of hunting for it on a keypad
2 parents 99c2dad + 0bb8cc8 commit 86bbf7c

7 files changed

Lines changed: 357 additions & 77 deletions

File tree

‎host/origo_sdl.c‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,20 @@ void seedtool_display_dice_screen(const char* title, const char* line1, const ch
113113
present();
114114
}
115115

116+
void seedtool_display_digits(const char* title, const char* digits, const size_t count, const size_t active,
117+
const char* footer, const seedtool_progress_t* progress)
118+
{
119+
seedtool_render_digits(title, digits, count, active, footer, progress);
120+
present();
121+
}
122+
123+
void seedtool_display_value_box(
124+
const char* title, const char* text, const bool back, const char* footer, const seedtool_progress_t* progress)
125+
{
126+
seedtool_render_value_box(title, text, back, footer, progress);
127+
present();
128+
}
129+
116130
void seedtool_display_splash(void)
117131
{
118132
seedtool_render_splash();

‎main/seedtool_app.c‎

Lines changed: 150 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ _Static_assert(ADDRESS_SHOWN_ROWS <= ADDRESS_LIST_ROWS, "more address rows are s
7575
_Static_assert(ADDRESS_INDEX_DIGITS == 2 && SEEDTOOL_MAX_ADDRESS_INDEX == 99,
7676
"the address-index keypad width and the derived address range must be changed together");
7777

78+
/* The word-number field draws one box per digit, and the renderer bounds how
79+
* many of those fit across the display. Widening a word number without the
80+
* boxes to show it would run the row off both edges, which clips silently. */
81+
_Static_assert(SEEDTOOL_MAX_WORD_DIGITS <= SEEDTOOL_DIGIT_BOXES_MAX,
82+
"a word number has more digits than the digit field can draw boxes for");
83+
7884
/* Every key is found by its character rather than by where it sits, so the
7985
* layouts are QWERTY, or anything else, purely by changing the strings above. */
8086
#define PASSPHRASE_PAGES SEEDTOOL_PASSPHRASE_PAGES
@@ -368,7 +374,15 @@ static int enter_value(const char* title, const unsigned position, const unsigne
368374
* mis-keyed roll is caught against the paper now rather than at the end
369375
* of ninety-nine of them. Only its tail fits, which is the part that
370376
* just changed. */
371-
if (progress) {
377+
/* A plain number scrolls in its own box, the same shape the word-number
378+
* field uses - up and down are the value, and there is nothing else on
379+
* screen to hunt through. A formatted value (a card, "A Clubs") is too
380+
* wide for a box and stays on the text screen, where the running
381+
* transcript sits under it. */
382+
if (!format) {
383+
seedtool_display_value_box(
384+
heading, shown, on_back, chord_learned ? NULL : NAV_FOOTER, progress);
385+
} else if (progress) {
372386
seedtool_display_dice_screen(
373387
heading, on_back ? "[back]" : shown, history, chord_learned ? NULL : NAV_FOOTER, progress);
374388
} else {
@@ -901,92 +915,157 @@ static int enter_word(
901915
* chosen, 0 when the user deleted back out of this word, -1 on timeout or
902916
* overflow. Only digits that still lead to a word number are reachable, and the
903917
* number is shown as its word before it is accepted: a digit misread off paper
904-
* is otherwise a different seed with no sign that anything went wrong. */
918+
* is otherwise a different seed with no sign that anything went wrong.
919+
*
920+
* `allowed` narrows that further on a mnemonic's last word, where the checksum
921+
* leaves only a handful of words possible. It is the same set the letter
922+
* keyboard narrows by, and it has to be applied here too: a restore that
923+
* refuses a word by letter and accepts it by number is worse than one that does
924+
* neither, because the reader who used the number pad has been told nothing.
925+
*
926+
* Up to four boxes, scrolled one at a time, and a number ends where the reader
927+
* says it does: `240` is typed as three digits, not as `0240`. The ring for the
928+
* current box carries the digits that still lead somewhere, then OK once what
929+
* is typed is already a word number, then backspace - the same ring Jade's
930+
* index entry uses, where a short number also has to be able to stop early.
931+
*
932+
* A fixed four-box field was tried first and was wrong: it made 240 a dead end,
933+
* since no fourth digit takes 240x into 1..2048, so the reader who typed it
934+
* found a box with nothing in it but backspace. Ending the number early is not
935+
* a convenience here, it is what makes three-digit numbers reachable at all.
936+
*
937+
* Replaces a twelve-key keypad the reader had to walk a cursor across. Two
938+
* buttons that mean "the digit goes up" and "the digit goes down" need no
939+
* hunting, and the row of boxes shows how much is left to type without a
940+
* separate readout. */
905941
static int enter_word_number(
906942
const size_t position, const size_t total, const seedtool_wordset_t* allowed, char* output, const size_t output_len)
907943
{
908944
char digits[SEEDTOOL_MAX_WORD_DIGITS + 1] = { 0 };
909-
size_t digits_len = 0;
910-
size_t selected = seedtool_layout_center(WORD_NUMBER_LAYOUT);
911-
const size_t accept_index = layout_key_index(WORD_NUMBER_LAYOUT, SEEDTOOL_KEY_ACCEPT);
945+
/* What each box shows, including the one being scrolled. */
946+
char shown[SEEDTOOL_MAX_WORD_DIGITS + 1] = { 0 };
947+
size_t at = 0;
912948
char title[24];
913949
(void)snprintf(title, sizeof(title), "Word %u/%u", (unsigned)position, (unsigned)total);
914950

915-
for (;;) {
916-
bool reachable[SEEDTOOL_DIGITS] = { false };
917-
bool enabled[WORD_NUMBER_KEYS] = { false };
918-
(void)seedtool_next_digits_in(allowed, digits, digits_len, reachable);
919-
const unsigned number = seedtool_word_number_in(allowed, digits, digits_len);
920-
for (size_t i = 0; i < WORD_NUMBER_KEYS; ++i) {
921-
const char key = seedtool_layout_key(WORD_NUMBER_LAYOUT, i);
922-
enabled[i] = key == SEEDTOOL_KEY_BACKSPACE ? true
923-
: key == SEEDTOOL_KEY_ACCEPT ? number != 0
924-
: reachable[key - '0'];
925-
}
926-
/* Once the typed digits are themselves a complete valid number and
927-
* cannot be extended any further, every digit key goes dark at once
928-
* and the cursor has to land somewhere. nearest_enabled's ring
929-
* distance to Accept vs. Backspace from here depends on exactly
930-
* which key the reader was last on - circumstantial, and it lands on
931-
* Backspace as often as not. Accept is the deliberate target
932-
* whenever it's actually reachable; only fall back to the generic
933-
* search when it isn't (still mid-prefix, no complete number yet). */
934-
selected = !enabled[selected] && number ? accept_index : nearest_enabled(enabled, WORD_NUMBER_KEYS, selected);
935-
bool picked = false;
936-
while (!picked) {
937-
seedtool_display_keyboard(
938-
title, digits_len ? digits : "-", WORD_NUMBER_LAYOUT, enabled, selected, position, total);
939-
switch (wait_key()) {
940-
case KEY_SELECT:
941-
picked = true;
942-
break;
943-
case KEY_PREV:
944-
selected = step_key(enabled, WORD_NUMBER_KEYS, selected, false);
945-
break;
946-
case KEY_NEXT:
947-
selected = step_key(enabled, WORD_NUMBER_KEYS, selected, true);
948-
break;
949-
case KEY_REDRAW:
950-
break;
951-
default:
952-
seedtool_zero(digits, sizeof(digits));
953-
return -1;
954-
}
955-
}
951+
/* The ring for the current box: the digits still reachable, then backspace.
952+
* Rebuilt whenever the box changes, since which digits are reachable
953+
* depends on the ones already set. */
954+
char ring[SEEDTOOL_DIGITS + 2];
955+
size_t ring_len = 0, on = 0;
956+
bool rebuild = true;
956957

957-
const char pressed = seedtool_layout_key(WORD_NUMBER_LAYOUT, selected);
958-
if (pressed == SEEDTOOL_KEY_ACCEPT) {
959-
const char* const word = seedtool_word(number - 1);
960-
char counted[32];
961-
(void)snprintf(counted, sizeof(counted), "Number %u of %u", number, (unsigned)SEEDTOOL_WORDLIST_LEN);
962-
const seedtool_key_t key = confirm(title, word, counted);
963-
seedtool_zero(counted, sizeof(counted));
964-
if (key == KEY_SELECT) {
965-
const int result = !word || strlen(word) + 1 > output_len ? -1 : 1;
966-
if (result == 1) {
967-
strcpy(output, word);
958+
for (;;) {
959+
if (rebuild) {
960+
bool reachable[SEEDTOOL_DIGITS] = { false };
961+
/* Narrowed by `allowed`, exactly as the letter keyboard is. The
962+
* ring is built per box, so a digit that cannot begin an allowed
963+
* word never appears to be scrolled to in the first place. */
964+
(void)seedtool_next_digits_in(allowed, digits, at, reachable);
965+
ring_len = 0;
966+
for (size_t d = 0; d < SEEDTOOL_DIGITS; ++d) {
967+
if (reachable[d]) {
968+
ring[ring_len++] = (char)('0' + d);
968969
}
969-
seedtool_zero(digits, sizeof(digits));
970-
return result;
971970
}
972-
if (key == KEY_TIMEOUT) {
973-
seedtool_zero(digits, sizeof(digits));
974-
return -1;
971+
/* OK only once the digits already name a word - so it is absent on
972+
* an empty field and on a prefix like `20`, which is a real number
973+
* but is offered here as a step towards 200x rather than as 20
974+
* itself... which it also is. Both readings are live, which is
975+
* exactly why the reader has to say which one they meant. */
976+
if (seedtool_word_number_in(allowed, digits, at)) {
977+
ring[ring_len++] = SEEDTOOL_KEY_ACCEPT;
975978
}
976-
/* Went back: the number is cleared rather than left half-entered, so
977-
* what is on screen is always the whole of what was typed. */
979+
ring[ring_len++] = SEEDTOOL_KEY_BACKSPACE;
980+
on = 0;
981+
rebuild = false;
982+
}
983+
984+
memset(shown, 0, sizeof(shown));
985+
memcpy(shown, digits, at);
986+
shown[at] = ring[on];
987+
for (size_t i = at + 1; i < SEEDTOOL_MAX_WORD_DIGITS; ++i) {
988+
shown[i] = ' ';
989+
}
990+
seedtool_display_digits(title, shown, SEEDTOOL_MAX_WORD_DIGITS, at, ACK_FOOTER, NULL);
991+
992+
seedtool_key_t key = wait_key();
993+
if (key == KEY_REDRAW) {
994+
continue;
995+
}
996+
if (key == KEY_TIMEOUT) {
978997
seedtool_zero(digits, sizeof(digits));
979-
digits_len = 0;
980-
} else if (pressed == SEEDTOOL_KEY_BACKSPACE) {
981-
if (!digits_len) {
998+
seedtool_zero(shown, sizeof(shown));
999+
return -1;
1000+
}
1001+
if (key == KEY_PREV) {
1002+
on = on ? on - 1 : ring_len - 1;
1003+
continue;
1004+
}
1005+
if (key == KEY_NEXT) {
1006+
on = on + 1 < ring_len ? on + 1 : 0;
1007+
continue;
1008+
}
1009+
1010+
/* KEY_SELECT: commit whatever the box is showing. */
1011+
if (ring[on] == SEEDTOOL_KEY_BACKSPACE) {
1012+
if (!at) {
9821013
seedtool_zero(digits, sizeof(digits));
1014+
seedtool_zero(shown, sizeof(shown));
9831015
return 0;
9841016
}
985-
digits[--digits_len] = '\0';
986-
} else if (digits_len < SEEDTOOL_MAX_WORD_DIGITS) {
987-
digits[digits_len++] = pressed;
988-
digits[digits_len] = '\0';
1017+
digits[--at] = '\0';
1018+
rebuild = true;
1019+
continue;
1020+
}
1021+
bool typed_last = false;
1022+
if (ring[on] != SEEDTOOL_KEY_ACCEPT) {
1023+
digits[at++] = ring[on];
1024+
digits[at] = '\0';
1025+
rebuild = true;
1026+
/* A fourth digit has nowhere left to go, so it confirms itself
1027+
* rather than asking for an OK the field has no box to show. */
1028+
if (at < SEEDTOOL_MAX_WORD_DIGITS) {
1029+
continue;
1030+
}
1031+
typed_last = true;
1032+
}
1033+
1034+
/* Narrowed here too, not only where the ring is built. The ring should
1035+
* already have made a disallowed number untypeable - but this is the
1036+
* gate a number passes through to become a word, and a gate that
1037+
* inherits its guarantee from the screen upstream is one bug away from
1038+
* accepting what that screen was supposed to have refused. A number
1039+
* outside the set reads as no word at all, which sends the reader back
1040+
* to the field rather than into a confirmation they should never see. */
1041+
const unsigned number = seedtool_word_number_in(allowed, digits, at);
1042+
const char* const word = number ? seedtool_word(number - 1) : NULL;
1043+
char counted[32];
1044+
(void)snprintf(counted, sizeof(counted), "Number %u of %u", number, (unsigned)SEEDTOOL_WORDLIST_LEN);
1045+
const seedtool_key_t answer = word ? confirm(title, word, counted) : KEY_PREV;
1046+
seedtool_zero(counted, sizeof(counted));
1047+
if (answer == KEY_SELECT) {
1048+
const int result = strlen(word) + 1 > output_len ? -1 : 1;
1049+
if (result == 1) {
1050+
strcpy(output, word);
1051+
}
1052+
seedtool_zero(digits, sizeof(digits));
1053+
seedtool_zero(shown, sizeof(shown));
1054+
return result;
1055+
}
1056+
if (answer == KEY_TIMEOUT) {
1057+
seedtool_zero(digits, sizeof(digits));
1058+
seedtool_zero(shown, sizeof(shown));
1059+
return -1;
1060+
}
1061+
/* Went back from the confirmation. A number that confirmed itself on a
1062+
* fourth digit reopens that digit, so a wrong last one costs a press
1063+
* rather than the whole number; one the reader ended with OK returns to
1064+
* the field exactly as it was, with the OK still on the ring. */
1065+
if (typed_last) {
1066+
digits[--at] = '\0';
9891067
}
1068+
rebuild = true;
9901069
}
9911070
}
9921071

‎main/seedtool_display.c‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,20 @@ void seedtool_display_screen4(const char* title, const char* line1, const char*
202202
flush();
203203
}
204204

205+
void seedtool_display_digits(const char* title, const char* digits, const size_t count, const size_t active,
206+
const char* footer, const seedtool_progress_t* progress)
207+
{
208+
seedtool_render_digits(title, digits, count, active, footer, progress);
209+
flush();
210+
}
211+
212+
void seedtool_display_value_box(
213+
const char* title, const char* text, const bool back, const char* footer, const seedtool_progress_t* progress)
214+
{
215+
seedtool_render_value_box(title, text, back, footer, progress);
216+
flush();
217+
}
218+
205219
void seedtool_display_dice_screen(const char* title, const char* line1, const char* line2, const char* footer,
206220
const seedtool_progress_t* progress)
207221
{

‎main/seedtool_display.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ void seedtool_display_dice_screen(const char* title, const char* line1, const ch
3434
void seedtool_display_splash(void);
3535
void seedtool_display_list(const char* title, const char* const* items, size_t count, size_t selected, size_t top,
3636
const char* footer);
37+
void seedtool_display_digits(const char* title, const char* digits, size_t count, size_t active, const char* footer,
38+
const seedtool_progress_t* progress);
39+
void seedtool_display_value_box(
40+
const char* title, const char* text, bool back, const char* footer, const seedtool_progress_t* progress);
3741
void seedtool_display_keyboard(const char* title, const char* text, const char* layout, const bool* enabled,
3842
size_t selected, size_t position, size_t total);
3943
bool seedtool_display_qr(const char* title, const char* text);

0 commit comments

Comments
 (0)