4848#define LIST_TOP 21
4949#define LIST_ROW_HEIGHT 33
5050#define LIST_FOOTER_Y 122
51+
52+ /* The plain screens put their title at the top and their footer near the
53+ * bottom; the band between is what the screen is for. Named because the digit
54+ * field centres itself in it. */
55+ #define SCREEN_TITLE_Y 2
56+ #define SCREEN_TITLE_BOTTOM (SCREEN_TITLE_Y + 16) /* the 16px face's own height */
57+ #define SCREEN_FOOTER_Y 111
5158#define LIST_GUTTER 10
5259#define LIST_BAR_X 2
5360#define LIST_BAR_WIDTH (SEEDTOOL_DISPLAY_WIDTH - LIST_GUTTER - 2 * LIST_BAR_X)
6976#define NAV_BACK_HEIGHT 19
7077#define NAV_ARROW_WIDTH 7
7178#define NAV_ARROW_HEIGHT 11
79+ /* The tick gets its own box rather than the arrow's: the arrow is tall and
80+ * narrow to read as a triangle, a check this small has to be wider than tall.
81+ * The legs and stroke are the shape; the box is what they add up to, derived
82+ * so it cannot drift from them. */
83+ #define NAV_TICK_STROKE 2
84+ #define NAV_TICK_SHORT 3
85+ #define NAV_TICK_LONG 6
86+ #define NAV_TICK_WIDTH (NAV_TICK_SHORT + NAV_TICK_LONG + NAV_TICK_STROKE)
87+ #define NAV_TICK_HEIGHT (NAV_TICK_LONG + NAV_TICK_STROKE)
7288#define NAV_ROW_HEIGHT 32
7389/* The rows' own height, less the 2px the last one leaves under itself: a
7490 * plain list can let its track overhang that gap, having nothing below it,
@@ -425,7 +441,7 @@ void seedtool_render_screen(const char* title, const char* line1, const char* li
425441 * small because 16px there would run off the bottom of the display. */
426442 draw_centered (tft_Ubuntu16 , line1 , 39 );
427443 draw_centered (tft_Ubuntu16 , line2 , 65 );
428- draw_centered (tft_DefaultFont , footer , 111 );
444+ draw_centered (tft_DefaultFont , footer , SCREEN_FOOTER_Y );
429445}
430446
431447seedtool_thumb_t seedtool_list_thumb (const size_t count , const size_t top , const int track )
@@ -471,31 +487,54 @@ size_t seedtool_list_top(const size_t count, const size_t selected, size_t previ
471487 return previous_top ;
472488}
473489
474- /* A solid left-pointing triangle with its apex at (x, y + height / 2): the
475- * back arrow, drawn rather than set, since the 16px face carries no glyph for
476- * one and Jade's own symbols font is not linked here. */
477- static void draw_back_arrow (const int x , const int y , const int width , const int height , const uint16_t color )
490+ /* A solid isoceles triangle filling a width x height box, apex pointing the
491+ * way asked - drawn rather than set, since the 16px face carries no glyph for
492+ * one and Jade's own symbols font is not linked here.
493+ *
494+ * Up and down walk the rows drawing horizontal bars; left walks the columns
495+ * drawing vertical ones. That is the same arithmetic transposed, which is why
496+ * the header's back arrow and the digit field's scroll hints are one function
497+ * rather than two that drift apart.
498+ *
499+ * The `+ 1` on the step is what keeps the apex a single pixel: without it the
500+ * first two steps round to the same half-width and the point comes out as a
501+ * two-pixel spike. */
502+ typedef enum { TRIANGLE_UP , TRIANGLE_DOWN , TRIANGLE_LEFT } triangle_dir_t ;
503+
504+ static void draw_triangle (
505+ const int x , const int y , const int width , const int height , const triangle_dir_t dir , const uint16_t color )
478506{
479- for (int column = 0 ; column < width ; ++ column ) {
480- const int half = (column + 1 ) * height / (2 * width );
481- fill_rect (x + column , y + height / 2 - half , 1 , 2 * half + 1 , color );
507+ const bool vertical = dir != TRIANGLE_LEFT ;
508+ const int steps = vertical ? height : width ;
509+ const int across = vertical ? width : height ;
510+ for (int i = 0 ; i < steps ; ++ i ) {
511+ const int from_point = dir == TRIANGLE_DOWN ? steps - 1 - i : i ;
512+ const int half = (from_point + 1 ) * across / (2 * steps );
513+ if (vertical ) {
514+ fill_rect (x + width / 2 - half , y + i , 2 * half + 1 , 1 , color );
515+ } else {
516+ fill_rect (x + i , y + height / 2 - half , 1 , 2 * half + 1 , color );
517+ }
482518 }
483519}
484520
485521/* A tick, drawn rather than set, for the same reason the arrow above is: the
486522 * 16px face has no glyph for one and Jade's symbols font is a component the
487- * audit rejects by name. Two strokes from a common low point - a short one up
488- * to the left, a long one up to the right - each a stack of 2px squares so the
489- * diagonals read as solid rather than as a dotted line of single pixels. */
490- static void draw_tick (const int x , const int y , const int width , const int height , const uint16_t color )
523+ * audit rejects by name. Two strokes up from a common low point, each a stack
524+ * of squares so the diagonals read as solid rather than as dotted pixels.
525+ *
526+ * The legs are fixed lengths rather than fractions of one width - at this size
527+ * that rounds the short leg away entirely - and roughly 1:2 is what reads as a
528+ * check. `x, y` is the top-left of the NAV_TICK_WIDTH x NAV_TICK_HEIGHT box. */
529+ static void draw_tick (const int x , const int y , const uint16_t color )
491530{
492- const int foot_x = x + width / 3 ;
493- const int foot_y = y + height - 2 ;
494- for (int i = 0 ; i < width / 3 ; ++ i ) {
495- fill_rect (foot_x - i , foot_y - i , 2 , 2 , color );
531+ const int foot_x = x + NAV_TICK_SHORT ;
532+ const int foot_y = y + NAV_TICK_LONG ;
533+ for (int i = 0 ; i <= NAV_TICK_SHORT ; ++ i ) {
534+ fill_rect (foot_x - i , foot_y - i , NAV_TICK_STROKE , NAV_TICK_STROKE , color );
496535 }
497- for (int i = 0 ; i < width - width / 3 ; ++ i ) {
498- fill_rect (foot_x + i , foot_y - i , 2 , 2 , color );
536+ for (int i = 0 ; i <= NAV_TICK_LONG ; ++ i ) {
537+ fill_rect (foot_x + i , foot_y - i , NAV_TICK_STROKE , NAV_TICK_STROKE , color );
499538 }
500539}
501540
@@ -533,8 +572,8 @@ static void draw_nav_header(const seedtool_nav_t* nav, const char* title)
533572 fill_rect (NAV_BACK_X , NAV_BACK_Y , NAV_BACK_WIDTH , 1 , edge );
534573 fill_rect (NAV_BACK_X , NAV_BACK_Y , 1 , NAV_BACK_HEIGHT , edge );
535574 fill_rect (NAV_BACK_X + NAV_BACK_WIDTH - 1 , NAV_BACK_Y , 1 , NAV_BACK_HEIGHT , edge );
536- draw_back_arrow (NAV_BACK_X + (NAV_BACK_WIDTH - NAV_ARROW_WIDTH ) / 2 ,
537- NAV_BACK_Y + (NAV_BACK_HEIGHT - NAV_ARROW_HEIGHT ) / 2 , NAV_ARROW_WIDTH , NAV_ARROW_HEIGHT ,
575+ draw_triangle (NAV_BACK_X + (NAV_BACK_WIDTH - NAV_ARROW_WIDTH ) / 2 ,
576+ NAV_BACK_Y + (NAV_BACK_HEIGHT - NAV_ARROW_HEIGHT ) / 2 , NAV_ARROW_WIDTH , NAV_ARROW_HEIGHT , TRIANGLE_LEFT ,
538577 on_back ? COLOR_BLACK : COLOR_WHITE );
539578 }
540579 /* The confirm as a tick in the right slot, mirroring the arrow: same box,
@@ -561,8 +600,7 @@ static void draw_nav_header(const seedtool_nav_t* nav, const char* title)
561600 fill_rect (x , NAV_BACK_Y , NAV_BACK_WIDTH , 1 , edge );
562601 fill_rect (x , NAV_BACK_Y , 1 , NAV_BACK_HEIGHT , edge );
563602 fill_rect (x + NAV_BACK_WIDTH - 1 , NAV_BACK_Y , 1 , NAV_BACK_HEIGHT , edge );
564- draw_tick (x + (NAV_BACK_WIDTH - NAV_ARROW_WIDTH ) / 2 ,
565- NAV_BACK_Y + (NAV_BACK_HEIGHT - NAV_ARROW_HEIGHT ) / 2 , NAV_ARROW_WIDTH , NAV_ARROW_HEIGHT ,
603+ draw_tick (x + (NAV_BACK_WIDTH - NAV_TICK_WIDTH ) / 2 , NAV_BACK_Y + (NAV_BACK_HEIGHT - NAV_TICK_HEIGHT ) / 2 ,
566604 !nav -> confirm_enabled ? COLOR_DIM : on_tick ? COLOR_BLACK : COLOR_WHITE );
567605 }
568606 /* Centred between two margins the width of the arrow's box, not across the
@@ -821,11 +859,23 @@ static void draw_progress_bar(const seedtool_progress_t* progress)
821859#define DIGIT_BOX_WIDTH 34
822860#define DIGIT_BOX_HEIGHT 32
823861#define DIGIT_BOX_GAP 6
824- #define DIGIT_BOX_Y 36
825862#define DIGIT_ARROW_WIDTH 11
826- #define DIGIT_ARROW_HEIGHT 7
827- #define DIGIT_ARROW_UP_Y 26
828- #define DIGIT_ARROW_DOWN_Y 71
863+ /* Six rows, not seven: the half-width steps in whole pixels, so an 11-wide
864+ * triangle has exactly six distinct rows (1, 3, 5, 7, 9, 11). A seventh row
865+ * has no width left of its own and repeats one, which reads as a flat spot in
866+ * the edge rather than a straight diagonal. */
867+ #define DIGIT_ARROW_HEIGHT 6
868+
869+ /* An arrow, the boxes, an arrow: one stack, sitting at a different height on
870+ * each screen that draws it because what is under it differs - the value box
871+ * shares its screen with the entropy quality bar, the digit field has only the
872+ * footer. Derived from the stack's own parts, so changing an arrow's height
873+ * moves the stack rather than silently unbalancing it. */
874+ #define DIGIT_FIELD_GAP 4
875+ #define DIGIT_FIELD_HEIGHT (2 * (DIGIT_ARROW_HEIGHT + DIGIT_FIELD_GAP) + DIGIT_BOX_HEIGHT)
876+ #define DIGIT_FIELD_TOP (bottom ) (SCREEN_TITLE_BOTTOM + ((bottom) -SCREEN_TITLE_BOTTOM - DIGIT_FIELD_HEIGHT) / 2)
877+ #define DIGIT_FIELD_BOX_Y (top ) ((top) + DIGIT_ARROW_HEIGHT + DIGIT_FIELD_GAP)
878+ #define DIGIT_FIELD_DOWN_Y (top ) (DIGIT_FIELD_BOX_Y(top) + DIGIT_BOX_HEIGHT + DIGIT_FIELD_GAP)
829879
830880/* Held rather than eyeballed. The widest field is one box per digit a word
831881 * number can have, and it is centred, so a box or gap grown by a few pixels
@@ -836,22 +886,14 @@ static void draw_progress_bar(const seedtool_progress_t* progress)
836886_Static_assert (SEEDTOOL_DIGIT_BOXES_MAX * DIGIT_BOX_WIDTH + (SEEDTOOL_DIGIT_BOXES_MAX - 1 ) * DIGIT_BOX_GAP
837887 <= SEEDTOOL_DISPLAY_WIDTH ,
838888 "the digit boxes are wider than the display" );
839- _Static_assert (DIGIT_ARROW_UP_Y + DIGIT_ARROW_HEIGHT <= DIGIT_BOX_Y , "the up arrow runs into the digit boxes" );
840- _Static_assert (DIGIT_BOX_Y + DIGIT_BOX_HEIGHT <= DIGIT_ARROW_DOWN_Y , "the digit boxes run into the down arrow" );
841- _Static_assert (DIGIT_ARROW_DOWN_Y + DIGIT_ARROW_HEIGHT <= LIST_FOOTER_Y , "the down arrow runs into the footer" );
842-
843- /* A solid triangle, `up` pointing to the top. Drawn row by row like the
844- * backspace glyph above rather than from a font, so it scales with the box
845- * rather than with whatever glyph a face happens to carry. */
846- static void draw_triangle (const int x , const int y , const bool up , const uint16_t color )
847- {
848- for (int row = 0 ; row < DIGIT_ARROW_HEIGHT ; ++ row ) {
849- /* Widest at the base, one pixel at the point. */
850- const int from_point = up ? row : DIGIT_ARROW_HEIGHT - 1 - row ;
851- const int half = from_point * (DIGIT_ARROW_WIDTH / 2 ) / (DIGIT_ARROW_HEIGHT - 1 );
852- fill_rect (x + DIGIT_ARROW_WIDTH / 2 - half , y + row , 2 * half + 1 , 1 , color );
853- }
854- }
889+ /* Both placements, each against the thing that actually bounds it. */
890+ _Static_assert (DIGIT_FIELD_TOP (SCREEN_FOOTER_Y ) >= SCREEN_TITLE_BOTTOM , "the digit field runs into the title" );
891+ _Static_assert (DIGIT_FIELD_DOWN_Y (DIGIT_FIELD_TOP (SCREEN_FOOTER_Y )) + DIGIT_ARROW_HEIGHT <= SCREEN_FOOTER_Y ,
892+ "the digit field runs into the footer" );
893+ _Static_assert (DIGIT_FIELD_TOP (DICE_BAR_Y ) >= SCREEN_TITLE_BOTTOM , "the value box runs into the title" );
894+ _Static_assert (DIGIT_FIELD_DOWN_Y (DIGIT_FIELD_TOP (DICE_BAR_Y )) + DIGIT_ARROW_HEIGHT <= DICE_BAR_Y ,
895+ "the value box runs into the quality bar" );
896+
855897
856898/* One wider box for a value scrolled whole rather than digit by digit: a die
857899 * face is 1-6 and a D20 is 1-20, so splitting it into per-digit boxes would
@@ -863,21 +905,27 @@ void seedtool_render_value_box(
863905 const char * title , const char * text , const bool back , const char * footer , const seedtool_progress_t * progress )
864906{
865907 seedtool_render_clear ();
866- draw_centered (tft_Ubuntu16 , title , 2 );
908+ draw_centered (tft_Ubuntu16 , title , SCREEN_TITLE_Y );
909+ /* Centred above the quality bar, not the footer: the bar is what shares
910+ * this screen, whether or not `progress` draws one this time - the box
911+ * should not jump between runs that grade their entropy and runs that do
912+ * not. */
913+ const int top = DIGIT_FIELD_TOP (DICE_BAR_Y );
914+ const int box_y = DIGIT_FIELD_BOX_Y (top );
867915 const int x = (SEEDTOOL_DISPLAY_WIDTH - VALUE_BOX_WIDTH ) / 2 ;
868- fill_rect (x , DIGIT_BOX_Y , VALUE_BOX_WIDTH , DIGIT_BOX_HEIGHT , COLOR_HIGHLIGHT );
869- draw_border (x , DIGIT_BOX_Y , VALUE_BOX_WIDTH , DIGIT_BOX_HEIGHT , COLOR_HIGHLIGHT );
916+ fill_rect (x , box_y , VALUE_BOX_WIDTH , DIGIT_BOX_HEIGHT , COLOR_HIGHLIGHT );
917+ draw_border (x , box_y , VALUE_BOX_WIDTH , DIGIT_BOX_HEIGHT , COLOR_HIGHLIGHT );
870918 if (back ) {
871919 draw_backspace (x + (VALUE_BOX_WIDTH - BACKSPACE_WIDTH ) / 2 ,
872- DIGIT_BOX_Y + (DIGIT_BOX_HEIGHT - BACKSPACE_HEIGHT ) / 2 , COLOR_BLACK , COLOR_HIGHLIGHT );
920+ box_y + (DIGIT_BOX_HEIGHT - BACKSPACE_HEIGHT ) / 2 , COLOR_BLACK , COLOR_HIGHLIGHT );
873921 } else {
874922 draw_centered_in (tft_Ubuntu16 , text , x , VALUE_BOX_WIDTH ,
875- DIGIT_BOX_Y + (DIGIT_BOX_HEIGHT - tft_Ubuntu16 [1 ]) / 2 , COLOR_BLACK );
923+ box_y + (DIGIT_BOX_HEIGHT - tft_Ubuntu16 [1 ]) / 2 , COLOR_BLACK );
876924 }
877925 const int arrow_x = x + (VALUE_BOX_WIDTH - DIGIT_ARROW_WIDTH ) / 2 ;
878- draw_triangle (arrow_x , DIGIT_ARROW_UP_Y , true , COLOR_WHITE );
879- draw_triangle (arrow_x , DIGIT_ARROW_DOWN_Y , false , COLOR_WHITE );
880- draw_centered (tft_DefaultFont , footer , 111 );
926+ draw_triangle (arrow_x , top , DIGIT_ARROW_WIDTH , DIGIT_ARROW_HEIGHT , TRIANGLE_UP , COLOR_WHITE );
927+ draw_triangle (arrow_x , DIGIT_FIELD_DOWN_Y ( top ), DIGIT_ARROW_WIDTH , DIGIT_ARROW_HEIGHT , TRIANGLE_DOWN , COLOR_WHITE );
928+ draw_centered (tft_DefaultFont , footer , SCREEN_FOOTER_Y );
881929 if (progress ) {
882930 draw_progress_bar (progress );
883931 }
@@ -887,29 +935,33 @@ void seedtool_render_digits(const char* title, const char* digits, const size_t
887935 const char * footer , const seedtool_progress_t * progress )
888936{
889937 seedtool_render_clear ();
890- draw_centered (tft_Ubuntu16 , title , 2 );
938+ draw_centered (tft_Ubuntu16 , title , SCREEN_TITLE_Y );
891939
940+ /* Nothing under this field but the footer, so it centres in that whole
941+ * band rather than sharing the value box's higher placement. */
942+ const int top = DIGIT_FIELD_TOP (SCREEN_FOOTER_Y );
943+ const int box_y = DIGIT_FIELD_BOX_Y (top );
892944 const int span = (int )count * DIGIT_BOX_WIDTH + ((int )count - 1 ) * DIGIT_BOX_GAP ;
893945 const int left = (SEEDTOOL_DISPLAY_WIDTH - span ) / 2 ;
894946 for (size_t i = 0 ; i < count ; ++ i ) {
895947 const int x = left + (int )i * (DIGIT_BOX_WIDTH + DIGIT_BOX_GAP );
896948 const bool selected = i == active ;
897949 const bool set = i < active ;
898950 if (selected ) {
899- fill_rect (x , DIGIT_BOX_Y , DIGIT_BOX_WIDTH , DIGIT_BOX_HEIGHT , COLOR_HIGHLIGHT );
951+ fill_rect (x , box_y , DIGIT_BOX_WIDTH , DIGIT_BOX_HEIGHT , COLOR_HIGHLIGHT );
900952 }
901- draw_border (x , DIGIT_BOX_Y , DIGIT_BOX_WIDTH , DIGIT_BOX_HEIGHT ,
953+ draw_border (x , box_y , DIGIT_BOX_WIDTH , DIGIT_BOX_HEIGHT ,
902954 selected ? COLOR_HIGHLIGHT : set ? COLOR_WHITE : COLOR_DIM );
903955
904956 const char shown = digits [i ];
905- const int glyph_y = DIGIT_BOX_Y + (DIGIT_BOX_HEIGHT - tft_Ubuntu16 [1 ]) / 2 ;
957+ const int glyph_y = box_y + (DIGIT_BOX_HEIGHT - tft_Ubuntu16 [1 ]) / 2 ;
906958 if (shown == SEEDTOOL_KEY_ACCEPT ) {
907959 draw_centered_in (tft_Ubuntu16 , "OK" , x , DIGIT_BOX_WIDTH , glyph_y , selected ? COLOR_BLACK : COLOR_WHITE );
908960 } else if (shown == SEEDTOOL_KEY_BACKSPACE ) {
909961 /* The way out of the field rides the same ring as the digits, so
910962 * the box shows the backspace glyph the keypad already uses for
911963 * it rather than a letter the reader has to decode. */
912- draw_backspace (x + (DIGIT_BOX_WIDTH - BACKSPACE_WIDTH ) / 2 , DIGIT_BOX_Y + (DIGIT_BOX_HEIGHT - BACKSPACE_HEIGHT ) / 2 ,
964+ draw_backspace (x + (DIGIT_BOX_WIDTH - BACKSPACE_WIDTH ) / 2 , box_y + (DIGIT_BOX_HEIGHT - BACKSPACE_HEIGHT ) / 2 ,
913965 COLOR_BLACK , COLOR_HIGHLIGHT );
914966 } else if (shown && shown != ' ' ) {
915967 const char text [2 ] = { shown , '\0' };
@@ -918,12 +970,13 @@ void seedtool_render_digits(const char* title, const char* digits, const size_t
918970
919971 if (selected ) {
920972 const int arrow_x = x + (DIGIT_BOX_WIDTH - DIGIT_ARROW_WIDTH ) / 2 ;
921- draw_triangle (arrow_x , DIGIT_ARROW_UP_Y , true, COLOR_WHITE );
922- draw_triangle (arrow_x , DIGIT_ARROW_DOWN_Y , false, COLOR_WHITE );
973+ draw_triangle (arrow_x , top , DIGIT_ARROW_WIDTH , DIGIT_ARROW_HEIGHT , TRIANGLE_UP , COLOR_WHITE );
974+ draw_triangle (
975+ arrow_x , DIGIT_FIELD_DOWN_Y (top ), DIGIT_ARROW_WIDTH , DIGIT_ARROW_HEIGHT , TRIANGLE_DOWN , COLOR_WHITE );
923976 }
924977 }
925978
926- draw_centered (tft_DefaultFont , footer , 111 );
979+ draw_centered (tft_DefaultFont , footer , SCREEN_FOOTER_Y );
927980 if (progress ) {
928981 draw_progress_bar (progress );
929982 }
0 commit comments