You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
6.8 KiB
243 lines
6.8 KiB
// SPDX-FileCopyrightText: 2020 Foundation Devices, Inc.
|
|
// <hello@foundationdevices.com> SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
// ui.c - Simple UI elements for the bootloader
|
|
|
|
#include <string.h>
|
|
|
|
#include "ui.h"
|
|
#include "gpio.h"
|
|
#include "delay.h"
|
|
#include "utils.h"
|
|
#include "display.h"
|
|
#include "passport_fonts.h"
|
|
#include "ring_buffer.h"
|
|
#include "lcd-sharp-ls018B7dh02.h"
|
|
|
|
#define HEADER_HEIGHT 40
|
|
#define FOOTER_HEIGHT 32
|
|
#define SIDE_MARGIN 4
|
|
#define TOP_MARGIN 4
|
|
|
|
void ui_draw_header(char* title) {
|
|
uint16_t title_y = 10;
|
|
|
|
// Title
|
|
display_text(title, CENTER_X, title_y, &FontSmall, false);
|
|
|
|
// Divider
|
|
display_fill_rect(0, HEADER_HEIGHT-4, SCREEN_WIDTH, 2, 1);
|
|
}
|
|
|
|
void ui_draw_button(uint16_t x, uint16_t y, uint16_t w, uint16_t h, char* label, bool is_pressed) {
|
|
if (is_pressed) {
|
|
display_fill_rect(x, y, w, h, 1);
|
|
} else {
|
|
display_rect(x, y, w, h, 1);
|
|
}
|
|
|
|
// Measure text and center it in the button
|
|
uint16_t label_width = display_measure_text(label, &FontTiny);
|
|
|
|
x = x + (w / 2 - label_width / 2);
|
|
y = y + (h / 2 - FontTiny.ascent / 2);
|
|
|
|
display_text(label, x, y - 1, &FontTiny, is_pressed);
|
|
}
|
|
|
|
void ui_draw_footer(char* left_btn, bool is_left_pressed, char* right_btn, bool is_right_pressed) {
|
|
|
|
uint16_t btn_w = SCREEN_WIDTH / 2;
|
|
|
|
// Draw left button
|
|
ui_draw_button(-1, SCREEN_HEIGHT - FOOTER_HEIGHT + 1, btn_w + 1,
|
|
FOOTER_HEIGHT, left_btn, is_left_pressed);
|
|
|
|
// Draw right button
|
|
ui_draw_button(btn_w - 1, SCREEN_HEIGHT - FOOTER_HEIGHT + 1,
|
|
btn_w + 2, FOOTER_HEIGHT, right_btn, is_right_pressed);
|
|
}
|
|
|
|
void ui_draw_wrapped_text(uint16_t x, uint16_t y, uint16_t max_width, char* text, bool center) {
|
|
// Buffer to hold each wrapped line
|
|
char line[80];
|
|
uint16_t curr_y = y;
|
|
|
|
while (*text != 0) {
|
|
uint16_t sp = 0;
|
|
uint16_t last_space = 0;
|
|
uint16_t line_width = 0;
|
|
uint16_t first_non_space = 0;
|
|
uint16_t text_len = strlen(text);
|
|
uint16_t sp_skip = 0;
|
|
|
|
// Skip leading spaces
|
|
while (true) {
|
|
if (text[sp] == ' ') {
|
|
sp++;
|
|
first_non_space = sp;
|
|
} else if (text[sp] == '\n') {
|
|
sp++;
|
|
first_non_space = sp;
|
|
curr_y += FontSmall.leading;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (sp < text_len) {
|
|
char ch = text[sp];
|
|
if (ch == ' ') {
|
|
last_space = sp;
|
|
}
|
|
else if (ch == '\n') {
|
|
// Time to break the line - Skip over this character after copying and rendering the line with sp_skip
|
|
sp_skip = 1;
|
|
break;
|
|
}
|
|
|
|
uint16_t ch_width = display_get_char_width(ch, &FontSmall);
|
|
line_width += ch_width;
|
|
if (line_width >= max_width) {
|
|
// If we found a space, we can break there, but if we didn't
|
|
// then just break before we go over.
|
|
if (last_space != 0) {
|
|
sp = last_space;
|
|
}
|
|
break;
|
|
}
|
|
sp++;
|
|
}
|
|
|
|
// Copy to prepare for rendering
|
|
strncpy(line, text + first_non_space, sp-first_non_space);
|
|
line[sp-first_non_space] = 0;
|
|
text = text + sp + sp_skip;
|
|
|
|
|
|
// Draw the line
|
|
display_text(line, center ? CENTER_X : SIDE_MARGIN, curr_y, &FontSmall, false);
|
|
|
|
curr_y += FontSmall.leading;
|
|
}
|
|
}
|
|
|
|
#ifndef DEBUG
|
|
static bool poll_for_key(uint8_t* p_key, bool* p_is_key_down) {
|
|
uint8_t key;
|
|
uint8_t count = ring_buffer_dequeue(&key);
|
|
|
|
if (count == 0) {
|
|
return false;
|
|
}
|
|
|
|
*p_key = key & 0x7F;
|
|
*p_is_key_down = (key & 0x80) ? true : false;
|
|
|
|
return true;
|
|
}
|
|
#endif // DEBUG
|
|
|
|
// Show message and then delay or wait for button press
|
|
bool ui_show_message(char* title, char* message, char* left_btn, char *right_btn, bool center) {
|
|
bool exit = false;
|
|
bool result = false;
|
|
bool is_left_pressed = false;
|
|
bool is_right_pressed = false;
|
|
|
|
do {
|
|
display_clear(0);
|
|
|
|
// Draw the text
|
|
ui_draw_wrapped_text(SIDE_MARGIN, HEADER_HEIGHT + TOP_MARGIN, SCREEN_WIDTH - SIDE_MARGIN * 2, message, center);
|
|
|
|
// Draw the header
|
|
ui_draw_header(title);
|
|
|
|
// Draw the footer
|
|
ui_draw_footer(left_btn, is_left_pressed, right_btn, is_right_pressed);
|
|
display_show();
|
|
|
|
#ifdef DEBUG
|
|
delay_ms(5000);
|
|
result = true;
|
|
} while (exit);
|
|
#else
|
|
// Only poll if we are not exiting
|
|
if (!exit) {
|
|
// Poll for key
|
|
uint8_t key;
|
|
bool is_key_down;
|
|
bool key_read;
|
|
do {
|
|
key_read = poll_for_key(&key, &is_key_down);
|
|
} while (!key_read);
|
|
|
|
// Handle key
|
|
if (key_read) {
|
|
if (is_key_down) {
|
|
switch (key) {
|
|
case 99: // 'y'
|
|
is_right_pressed = true;
|
|
break;
|
|
|
|
case 113: // 'x'
|
|
is_left_pressed = true;
|
|
break;
|
|
}
|
|
} else {
|
|
switch (key) {
|
|
case 99: // 'y'
|
|
is_right_pressed = false;
|
|
exit = true;
|
|
result = true;
|
|
continue;
|
|
|
|
case 113: // 'x'
|
|
is_left_pressed = false;
|
|
exit = true;
|
|
result = false;
|
|
continue;
|
|
}
|
|
}
|
|
} else {
|
|
delay_ms(50);
|
|
}
|
|
}
|
|
} while (!exit);
|
|
#endif // DEBUG
|
|
|
|
return result;
|
|
}
|
|
|
|
// Show the error message and give user the option to SHUTDOWN, or view
|
|
// CONTACT information. Then have option to go BACK to the error.
|
|
// NOTE: This function never returns!
|
|
void ui_show_fatal_error(char* error) {
|
|
bool show_error = true;
|
|
|
|
while (true) {
|
|
if (show_error) {
|
|
// Show the error
|
|
if (ui_show_message("Fatal Error", error, "CONTACT", "SHUTDOWN", true)) {
|
|
display_clean_shutdown();
|
|
} else {
|
|
show_error = false;
|
|
}
|
|
} else {
|
|
// Show Contact Info
|
|
if (ui_show_message("Contact", "\nContact us at:\n\nhello@foundationdevices.com",
|
|
"BACK", "SHUTDOWN", true)) {
|
|
display_clean_shutdown();
|
|
} else {
|
|
show_error = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ui_show_hex_buffer(char* title, uint8_t* data, uint32_t length) {
|
|
char buf[512];
|
|
bytes_to_hex_str(data, length, buf, 8, '\n');
|
|
ui_show_message(title, buf, "SHUTDOWN", "CONTINUE", true);
|
|
}
|
|
|