diff --git a/.gitignore b/.gitignore index 334b961..5df25f3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,14 @@ build*/ CMakeFiles/ *.cmake emulator/build/ -.DS_Store \ No newline at end of file +.DS_Store +.cache +/emulator/CMakeFiles/* +/emulator/build/* +/emulator/games/* +/emulator/games/lua_examples/* +/emulator/cmake_install.cmake +/emulator/CMakeCache.txt +/emulator/basic1_emulator +/emulator/Makefile +screenlog* \ No newline at end of file diff --git a/basic1.cpp b/basic1.cpp index 700710b..4538047 100644 --- a/basic1.cpp +++ b/basic1.cpp @@ -573,10 +573,11 @@ int main() uint32_t last_frame_time = 0; bool needs_refresh = false; // Track if screen needs redraw + bool dirty_rect_opt_state = (display->get_type() == DISPLAY_TYPE_ST7796); while (1) { // 0. Process serial uploads (for rapid game iteration) - serial_uploader.process(); + serial_uploader.process(is_refresh_in_progress()); // If serial uploader wants to launch a game, wait until it's safe (no display refresh) if (serial_uploader.wants_to_launch_game() && !is_refresh_in_progress()) { @@ -645,7 +646,7 @@ int main() launcher.reset(); needs_refresh = true; // Force full clear for clean transition - display->clear(false); + // display->clear(false); // Unsafe while Core 1 might be busy if (display->get_type() == DISPLAY_TYPE_EPAPER) { LowLevelDisplayEPaper* epaper = static_cast(display); epaper->full_refresh(); @@ -667,7 +668,7 @@ int main() launcher.reset(); needs_refresh = true; // Force full clear for clean transition - display->clear(false); + // display->clear(false); // Unsafe while Core 1 might be busy if (display->get_type() == DISPLAY_TYPE_EPAPER) { LowLevelDisplayEPaper* epaper = static_cast(display); epaper->full_refresh(); @@ -677,12 +678,19 @@ int main() } } else { // In launcher mode - process menu input + + // Wait for any active display refresh to finish before potentially loading a game (SD Card I/O) + // This prevents SPI bus conflicts between Core 0 (SD Card) and Core 1 (Display) + while (is_refresh_in_progress()) { + sleep_us(100); + } + bool game_selected = launcher.update(input); if (game_selected) { printf("Game launched successfully\n"); game_start_time = 0; // Force full clear for clean transition to game - display->clear(false); + // display->clear(false); // Unsafe while Core 1 might be busy // if (display->get_type() == DISPLAY_TYPE_EPAPER) { // LowLevelDisplayEPaper* epaper = static_cast(display); // epaper->full_refresh(); @@ -714,6 +722,23 @@ int main() if (time_since_last_frame >= TARGET_FRAME_TIME_MS) { // Only draw if Core 1 is finished with the buffer if (!is_refresh_in_progress()) { + // Update dirty rectangle optimization based on continuous updates + if (display->get_type() == DISPLAY_TYPE_ST7796) { + bool wants_opt = false; + if (launcher.is_game_selected()) { + Game* g = launcher.get_selected_game(); + if (g && g->wants_frame_updates()) { + wants_opt = true; + } + } + + if (dirty_rect_opt_state != wants_opt) { + LowLevelDisplayST7796* st7796_display = static_cast(display); + st7796_display->enable_dirty_rect(wants_opt); + dirty_rect_opt_state = wants_opt; + } + } + // Clear buffer and redraw entire UI with updated state memset(bit_buffer, 0, V_WIDTH * V_HEIGHT / 8); diff --git a/display/low_level_gui.cpp b/display/low_level_gui.cpp index aacd819..877a38e 100644 --- a/display/low_level_gui.cpp +++ b/display/low_level_gui.cpp @@ -1,361 +1,402 @@ -#include "low_level_render.h" #include "low_level_gui.h" +#include "low_level_render.h" #include #include -LowLevelWindow* validate_or_create_window(LowLevelWindow* window, LowLevelRenderer* renderer) { - if (window == nullptr) { - return new LowLevelWindow(0, 0, renderer->get_width(), renderer->get_height(), "Default Window"); - } - return window; +LowLevelWindow *validate_or_create_window(LowLevelWindow *window, + LowLevelRenderer *renderer) { + if (window == nullptr) { + return new LowLevelWindow(0, 0, renderer->get_width(), + renderer->get_height(), "Default Window"); + } + return window; } -LowLevelGUI::LowLevelGUI(LowLevelRenderer *rend, const Font& font) : renderer(rend), current_font(&font) {} +LowLevelGUI::LowLevelGUI(LowLevelRenderer *rend, const Font &font) + : renderer(rend), current_font(&font) {} -LowLevelWindow* LowLevelGUI::draw_new_window(int x, int y, int width, int height, const char *title) -{ - LowLevelWindow* w = new LowLevelWindow(x, y, width, height, title); - draw_window(w); - return w; +LowLevelWindow *LowLevelGUI::draw_new_window(int x, int y, int width, + int height, const char *title) { + LowLevelWindow *w = new LowLevelWindow(x, y, width, height, title); + draw_window(w); + return w; } -void LowLevelGUI::draw_window(LowLevelWindow* window){ - // Draw window border - - if (use_rounded_corners) - { - //shadow - renderer->draw_rounded_rectangle(window->x+3, window->y+3, window->width, window->height, 10, true, true); - renderer->draw_rounded_rectangle(window->x-2, window->y-2, window->width+2, window->height+2, 10, false, true); - renderer->draw_rounded_rectangle(window->x, window->y, window->width, window->height, 10, true); - } - else - { - renderer->draw_filled_rectangle(window->x + 3, window->y + 3, window->width + 2, window->height + 2, true, 2); - renderer->draw_filled_rectangle(window->x - 2, window->y - 2, window->width + 2, window->height + 2, false, 2); - renderer->draw_rectangle(window->x, window->y, window->width, window->height, true, 2); - } +void LowLevelGUI::draw_window(LowLevelWindow *window) { + // Draw window border - renderer->draw_line(window->x, window->y + 20, window->x + window->width - 1, window->y + 20, true, 1); - // draw closing 'X' button - int close_size = 12; - int close_x = window->x + window->width - close_size - 4; - int close_y = window->y + 4; - //renderer->draw_rectangle(close_x, close_y, close_size, close_size, true, 1); - renderer->draw_line(close_x + 3, close_y + 3, close_x + close_size - 4, close_y + close_size - 4, true, 1); - renderer->draw_line(close_x + close_size - 4, close_y + 3, close_x + 3, close_y + close_size - 4, true, 1); + if (use_rounded_corners) { + // shadow + renderer->draw_rounded_rectangle(window->x + 3, window->y + 3, + window->width, window->height, 10, true, + true); + renderer->draw_rounded_rectangle(window->x - 2, window->y - 2, + window->width + 2, window->height + 2, 10, + false, true); + renderer->draw_rounded_rectangle(window->x, window->y, window->width, + window->height, 10, true); + } else { + renderer->draw_filled_rectangle(window->x + 3, window->y + 3, + window->width + 2, window->height + 2, true, + 2); + renderer->draw_filled_rectangle(window->x - 2, window->y - 2, + window->width + 2, window->height + 2, + false, 2); + renderer->draw_rectangle(window->x, window->y, window->width, + window->height, true, 2); + } - const Font* original_font = renderer->get_current_font(); - renderer->set_font(current_font); - renderer->draw_string_scaled(window->x + 10, window->y + 3, window->title, 2); + renderer->draw_line(window->x, window->y + 20, window->x + window->width - 1, + window->y + 20, true, 1); + // draw closing 'X' button + int close_size = 12; + int close_x = window->x + window->width - close_size - 4; + int close_y = window->y + 4; + // renderer->draw_rectangle(close_x, close_y, close_size, close_size, true, + // 1); + renderer->draw_line(close_x + 3, close_y + 3, close_x + close_size - 4, + close_y + close_size - 4, true, 1); + renderer->draw_line(close_x + close_size - 4, close_y + 3, close_x + 3, + close_y + close_size - 4, true, 1); - renderer->set_font(original_font); + const Font *original_font = renderer->get_current_font(); + renderer->set_font(current_font); + renderer->draw_string_scaled(window->x + 10, window->y + 3, window->title, 2); + + renderer->set_font(original_font); } -void LowLevelGUI::draw_button(LowLevelWindow* window, int x, int y, const char *label, bool pressed, bool rounded) -{ - window = validate_or_create_window(window, renderer); - const Font* original_font = renderer->get_current_font(); - bool original_text_color = renderer->get_current_text_color(); - renderer->set_font(current_font); - int text_x = window->x + x + 5; - int text_y = window->y + y + 5; - int height = renderer->get_current_font()->get_char_height() * 2 + 10; - int width = int(renderer->draw_string_scaled(text_x, text_y, label, 2) * 1) + 30; +void LowLevelGUI::draw_button(LowLevelWindow *window, int x, int y, + const char *label, bool pressed, bool rounded) { + window = validate_or_create_window(window, renderer); + const Font *original_font = renderer->get_current_font(); + bool original_text_color = renderer->get_current_text_color(); + renderer->set_font(current_font); + int text_x = window->x + x + 5; + int text_y = window->y + y + 5; + int height = renderer->get_current_font()->get_char_height() * 2 + 10; + int width = renderer->get_string_width_scaled(label, 2) + 30; - if (pressed) - { - renderer->draw_rounded_rectangle(window->x + x - 1, window->y + y -1, width + 2, height+ 2, rounded ? 5 : 0, false, true); - renderer->draw_rounded_rectangle(window->x + x, window->y + y, width, height, rounded ? 5 : 0, true, true); - renderer->draw_rounded_rectangle(window->x + x + 2, window->y + y + 2, width - 4, height - 4, rounded ? 5 : 0, false, false); - } - else - { - renderer->draw_rounded_rectangle(window->x + x - 1, window->y + y -1, width + 2, height+ 2, rounded ? 5 : 0, false, true); - renderer->draw_rounded_rectangle(window->x + x, window->y + y, width, height, rounded ? 5 : 0, false, true); - renderer->draw_rounded_rectangle(window->x + x + 2, window->y + y + 2, width - 4, height - 4, rounded ? 5 : 0, true, false); - } - - renderer->set_text_color(!pressed); - renderer->draw_string_scaled(text_x, text_y, label, 2); - - renderer->set_font(original_font); - renderer->set_text_color(original_text_color); + if (pressed) { + renderer->draw_rounded_rectangle(window->x + x - 1, window->y + y - 1, + width + 2, height + 2, rounded ? 5 : 0, + false, true); + renderer->draw_rounded_rectangle(window->x + x, window->y + y, width, + height, rounded ? 5 : 0, true, true); + renderer->draw_rounded_rectangle(window->x + x + 2, window->y + y + 2, + width - 4, height - 4, rounded ? 5 : 0, + false, false); + } else { + renderer->draw_rounded_rectangle(window->x + x - 1, window->y + y - 1, + width + 2, height + 2, rounded ? 5 : 0, + false, true); + renderer->draw_rounded_rectangle(window->x + x, window->y + y, width, + height, rounded ? 5 : 0, false, true); + renderer->draw_rounded_rectangle(window->x + x + 2, window->y + y + 2, + width - 4, height - 4, rounded ? 5 : 0, + true, false); + } + + renderer->set_text_color(!pressed); + renderer->draw_string_scaled(text_x, text_y, label, 2); + + renderer->set_font(original_font); + renderer->set_text_color(original_text_color); } -void LowLevelGUI::draw_checkbox(LowLevelWindow* window, int x, int y, const char *label, bool checked) { - window = validate_or_create_window(window, renderer); - const Font* original_font = renderer->get_current_font(); - renderer->set_font(current_font); - int box_size = renderer->get_current_font()->get_char_height() * 2 ; - int box_x = window->x + x; - int box_y = window->y + y; - // Draw checkbox square - renderer->draw_rectangle(box_x, box_y, box_size, box_size, true, 1); - if (checked) - { - // Draw check mark - renderer->draw_line(box_x + 2, box_y + box_size / 2, box_x + box_size / 2, box_y + box_size - 3, true, 1); - renderer->draw_line(box_x + box_size / 2, box_y + box_size - 3, box_x + box_size - 2, box_y + 2, true, 1); - } - // Draw label - renderer->set_text_color(true); - renderer->draw_string_scaled(box_x + box_size + 5, box_y - 1, label, 2); - renderer->set_font(original_font); +void LowLevelGUI::draw_checkbox(LowLevelWindow *window, int x, int y, + const char *label, bool checked) { + window = validate_or_create_window(window, renderer); + const Font *original_font = renderer->get_current_font(); + renderer->set_font(current_font); + int box_size = renderer->get_current_font()->get_char_height() * 2; + int box_x = window->x + x; + int box_y = window->y + y; + // Draw checkbox square + renderer->draw_rectangle(box_x, box_y, box_size, box_size, true, 1); + if (checked) { + // Draw check mark + renderer->draw_line(box_x + 2, box_y + box_size / 2, box_x + box_size / 2, + box_y + box_size - 3, true, 1); + renderer->draw_line(box_x + box_size / 2, box_y + box_size - 3, + box_x + box_size - 2, box_y + 2, true, 1); + } + // Draw label + renderer->set_text_color(true); + renderer->draw_string_scaled(box_x + box_size + 5, box_y - 1, label, 2); + renderer->set_font(original_font); } -void LowLevelGUI::draw_radio_button(LowLevelWindow* window, int x, int y, const char *label, bool selected) { - window = validate_or_create_window(window, renderer); - const Font* original_font = renderer->get_current_font(); - renderer->set_font(current_font); - int radius = renderer->get_current_font()->get_char_height(); - int center_x = window->x + x + radius; - int center_y = window->y + y + radius; - // Draw outer circle - renderer->draw_circle(center_x, center_y, radius, true); - if (selected) - { - // Draw inner filled circle - renderer->draw_filled_circle(center_x, center_y, radius - 4, true); - } - // Draw label - renderer->set_text_color(true); - renderer->draw_string_scaled(center_x + radius + 5, center_y - radius / 2, label, 2); - renderer->set_font(original_font); - +void LowLevelGUI::draw_radio_button(LowLevelWindow *window, int x, int y, + const char *label, bool selected) { + window = validate_or_create_window(window, renderer); + const Font *original_font = renderer->get_current_font(); + renderer->set_font(current_font); + int radius = renderer->get_current_font()->get_char_height(); + int center_x = window->x + x + radius; + int center_y = window->y + y + radius; + // Draw outer circle + renderer->draw_circle(center_x, center_y, radius, true); + if (selected) { + // Draw inner filled circle + renderer->draw_filled_circle(center_x, center_y, radius - 4, true); + } + // Draw label + renderer->set_text_color(true); + renderer->draw_string_scaled(center_x + radius + 5, center_y - radius / 2, + label, 2); + renderer->set_font(original_font); } -void LowLevelGUI::draw_slider(LowLevelWindow* window, int x, int y, int width, int height, int position, char* label) { - window = validate_or_create_window(window, renderer); - int slider_x = window->x + x; - int slider_y = window->y + y + (label != nullptr ? 20 : 0); - position = std::max(0, std::min(100, position)); - // Draw slider track - renderer->draw_filled_rectangle(slider_x, slider_y + height / 2 - 2, width, 4, true, 1); - // Draw slider handle, considering position to be within [0, 100] - int handle_x = slider_x + (position * width / 100); - renderer->draw_filled_rectangle(handle_x - 5, slider_y, 10, height, true, 1); - renderer->draw_rectangle(handle_x - 6, slider_y - 1, 12, height + 2, true, 1); - - - const Font* original_font = renderer->get_current_font(); - renderer->set_font(current_font); - // draw current position value label on top of the slider - char pos_label[10]; - snprintf(pos_label, sizeof(pos_label), "%d", position); - renderer->draw_string_scaled(slider_x + width + 10, slider_y + (height / 2) - 5, pos_label, 1); - // Draw label if provided - if (label != nullptr) { - renderer->draw_string_scaled(slider_x, window->y + y, label, 2); - } - - renderer->set_font(original_font); - +void LowLevelGUI::draw_slider(LowLevelWindow *window, int x, int y, int width, + int height, int position, char *label) { + window = validate_or_create_window(window, renderer); + int slider_x = window->x + x; + int slider_y = window->y + y + (label != nullptr ? 20 : 0); + position = std::max(0, std::min(100, position)); + // Draw slider track + renderer->draw_filled_rectangle(slider_x, slider_y + height / 2 - 2, width, 4, + true, 1); + // Draw slider handle, considering position to be within [0, 100] + int handle_x = slider_x + (position * width / 100); + renderer->draw_filled_rectangle(handle_x - 5, slider_y, 10, height, true, 1); + renderer->draw_rectangle(handle_x - 6, slider_y - 1, 12, height + 2, true, 1); + const Font *original_font = renderer->get_current_font(); + renderer->set_font(current_font); + // draw current position value label on top of the slider + char pos_label[10]; + snprintf(pos_label, sizeof(pos_label), "%d", position); + renderer->draw_string_scaled(slider_x + width + 10, + slider_y + (height / 2) - 5, pos_label, 1); + // Draw label if provided + if (label != nullptr) { + renderer->draw_string_scaled(slider_x, window->y + y, label, 2); + } + renderer->set_font(original_font); } -void LowLevelGUI::draw_calendar(LowLevelWindow* window, int x, int y, int month, int year) { - window = validate_or_create_window(window, renderer); - const Font* original_font = renderer->get_current_font(); - renderer->set_font(current_font); +void LowLevelGUI::draw_calendar(LowLevelWindow *window, int x, int y, int month, + int year) { + window = validate_or_create_window(window, renderer); + const Font *original_font = renderer->get_current_font(); + renderer->set_font(current_font); - // 1. Draw Month and Year Header - char title[32]; - const char* month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; - snprintf(title, sizeof(title), "%s %04d", month_names[month - 1], year); - renderer->draw_string_scaled(window->x + x, window->y + y, title, 1); + // 1. Draw Month and Year Header + char title[32]; + const char *month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + snprintf(title, sizeof(title), "%s %04d", month_names[month - 1], year); + renderer->draw_string_scaled(window->x + x, window->y + y, title, 1); - // 2. Draw Days of the Week labels - const char* days[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}; - for (int i = 0; i < 7; i++) { - renderer->draw_string_scaled(window->x + x + (i * 20), window->y + y + 15, days[i], 1); - } + // 2. Draw Days of the Week labels + const char *days[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}; + for (int i = 0; i < 7; i++) { + renderer->draw_string_scaled(window->x + x + (i * 20), window->y + y + 15, + days[i], 1); + } - // 3. Calculate Month Metadata - // Get starting day of the week (0 = Sunday) using a simplified formula - struct tm first_day = {0}; - first_day.tm_mday = 1; - first_day.tm_mon = month - 1; - first_day.tm_year = year - 1900; - mktime(&first_day); - int start_col = first_day.tm_wday; + // 3. Calculate Month Metadata + // Get starting day of the week (0 = Sunday) using a simplified formula + struct tm first_day = {0}; + first_day.tm_mday = 1; + first_day.tm_mon = month - 1; + first_day.tm_year = year - 1900; + mktime(&first_day); + int start_col = first_day.tm_wday; - // Get number of days in the month - int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) { - days_in_month[1] = 29; - } - int total_days = days_in_month[month - 1]; + // Get number of days in the month + int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) { + days_in_month[1] = 29; + } + int total_days = days_in_month[month - 1]; - // 4. Draw the Days Grid - for (int day = 1; day <= total_days; day++) { - int index = start_col + day - 1; - int col = index % 7; - int row = index / 7; + // 4. Draw the Days Grid + for (int day = 1; day <= total_days; day++) { + int index = start_col + day - 1; + int col = index % 7; + int row = index / 7; - int cell_x = window->x + x + (col * 20); - int cell_y = window->y + y + 30 + (row * 20); + int cell_x = window->x + x + (col * 20); + int cell_y = window->y + y + 30 + (row * 20); - // Draw cell border/background - renderer->draw_rectangle(cell_x, cell_y, 20, 20, false, 1); + // Draw cell border/background + renderer->draw_rectangle(cell_x, cell_y, 20, 20, false, 1); - // Draw day number - char day_str[3]; - snprintf(day_str, sizeof(day_str), "%d", day); - - // Center the text slightly within the 20x20 cell - int offset_x = (day < 10) ? 7 : 2; - renderer->draw_string_scaled(cell_x + offset_x, cell_y + 5, day_str, 1); - } + // Draw day number + char day_str[3]; + snprintf(day_str, sizeof(day_str), "%d", day); - renderer->set_font(original_font); + // Center the text slightly within the 20x20 cell + int offset_x = (day < 10) ? 7 : 2; + renderer->draw_string_scaled(cell_x + offset_x, cell_y + 5, day_str, 1); + } + + renderer->set_font(original_font); } -void LowLevelGUI::draw_textbox(LowLevelWindow* window, int x, int y, int width, int height, const char* content, bool focused) { - window = validate_or_create_window(window, renderer); - // Draw textbox border - int box_x = window->x + x; - int box_y = window->y + y; - if(focused) { - renderer->draw_filled_rectangle(box_x, box_y, width, height, true, 1); - renderer->draw_rectangle(box_x - 1, box_y - 1, width + 2, height + 2, false, 1); - } else { - renderer->draw_filled_rectangle(box_x, box_y, width, height, false, 1); - renderer->draw_rectangle(box_x - 1, box_y - 1, width + 2, height + 2, true, 1); - } +void LowLevelGUI::draw_textbox(LowLevelWindow *window, int x, int y, int width, + int height, const char *content, bool focused) { + window = validate_or_create_window(window, renderer); + // Draw textbox border + int box_x = window->x + x; + int box_y = window->y + y; + if (focused) { + renderer->draw_filled_rectangle(box_x, box_y, width, height, true, 1); + renderer->draw_rectangle(box_x - 1, box_y - 1, width + 2, height + 2, false, + 1); + } else { + renderer->draw_filled_rectangle(box_x, box_y, width, height, false, 1); + renderer->draw_rectangle(box_x - 1, box_y - 1, width + 2, height + 2, true, + 1); + } - // Draw content text inside the textbox - const Font* original_font = renderer->get_current_font(); - renderer->set_font(current_font); - renderer->set_text_color(true); + // Draw content text inside the textbox + const Font *original_font = renderer->get_current_font(); + renderer->set_font(current_font); + renderer->set_text_color(true); - int text_x = box_x + 5; - int text_y = box_y + 5; - renderer->draw_string_scaled(text_x, text_y, content, 1); + int text_x = box_x + 5; + int text_y = box_y + 5; + renderer->draw_string_scaled(text_x, text_y, content, 1); - renderer->set_font(original_font); + renderer->set_font(original_font); } -void LowLevelGUI::draw_tab(LowLevelWindow* window, int x, int y, int width, int height, const char* label, bool selected) { - window = validate_or_create_window(window, renderer); - int tab_x = window->x + x; - int tab_y = window->y + y; - if (selected) { - renderer->draw_filled_rectangle(tab_x, tab_y, width, height, true, 1); - renderer->draw_rectangle(tab_x - 1, tab_y - 1, width + 2, height + 2, false, 1); - } else { - renderer->draw_filled_rectangle(tab_x, tab_y, width, height, false, 1); - renderer->draw_rectangle(tab_x - 1, tab_y - 1, width + 2, height + 2, true, 1); - } +void LowLevelGUI::draw_tab(LowLevelWindow *window, int x, int y, int width, + int height, const char *label, bool selected) { + window = validate_or_create_window(window, renderer); + int tab_x = window->x + x; + int tab_y = window->y + y; + if (selected) { + renderer->draw_filled_rectangle(tab_x, tab_y, width, height, true, 1); + renderer->draw_rectangle(tab_x - 1, tab_y - 1, width + 2, height + 2, false, + 1); + } else { + renderer->draw_filled_rectangle(tab_x, tab_y, width, height, false, 1); + renderer->draw_rectangle(tab_x - 1, tab_y - 1, width + 2, height + 2, true, + 1); + } - const Font* original_font = renderer->get_current_font(); - renderer->set_font(current_font); - renderer->set_text_color(true); + const Font *original_font = renderer->get_current_font(); + renderer->set_font(current_font); + renderer->set_text_color(true); - // Center the label within the tab - int text_width = int(renderer->draw_string_scaled(0, 0, label, 1) * 0.75); - int text_x = tab_x + (width - text_width) / 2; - int text_y = tab_y + (height - renderer->get_current_font()->get_char_height()) / 2; - renderer->draw_string_scaled(text_x, text_y, label, 1); + // Center the label within the tab + int text_width = renderer->get_string_width_scaled(label, 1); + int text_x = tab_x + (width - text_width) / 2; + int text_y = + tab_y + (height - renderer->get_current_font()->get_char_height()) / 2; + renderer->draw_string_scaled(text_x, text_y, label, 1); - renderer->set_font(original_font); + renderer->set_font(original_font); } -void LowLevelGUI::draw_status_bar(LowLevelWindow* window, int x, int y, int width, const char* label, const char* sublabel, int percentage, const char* value_text) { - window = validate_or_create_window(window, renderer); - int base_x = window->x + x; - int base_y = window->y + y; - - const Font* original_font = renderer->get_current_font(); - renderer->set_font(current_font); - - // Draw Main Label (e.g., "PANELS") - renderer->draw_string_scaled(base_x, base_y, label, 2); - // Draw Sublabel and Value (e.g., "Weekly Average Charge" and "190KWH") - renderer->draw_string_scaled(base_x, base_y + 15, sublabel, 1); - - int val_width = strlen(value_text) * 8; // Approximation - renderer->draw_string_scaled(base_x + width - val_width, base_y + 15, value_text, 1); +void LowLevelGUI::draw_status_bar(LowLevelWindow *window, int x, int y, + int width, const char *label, + const char *sublabel, int percentage, + const char *value_text) { + window = validate_or_create_window(window, renderer); + int base_x = window->x + x; + int base_y = window->y + y; - // Draw Bar Container (Rounded) - int bar_y = base_y + 30; - int bar_height = 12; - renderer->draw_rounded_rectangle(base_x, bar_y, width, bar_height, 6, true); - - // Draw Progress Fill - int fill_width = (percentage * width) / 100; - if (fill_width > 4) { - renderer->draw_rounded_rectangle(base_x + 2, bar_y + 2, fill_width - 4, bar_height - 4, 4, true, true); - } - - renderer->set_font(original_font); + const Font *original_font = renderer->get_current_font(); + renderer->set_font(current_font); + + // Draw Main Label (e.g., "PANELS") + renderer->draw_string_scaled(base_x, base_y, label, 2); + // Draw Sublabel and Value (e.g., "Weekly Average Charge" and "190KWH") + renderer->draw_string_scaled(base_x, base_y + 15, sublabel, 1); + + int val_width = strlen(value_text) * 8; // Approximation + renderer->draw_string_scaled(base_x + width - val_width, base_y + 15, + value_text, 1); + + // Draw Bar Container (Rounded) + int bar_y = base_y + 30; + int bar_height = 12; + renderer->draw_rounded_rectangle(base_x, bar_y, width, bar_height, 6, true); + + // Draw Progress Fill + int fill_width = (percentage * width) / 100; + if (fill_width > 4) { + renderer->draw_rounded_rectangle(base_x + 2, bar_y + 2, fill_width - 4, + bar_height - 4, 4, true, true); + } + + renderer->set_font(original_font); } -void LowLevelGUI::draw_circular_gauge(LowLevelWindow* window, int x, int y, int width, const char* label, int percentage) { - window = validate_or_create_window(window, renderer); - int base_x = window->x + x; - int base_y = window->y + y; - int height = 50; - - // Draw pill-shaped container - renderer->draw_rounded_rectangle(base_x, base_y, width, height, height/2, true); - - const Font* original_font = renderer->get_current_font(); - renderer->set_font(current_font); - - // Draw Label - renderer->draw_string_scaled(base_x + 20, base_y + 18, label, 2); - - // Draw Circular Gauge on the right - int centerX = base_x + width - 30; - int centerY = base_y + 25; - int radius = 18; - - // Draw background track (dimmed) - renderer->draw_circle(centerX, centerY, radius, true); - - // Draw percentage text inside circle - char buf[5]; - snprintf(buf, sizeof(buf), "%d%%", percentage); - renderer->draw_string_scaled(centerX - 10, centerY - 5, buf, 1); - - // Note: If your renderer supports arcs: - // renderer->draw_arc(centerX, centerY, radius, 0, (percentage * 360) / 100); - - renderer->set_font(original_font); +void LowLevelGUI::draw_circular_gauge(LowLevelWindow *window, int x, int y, + int width, const char *label, + int percentage) { + window = validate_or_create_window(window, renderer); + int base_x = window->x + x; + int base_y = window->y + y; + int height = 50; + + // Draw pill-shaped container + renderer->draw_rounded_rectangle(base_x, base_y, width, height, height / 2, + true); + + const Font *original_font = renderer->get_current_font(); + renderer->set_font(current_font); + + // Draw Label + renderer->draw_string_scaled(base_x + 20, base_y + 18, label, 2); + + // Draw Circular Gauge on the right + int centerX = base_x + width - 30; + int centerY = base_y + 25; + int radius = 18; + + // Draw background track (dimmed) + renderer->draw_circle(centerX, centerY, radius, true); + + // Draw percentage text inside circle + char buf[5]; + snprintf(buf, sizeof(buf), "%d%%", percentage); + renderer->draw_string_scaled(centerX - 10, centerY - 5, buf, 1); + + // Note: If your renderer supports arcs: + // renderer->draw_arc(centerX, centerY, radius, 0, (percentage * 360) / 100); + + renderer->set_font(original_font); } +void LowLevelGUI::draw_notification(LowLevelWindow *window, int x, int y, + int width, const char *time, + const char *message) { + // window = validate_or_create_window(window, renderer); + int base_x = window->x + x; + int base_y = window->y + y; -void LowLevelGUI::draw_notification(LowLevelWindow* window, int x, int y, int width, const char* time, const char* message) { - // window = validate_or_create_window(window, renderer); - int base_x = window->x + x; - int base_y = window->y + y; - - // Draw dark background - renderer->draw_rounded_rectangle(base_x, base_y, width, 100, 15, true, true); - - const Font* original_font = renderer->get_current_font(); - renderer->set_font(current_font); - renderer->set_text_color(false); // Assume false is white/light on dark - - renderer->draw_string_scaled(base_x + 15, base_y + 10, time, 1); - renderer->draw_string_scaled(base_x + width - 20, base_y + 10, "x", 1); - - // Simple word wrap or multi-line manual draw for the message - // This is a simplified version - renderer->draw_string_scaled(base_x + 15, base_y + 30, message, 2); - - renderer->set_text_color(true); - renderer->set_font(original_font); + // Draw dark background + renderer->draw_rounded_rectangle(base_x, base_y, width, 100, 15, true, true); + + const Font *original_font = renderer->get_current_font(); + renderer->set_font(current_font); + renderer->set_text_color(false); // Assume false is white/light on dark + + renderer->draw_string_scaled(base_x + 15, base_y + 10, time, 1); + renderer->draw_string_scaled(base_x + width - 20, base_y + 10, "x", 1); + + // Simple word wrap or multi-line manual draw for the message + // This is a simplified version + renderer->draw_string_scaled(base_x + 15, base_y + 30, message, 2); + + renderer->set_text_color(true); + renderer->set_font(original_font); } -void LowLevelGUI::draw_large_clock(LowLevelWindow* window, int x, int y, const char* time_str) { - window = validate_or_create_window(window, renderer); - const Font* original_font = renderer->get_current_font(); - renderer->set_font(current_font); - // Draw the time significantly larger (scale 5 or 6) - renderer->draw_string_scaled(window->x + x, window->y + y, time_str, 6); - renderer->set_font(original_font); +void LowLevelGUI::draw_large_clock(LowLevelWindow *window, int x, int y, + const char *time_str) { + window = validate_or_create_window(window, renderer); + const Font *original_font = renderer->get_current_font(); + renderer->set_font(current_font); + // Draw the time significantly larger (scale 5 or 6) + renderer->draw_string_scaled(window->x + x, window->y + y, time_str, 6); + renderer->set_font(original_font); } - diff --git a/display/low_level_render.cpp b/display/low_level_render.cpp index 648ed52..3e16956 100644 --- a/display/low_level_render.cpp +++ b/display/low_level_render.cpp @@ -671,3 +671,57 @@ int LowLevelRenderer::draw_string_scaled(int x, int y, const char* text, int sca } return current_x; } +int LowLevelRenderer::get_char_width_scaled(char c, int scale) { + if (!current_font) + return 0; + + if (c < 32 || c > 127) + return 0; + if (scale < 1) + scale = 1; + + int font_idx = c - 32; + const unsigned char *char_data = current_font->get_char_data(font_idx); + if (!char_data) + return 0; + + int bytes_per_char = current_font->get_bytes_per_char(); + + // Find the actual width by skipping trailing empty columns + int actual_width = 0; + for (int col = bytes_per_char - 1; col >= 0; col--) { + if (char_data[col] != 0) { + actual_width = col + 1; + break; + } + } + + return actual_width * scale; +} + +int LowLevelRenderer::get_string_width_scaled(const char *text, int scale, + int spacing) { + if (!current_font) + return 0; + int width = 0; + int i = 0; + while (text[i] != '\0') { + int char_width = get_char_width_scaled(text[i], scale); + // Add spacing only if it's not the last character, but logic usually adds + // spacing after each char In drawn_string_scaled: current_x += char_width + + // (spacing * scale); So width accumulates char_width + spacing*scale. + // However, the last character shouldn't really have spacing if we want + // exact bounding box, but let's match draw_string_scaled behavior which + // effectively advances cursor. Wait, draw_string_scaled returns + // `current_x`. If x=0, current_x ends up at sum(char_width + + // spacing*scale). + + width += char_width + (spacing * scale); + i++; + } + // Correction: draw_string_scaled includes spacing after the last character. + // If we want exact pixel width of the visible text, we might want to subtract + // the last spacing. But for UI alignment, usually cursor advancement is fine. + // Let's stick to returning what draw_string_scaled would add to x. + return width; +} diff --git a/display/low_level_render.h b/display/low_level_render.h index 7fb2ab0..373f7ee 100644 --- a/display/low_level_render.h +++ b/display/low_level_render.h @@ -1,9 +1,8 @@ -// class that handles low-level rendering operations, such as drawing pixels and shapes to the display. -// This class is framework-agnostic and focuses solely on manipulating a 1-bit per pixel buffer. -// Constructor Args: -// uint8_t* buffer: Pointer to the bit buffer -// int width: Display width in pixels -// int height: Display height in pixels +// class that handles low-level rendering operations, such as drawing pixels and +// shapes to the display. This class is framework-agnostic and focuses solely on +// manipulating a 1-bit per pixel buffer. Constructor Args: uint8_t* buffer: +// Pointer to the bit buffer int width: Display width in pixels int height: +// Display height in pixels #ifndef LOW_LEVEL_RENDER_H #define LOW_LEVEL_RENDER_H @@ -15,26 +14,28 @@ // Font class that holds font data and dimensions class Font { private: - const unsigned char* data; - int num_chars; - int bytes_per_char; - int char_height; + const unsigned char *data; + int num_chars; + int bytes_per_char; + int char_height; public: - Font(const unsigned char* font_data, int num_chars, int bytes_per_char, int char_height) - : data(font_data), num_chars(num_chars), bytes_per_char(bytes_per_char), - char_height(char_height) {} + Font(const unsigned char *font_data, int num_chars, int bytes_per_char, + int char_height) + : data(font_data), num_chars(num_chars), bytes_per_char(bytes_per_char), + char_height(char_height) {} - const unsigned char* get_data() const { return data; } - int get_num_chars() const { return num_chars; } - int get_bytes_per_char() const { return bytes_per_char; } - int get_char_height() const { return char_height; } - - // Get a specific character's data - const unsigned char* get_char_data(int char_index) const { - if (char_index < 0 || char_index >= num_chars) return nullptr; - return data + (char_index * bytes_per_char); - } + const unsigned char *get_data() const { return data; } + int get_num_chars() const { return num_chars; } + int get_bytes_per_char() const { return bytes_per_char; } + int get_char_height() const { return char_height; } + + // Get a specific character's data + const unsigned char *get_char_data(int char_index) const { + if (char_index < 0 || char_index >= num_chars) + return nullptr; + return data + (char_index * bytes_per_char); + } }; // Font extern declarations @@ -93,61 +94,78 @@ extern Font font_zxpix_obj; class LowLevelRenderer { private: - uint8_t* bit_buffer; - int V_WIDTH; - int V_HEIGHT; - const Font* current_font; - bool clipping_enabled; - int clip_x, clip_y, clip_width, clip_height; - bool text_color; - void draw_corner_arc(int center_x, int center_y, int radius, int quadrant, bool on); - void fill_bottom_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on); - void fill_top_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on); - bool is_point_in_clip_rect(int x, int y); + uint8_t *bit_buffer; + int V_WIDTH; + int V_HEIGHT; + const Font *current_font; + bool clipping_enabled; + int clip_x, clip_y, clip_width, clip_height; + bool text_color; + void draw_corner_arc(int center_x, int center_y, int radius, int quadrant, + bool on); + void fill_bottom_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, + bool on); + void fill_top_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, + bool on); + bool is_point_in_clip_rect(int x, int y); public: - LowLevelRenderer(uint8_t* buffer, int width, int height); + LowLevelRenderer(uint8_t *buffer, int width, int height); - // Font management - void set_font(const Font* font); - void set_text_color(bool color); - const Font* get_current_font() const { return current_font; } - bool get_current_text_color() const { return text_color; } - int get_width() const { return V_WIDTH; } - int get_height() const { return V_HEIGHT; } + // Font management + void set_font(const Font *font); + void set_text_color(bool color); + const Font *get_current_font() const { return current_font; } + bool get_current_text_color() const { return text_color; } + int get_width() const { return V_WIDTH; } + int get_height() const { return V_HEIGHT; } - // --- 1-BIT DRAWING PRIMITIVES --- - void set_pixel(int x, int y, bool on); - void draw_line(int x0, int y0, int x1, int y1, bool on, int width = 1); - void draw_rectangle(int x, int y, int width, int height, bool on, int line_width); - void draw_filled_rectangle(int x, int y, int width, int height, bool on, int line_width); - void draw_rounded_rectangle(int x, int y, int width, int height, int radius, bool on, bool filled = false); - void draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on); - void draw_filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on); - void draw_ellipse(int center_x, int center_y, int radius_x, int radius_y, bool on); - void draw_filled_ellipse(int center_x, int center_y, int radius_x, int radius_y, bool on); - void draw_polygon(const std::vector>& points, bool on); - void draw_filled_polygon(const std::vector>& points, bool on); - void draw_arc(int center_x, int center_y, int radius, int start_angle, int end_angle, bool on); - - // Bitmap drawing - void draw_bitmap(const unsigned char* bitmap, int x, int y, int width, int height, bool invert = false); - - // Clipping functions - void set_clip_rect(int x, int y, int width, int height); - void reset_clip_rect(); - bool is_clipping_enabled() const; - - // Buffer operations - void invert_buffer(); - void clear_buffer(); - - void draw_circle(int x, int y, int radius, bool on); - void draw_filled_circle(int x, int y, int radius, bool on); - int draw_char_vcol(int x, int y, char c); - void draw_string(int x, int y, const std::string &text, int spacing = 1); - int draw_char_scaled(int x, int y, char c, int scale); - int draw_string_scaled(int x, int y, const char* text, int scale, int spacing = 1); + // --- 1-BIT DRAWING PRIMITIVES --- + void set_pixel(int x, int y, bool on); + void draw_line(int x0, int y0, int x1, int y1, bool on, int width = 1); + void draw_rectangle(int x, int y, int width, int height, bool on, + int line_width); + void draw_filled_rectangle(int x, int y, int width, int height, bool on, + int line_width); + void draw_rounded_rectangle(int x, int y, int width, int height, int radius, + bool on, bool filled = false); + void draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on); + void draw_filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, + bool on); + void draw_ellipse(int center_x, int center_y, int radius_x, int radius_y, + bool on); + void draw_filled_ellipse(int center_x, int center_y, int radius_x, + int radius_y, bool on); + void draw_polygon(const std::vector> &points, bool on); + void draw_filled_polygon(const std::vector> &points, + bool on); + void draw_arc(int center_x, int center_y, int radius, int start_angle, + int end_angle, bool on); + + // Bitmap drawing + void draw_bitmap(const unsigned char *bitmap, int x, int y, int width, + int height, bool invert = false); + + // Clipping functions + void set_clip_rect(int x, int y, int width, int height); + void reset_clip_rect(); + bool is_clipping_enabled() const; + + // Buffer operations + void invert_buffer(); + void clear_buffer(); + + void draw_circle(int x, int y, int radius, bool on); + void draw_filled_circle(int x, int y, int radius, bool on); + int draw_char_vcol(int x, int y, char c); + void draw_string(int x, int y, const std::string &text, int spacing = 1); + int draw_char_scaled(int x, int y, char c, int scale); + int draw_string_scaled(int x, int y, const char *text, int scale, + int spacing = 1); + + // Width calculation without drawing + int get_char_width_scaled(char c, int scale); + int get_string_width_scaled(const char *text, int scale, int spacing = 1); }; #endif // LOW_LEVEL_RENDER_H \ No newline at end of file diff --git a/emulator/build_and_run.sh b/emulator/build_and_run.sh index a83f23c..60d34c9 100755 --- a/emulator/build_and_run.sh +++ b/emulator/build_and_run.sh @@ -1,8 +1,9 @@ #!/bin/bash set -e -echo "[Emulator] Cleaning old build..." -make clean +echo "[Emulator] Generating build files..." +cmake . + echo "[Emulator] Building..." make diff --git a/emulator/game_launcher.cpp b/emulator/game_launcher.cpp index d380d34..9cad155 100644 --- a/emulator/game_launcher.cpp +++ b/emulator/game_launcher.cpp @@ -1,164 +1,178 @@ // Copy of game_launcher.cpp for emulator build #include "game_launcher.h" -#include "input_manager.h" -#include "../display/low_level_render.h" #include "../display/low_level_gui.h" +#include "../display/low_level_render.h" +#include "input_manager.h" #include extern Font font_5x5_obj; -GameLauncher::GameLauncher(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager) - : width(width), height(height), renderer(renderer), gui(gui), input_manager(input_manager), - selected_index(0), selected_game(nullptr), current_page(0) {} -void GameLauncher::register_game(const char* name, const char* description, - std::function factory) { - GameEntry entry; - entry.name = name; - entry.description = description; - entry.factory = factory; - games.push_back(entry); - printf("Registered game: %s - %s\n", name, description); +GameLauncher::GameLauncher(uint16_t width, uint16_t height, + LowLevelRenderer *renderer, LowLevelGUI *gui, + InputManager *input_manager) + : width(width), height(height), renderer(renderer), gui(gui), + input_manager(input_manager), selected_index(0), selected_game(nullptr), + current_page(0) {} +void GameLauncher::register_game( + const char *name, const char *description, + std::function + factory) { + GameEntry entry; + entry.name = name; + entry.description = description; + entry.factory = factory; + games.push_back(entry); + printf("Registered game: %s - %s\n", name, description); } void GameLauncher::draw() { - LowLevelWindow* window = gui->draw_new_window(10, 10, width - 20, height - 20, "Game Launcher"); + LowLevelWindow *window = + gui->draw_new_window(10, 10, width - 20, height - 20, "Game Launcher"); + renderer->set_font(&font_5x5_obj); + + int total_pages = get_total_pages(); + char title[64]; + snprintf(title, sizeof(title), "Select a Game: (Page %d/%d)", + current_page + 1, total_pages); + renderer->draw_string_scaled(30, 40, title, 2); + + int page_start = get_page_start_index(); + int page_end = get_page_end_index(); + + for (int i = page_start; i < page_end && i < (int)games.size(); i++) { + int item_index = i - page_start; + int y = MENU_Y_START + (item_index * MENU_ITEM_HEIGHT); + bool is_selected = (i == selected_index); + gui->draw_button(window, 20, y, games[i].name, is_selected, true); renderer->set_font(&font_5x5_obj); - - int total_pages = get_total_pages(); - char title[64]; - snprintf(title, sizeof(title), "Select a Game: (Page %d/%d)", current_page + 1, total_pages); - renderer->draw_string_scaled(30, 40, title, 2); - + renderer->set_text_color(true); + renderer->draw_string_scaled(50, y + 36, games[i].description, 1); + } + + if (total_pages > 1) { + int button_y = height - 65; + renderer->set_font(&font_5x5_obj); + gui->draw_button(window, PREV_BUTTON_X, button_y, "PREV", false, true); + gui->draw_button(window, NEXT_BUTTON_X, button_y, "NEXT", false, true); + renderer->set_font(&font_5x5_obj); + renderer->draw_string_scaled(30, height - 25, "Touch buttons or KEY0/KEY1", + 1); + } else { + renderer->set_font(&font_5x5_obj); + renderer->draw_string_scaled( + 30, height - 35, "KEY0: Navigate | KEY1: Select | Touch to play", 1); + } +} +bool GameLauncher::update(const InputEvent &event) { + bool needs_refresh = false; + int total_pages = get_total_pages(); + + switch (event.type) { + case INPUT_TOUCH_DOWN: { + // Check if touch is on navigation buttons (if multiple pages) + if (total_pages > 1) { + if (event.x >= PREV_BUTTON_X && event.x < PREV_BUTTON_X + BUTTON_WIDTH && + event.y >= NAV_BUTTON_Y && event.y < NAV_BUTTON_Y + BUTTON_HEIGHT) { + if (current_page > 0) { + current_page--; + selected_index = get_page_start_index(); + needs_refresh = true; + } + break; + } + if (event.x >= NEXT_BUTTON_X && event.x < NEXT_BUTTON_X + BUTTON_WIDTH && + event.y >= NAV_BUTTON_Y && event.y < NAV_BUTTON_Y + BUTTON_HEIGHT) { + if (current_page + 1 < total_pages) { + current_page++; + selected_index = get_page_start_index(); + needs_refresh = true; + } + break; + } + } + int page_start = get_page_start_index(); int page_end = get_page_end_index(); - + for (int i = page_start; i < page_end && i < (int)games.size(); i++) { - int item_index = i - page_start; - int y = MENU_Y_START + (item_index * MENU_ITEM_HEIGHT); - bool is_selected = (i == selected_index); - gui->draw_button(window, 20, y, games[i].name, is_selected, true); - renderer->set_font(&font_5x5_obj); - renderer->set_text_color(true); - renderer->draw_string_scaled(50, y + 36, games[i].description, 1); + int item_index = i - page_start; + int y = MENU_Y_START + (item_index * MENU_ITEM_HEIGHT); + if (event.y >= y - 5 && event.y < y + MENU_ITEM_HEIGHT - 5) { + selected_game = + games[i].factory(width, height, renderer, gui, input_manager); + if (selected_game) { + selected_game->init(); + return true; + } + break; + } } - - if (total_pages > 1) { - int button_y = height - 65; - gui->draw_button(window, PREV_BUTTON_X, button_y, "< PREV", false, true); - gui->draw_button(window, NEXT_BUTTON_X, button_y, "NEXT >", false, true); - renderer->set_font(&font_5x5_obj); - renderer->draw_string_scaled(30, height - 25, "Touch buttons or KEY0/KEY1", 1); + break; + } + case INPUT_BUTTON_0: { + int page_start = get_page_start_index(); + int page_end = get_page_end_index(); + + if (page_end - page_start > 1) { + int old_index = selected_index; + selected_index++; + if (selected_index >= page_end) { + if (current_page + 1 < total_pages) { + current_page++; + selected_index = get_page_start_index(); + } else { + current_page = 0; + selected_index = 0; + } + } else if (selected_index < page_start) { + selected_index = page_start; + } } else { - renderer->set_font(&font_5x5_obj); - renderer->draw_string_scaled(30, height - 35, "KEY0: Navigate | KEY1: Select | Touch to play", 1); + if (current_page + 1 < total_pages) { + current_page++; + selected_index = get_page_start_index(); + } else { + current_page = 0; + selected_index = 0; + } } -} -bool GameLauncher::update(const InputEvent& event) { - bool needs_refresh = false; - int total_pages = get_total_pages(); - - switch (event.type) { - case INPUT_TOUCH_DOWN: { - // Check if touch is on navigation buttons (if multiple pages) - if (total_pages > 1) { - if (event.x >= PREV_BUTTON_X && event.x < PREV_BUTTON_X + BUTTON_WIDTH && - event.y >= NAV_BUTTON_Y && event.y < NAV_BUTTON_Y + BUTTON_HEIGHT) { - if (current_page > 0) { - current_page--; - selected_index = get_page_start_index(); - needs_refresh = true; - } - break; - } - if (event.x >= NEXT_BUTTON_X && event.x < NEXT_BUTTON_X + BUTTON_WIDTH && - event.y >= NAV_BUTTON_Y && event.y < NAV_BUTTON_Y + BUTTON_HEIGHT) { - if (current_page + 1 < total_pages) { - current_page++; - selected_index = get_page_start_index(); - needs_refresh = true; - } - break; - } - } - - int page_start = get_page_start_index(); - int page_end = get_page_end_index(); - - for (int i = page_start; i < page_end && i < (int)games.size(); i++) { - int item_index = i - page_start; - int y = MENU_Y_START + (item_index * MENU_ITEM_HEIGHT); - if (event.y >= y - 5 && event.y < y + MENU_ITEM_HEIGHT - 5) { - selected_game = games[i].factory(width, height, renderer, gui, input_manager); - if (selected_game) { - selected_game->init(); - return true; - } - break; - } - } - break; - } - case INPUT_BUTTON_0: { - int page_start = get_page_start_index(); - int page_end = get_page_end_index(); - - if (page_end - page_start > 1) { - int old_index = selected_index; - selected_index++; - if (selected_index >= page_end) { - if (current_page + 1 < total_pages) { - current_page++; - selected_index = get_page_start_index(); - } else { - current_page = 0; - selected_index = 0; - } - } else if (selected_index < page_start) { - selected_index = page_start; - } - } else { - if (current_page + 1 < total_pages) { - current_page++; - selected_index = get_page_start_index(); - } else { - current_page = 0; - selected_index = 0; - } - } - needs_refresh = true; - break; - } - case INPUT_BUTTON_1: { - if (selected_index >= 0 && selected_index < (int)games.size()) { - selected_game = games[selected_index].factory(width, height, renderer, gui, input_manager); - if (selected_game) { - selected_game->init(); - return true; - } - } - break; - } - default: - break; + needs_refresh = true; + break; + } + case INPUT_BUTTON_1: { + if (selected_index >= 0 && selected_index < (int)games.size()) { + selected_game = games[selected_index].factory(width, height, renderer, + gui, input_manager); + if (selected_game) { + selected_game->init(); + return true; + } } - return needs_refresh; + break; + } + default: + break; + } + return needs_refresh; } -Game* GameLauncher::get_selected_game() { return selected_game; } +Game *GameLauncher::get_selected_game() { return selected_game; } void GameLauncher::reset() { - if (selected_game) { - delete selected_game; - selected_game = nullptr; - } - selected_index = 0; - current_page = 0; + if (selected_game) { + delete selected_game; + selected_game = nullptr; + } + selected_index = 0; + current_page = 0; } int GameLauncher::get_total_pages() const { - if (games.empty()) return 1; - return (games.size() + GAMES_PER_PAGE - 1) / GAMES_PER_PAGE; + if (games.empty()) + return 1; + return (games.size() + GAMES_PER_PAGE - 1) / GAMES_PER_PAGE; } int GameLauncher::get_page_start_index() const { - return current_page * GAMES_PER_PAGE; + return current_page * GAMES_PER_PAGE; } int GameLauncher::get_page_end_index() const { - return (current_page + 1) * GAMES_PER_PAGE; + return (current_page + 1) * GAMES_PER_PAGE; } diff --git a/lib/serial_uploader.cpp b/lib/serial_uploader.cpp index 1d6dda4..cdaa785 100644 --- a/lib/serial_uploader.cpp +++ b/lib/serial_uploader.cpp @@ -173,7 +173,7 @@ bool SerialUploader::complete_launch() { return launched; } -bool SerialUploader::process() { +bool SerialUploader::process(bool spi_busy) { if (state == IDLE) { // Check for "UPLOAD" command int c = getchar_timeout_us(0); @@ -311,6 +311,9 @@ bool SerialUploader::process() { } if (state == WRITING_FILE) { + // Wait if SPI bus is busy (e.g. display refresh in progress on other core) + if (spi_busy) return false; + if (write_file_to_sd()) { state = LAUNCHING_GAME; // Prepare for launch by scanning games, but don't actually launch yet diff --git a/lib/serial_uploader.h b/lib/serial_uploader.h index 28493f8..d6678cd 100644 --- a/lib/serial_uploader.h +++ b/lib/serial_uploader.h @@ -12,7 +12,8 @@ public: // Process incoming serial data (call this frequently in main loop) // Returns true if a game was launched - bool process(); + // spi_busy: set to true if SPI bus is currently in use by another core (e.g. display refresh) + bool process(bool spi_busy = false); // Check if uploader wants to launch a game (after upload complete) bool wants_to_launch_game() const { return state == LAUNCHING_GAME; }