diff --git a/views/detail_canvas.c b/views/detail_canvas.c index d7dd421..06c638c 100644 --- a/views/detail_canvas.c +++ b/views/detail_canvas.c @@ -109,6 +109,17 @@ static void fit_with_ellipsis(Canvas* c, char* s, int max_w) { } } +static size_t detail_scroll_positions(size_t row_count) { + if(row_count < DETAIL_VISIBLE) return 1; + + /* One trailing blank row lets the last data row scroll above the OK hint. */ + return row_count - DETAIL_VISIBLE + 2; +} + +static size_t detail_scroll_max(size_t row_count) { + return detail_scroll_positions(row_count) - 1; +} + static void draw_rows(Canvas* c, const DetailModel* m) { canvas_set_color(c, ColorBlack); @@ -122,7 +133,8 @@ static void draw_rows(Canvas* c, const DetailModel* m) { size_t end = m->scroll + DETAIL_VISIBLE; if(end > m->row_count) end = m->row_count; - bool has_scrollbar = m->row_count > DETAIL_VISIBLE; + size_t scroll_positions = detail_scroll_positions(m->row_count); + bool has_scrollbar = scroll_positions > 1; int right_edge = has_scrollbar ? 122 : 126; for(size_t i = m->scroll; i < end; i++) { @@ -165,7 +177,7 @@ static void draw_rows(Canvas* c, const DetailModel* m) { if(has_scrollbar) { elements_scrollbar_pos( c, 127, DETAIL_LIST_TOP, 64 - DETAIL_LIST_TOP, - m->scroll, m->row_count - DETAIL_VISIBLE + 1); + m->scroll, scroll_positions); } } @@ -182,7 +194,8 @@ static void draw_raw(Canvas* c, const DetailModel* m) { "(no payload)"); return; } - bool has_scrollbar = total_rows > DETAIL_VISIBLE; + size_t scroll_positions = detail_scroll_positions(total_rows); + bool has_scrollbar = scroll_positions > 1; size_t end = m->raw_scroll + DETAIL_VISIBLE; if(end > total_rows) end = total_rows; @@ -201,7 +214,7 @@ static void draw_raw(Canvas* c, const DetailModel* m) { if(has_scrollbar) { elements_scrollbar_pos( c, 127, DETAIL_LIST_TOP, 64 - DETAIL_LIST_TOP, - m->raw_scroll, total_rows - DETAIL_VISIBLE + 1); + m->raw_scroll, scroll_positions); } } @@ -248,18 +261,24 @@ static bool detail_view_input(InputEvent* ev, void* ctx) { } } else if(m->raw_view) { size_t total = (m->apdu_len + DETAIL_RAW_BYTES_PER_ROW - 1) / DETAIL_RAW_BYTES_PER_ROW; - if(total > DETAIL_VISIBLE) { + size_t max_scroll = detail_scroll_max(total); + if(max_scroll > 0) { if(ev->key == InputKeyUp && m->raw_scroll > 0) { m->raw_scroll--; need = true; } else if(ev->key == InputKeyDown && - m->raw_scroll + DETAIL_VISIBLE < total) { + m->raw_scroll < max_scroll) { m->raw_scroll++; need = true; } } - } else if(m->row_count > DETAIL_VISIBLE) { - if(ev->key == InputKeyUp && m->scroll > 0) { m->scroll--; need = true; } - else if(ev->key == InputKeyDown && - m->scroll + DETAIL_VISIBLE < m->row_count) { m->scroll++; need = true; } + } else { + size_t max_scroll = detail_scroll_max(m->row_count); + if(max_scroll > 0) { + if(ev->key == InputKeyUp && m->scroll > 0) { + m->scroll--; need = true; + } else if(ev->key == InputKeyDown && m->scroll < max_scroll) { + m->scroll++; need = true; + } + } } }, need); @@ -316,7 +335,7 @@ void detail_canvas_set_rows(DetailCanvas* dc, const DetailRow* rows, size_t n) { { for(size_t i = 0; i < n; i++) m->rows[i] = rows[i]; m->row_count = n; - if(m->scroll > n) m->scroll = 0; + if(m->scroll > detail_scroll_max(n)) m->scroll = 0; /* Always reset to decoded view when fields change so the user * sees the new content first; OK still toggles on demand. */ m->raw_view = false; diff --git a/views/scan_canvas.c b/views/scan_canvas.c index 602152e..2e45827 100644 --- a/views/scan_canvas.c +++ b/views/scan_canvas.c @@ -108,7 +108,44 @@ static void draw_footer(Canvas* c, const ScanModel* m) { canvas_draw_str(c, 128 - tail_w - 2, 64 - 2, m->stats_tail); } -static void draw_row(Canvas* c, int y_top, const ScanRow* r, bool selected) { +static void fit_scan_text(Canvas* c, char* s, int max_w) { + while(canvas_string_width(c, s) > max_w && s[0]) { + s[strlen(s) - 1] = 0; + } +} + +static void fit_scan_head(Canvas* c, char* head, size_t cap, int max_w) { + if(canvas_string_width(c, head) <= max_w) return; + + char mfr[5] = {0}; + char medium[8] = {0}; + char tail[8] = {0}; + const char* first_space = strchr(head, ' '); + const char* last_space = strrchr(head, ' '); + + if(first_space && last_space && first_space < last_space) { + size_t mfr_len = (size_t)(first_space - head); + size_t medium_len = (size_t)(last_space - first_space - 1); + if(mfr_len >= sizeof(mfr)) mfr_len = sizeof(mfr) - 1; + if(medium_len >= sizeof(medium)) medium_len = sizeof(medium) - 1; + + memcpy(mfr, head, mfr_len); + memcpy(medium, first_space + 1, medium_len); + strncpy(tail, last_space + 1, sizeof(tail) - 1); + + if(medium[0]) { + snprintf(head, cap, "%s %c %s", mfr, medium[0], tail); + if(canvas_string_width(c, head) <= max_w) return; + } + + snprintf(head, cap, "%s %s", mfr, tail); + if(canvas_string_width(c, head) <= max_w) return; + } + + fit_scan_text(c, head, max_w); +} + +static void draw_row(Canvas* c, int y_top, const ScanRow* r, bool selected, bool has_scrollbar) { /* Selected rows paint inverted; the pen colour must remain set across * every draw call in the row. */ if(selected) { @@ -136,7 +173,8 @@ static void draw_row(Canvas* c, int y_top, const ScanRow* r, bool selected) { * edge until both fit. Without this clamp a verbose value (e.g. a * raw hex dump from a proprietary frame) used to wipe the head off * the row entirely, leaving the user staring at nothing but hex. */ - const int kHeadMinW = 60; /* enough for "MFR 12345678" + medium */ + const int kHeadMinW = 42; /* enough for compact "MFR 1234" */ + const int right_edge = has_scrollbar ? 122 : 126; char val[24]; val[0] = 0; if(r->value[0]) { @@ -144,19 +182,17 @@ static void draw_row(Canvas* c, int y_top, const ScanRow* r, bool selected) { val[sizeof(val) - 1] = 0; } int vw = val[0] ? canvas_string_width(c, val) : 0; - int max_val_w = 128 - text_x - kHeadMinW - 4; - while(vw > max_val_w && val[0]) { - val[strlen(val) - 1] = 0; - vw = canvas_string_width(c, val); - } + int max_val_w = right_edge - text_x - kHeadMinW - 4; + if(max_val_w < 0) max_val_w = 0; + fit_scan_text(c, val, max_val_w); + vw = val[0] ? canvas_string_width(c, val) : 0; - int max_head_w = 128 - text_x - vw - 4; - while(canvas_string_width(c, head) > max_head_w && head[0]) { - head[strlen(head) - 1] = 0; - } + int max_head_w = right_edge - text_x - vw - 4; + if(max_head_w < 0) max_head_w = 0; + fit_scan_head(c, head, sizeof(head), max_head_w); canvas_draw_str(c, text_x, text_y, head); - if(val[0]) canvas_draw_str(c, 128 - vw - 2, text_y, val); + if(val[0]) canvas_draw_str(c, right_edge - vw, text_y, val); canvas_set_color(c, ColorBlack); } @@ -176,16 +212,18 @@ static void scan_view_draw(Canvas* c, void* m_) { return; } + bool has_scrollbar = m->row_count > SCAN_VISIBLE_ROWS; + /* Visible window: rows[scroll .. scroll+SCAN_VISIBLE_ROWS-1]. */ for(size_t i = 0; i < SCAN_VISIBLE_ROWS; i++) { size_t idx = m->scroll + i; if(idx >= m->row_count) break; int y = SCAN_LIST_TOP + (int)i * SCAN_ROW_HEIGHT; - draw_row(c, y, &m->rows[idx], idx == m->cursor); + draw_row(c, y, &m->rows[idx], idx == m->cursor, has_scrollbar); } /* Scrollbar — thin column on the right edge of the list area. */ - if(m->row_count > SCAN_VISIBLE_ROWS) { + if(has_scrollbar) { elements_scrollbar_pos(c, 127, SCAN_LIST_TOP, SCAN_LIST_BOTTOM - SCAN_LIST_TOP, m->cursor, m->row_count); }