Fix emulator build script, UI rendering, and clean up repo
This commit is contained in:
10
.gitignore
vendored
10
.gitignore
vendored
@@ -10,3 +10,13 @@ CMakeFiles/
|
|||||||
*.cmake
|
*.cmake
|
||||||
emulator/build/
|
emulator/build/
|
||||||
.DS_Store
|
.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*
|
||||||
33
basic1.cpp
33
basic1.cpp
@@ -573,10 +573,11 @@ int main()
|
|||||||
uint32_t last_frame_time = 0;
|
uint32_t last_frame_time = 0;
|
||||||
|
|
||||||
bool needs_refresh = false; // Track if screen needs redraw
|
bool needs_refresh = false; // Track if screen needs redraw
|
||||||
|
bool dirty_rect_opt_state = (display->get_type() == DISPLAY_TYPE_ST7796);
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
// 0. Process serial uploads (for rapid game iteration)
|
// 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 a game, wait until it's safe (no display refresh)
|
||||||
if (serial_uploader.wants_to_launch_game() && !is_refresh_in_progress()) {
|
if (serial_uploader.wants_to_launch_game() && !is_refresh_in_progress()) {
|
||||||
@@ -645,7 +646,7 @@ int main()
|
|||||||
launcher.reset();
|
launcher.reset();
|
||||||
needs_refresh = true;
|
needs_refresh = true;
|
||||||
// Force full clear for clean transition
|
// 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) {
|
if (display->get_type() == DISPLAY_TYPE_EPAPER) {
|
||||||
LowLevelDisplayEPaper* epaper = static_cast<LowLevelDisplayEPaper*>(display);
|
LowLevelDisplayEPaper* epaper = static_cast<LowLevelDisplayEPaper*>(display);
|
||||||
epaper->full_refresh();
|
epaper->full_refresh();
|
||||||
@@ -667,7 +668,7 @@ int main()
|
|||||||
launcher.reset();
|
launcher.reset();
|
||||||
needs_refresh = true;
|
needs_refresh = true;
|
||||||
// Force full clear for clean transition
|
// 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) {
|
if (display->get_type() == DISPLAY_TYPE_EPAPER) {
|
||||||
LowLevelDisplayEPaper* epaper = static_cast<LowLevelDisplayEPaper*>(display);
|
LowLevelDisplayEPaper* epaper = static_cast<LowLevelDisplayEPaper*>(display);
|
||||||
epaper->full_refresh();
|
epaper->full_refresh();
|
||||||
@@ -677,12 +678,19 @@ int main()
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// In launcher mode - process menu input
|
// 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);
|
bool game_selected = launcher.update(input);
|
||||||
if (game_selected) {
|
if (game_selected) {
|
||||||
printf("Game launched successfully\n");
|
printf("Game launched successfully\n");
|
||||||
game_start_time = 0;
|
game_start_time = 0;
|
||||||
// Force full clear for clean transition to game
|
// 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) {
|
// if (display->get_type() == DISPLAY_TYPE_EPAPER) {
|
||||||
// LowLevelDisplayEPaper* epaper = static_cast<LowLevelDisplayEPaper*>(display);
|
// LowLevelDisplayEPaper* epaper = static_cast<LowLevelDisplayEPaper*>(display);
|
||||||
// epaper->full_refresh();
|
// epaper->full_refresh();
|
||||||
@@ -714,6 +722,23 @@ int main()
|
|||||||
if (time_since_last_frame >= TARGET_FRAME_TIME_MS) {
|
if (time_since_last_frame >= TARGET_FRAME_TIME_MS) {
|
||||||
// Only draw if Core 1 is finished with the buffer
|
// Only draw if Core 1 is finished with the buffer
|
||||||
if (!is_refresh_in_progress()) {
|
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<LowLevelDisplayST7796*>(display);
|
||||||
|
st7796_display->enable_dirty_rect(wants_opt);
|
||||||
|
dirty_rect_opt_state = wants_opt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clear buffer and redraw entire UI with updated state
|
// Clear buffer and redraw entire UI with updated state
|
||||||
memset(bit_buffer, 0, V_WIDTH * V_HEIGHT / 8);
|
memset(bit_buffer, 0, V_WIDTH * V_HEIGHT / 8);
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
#include "low_level_render.h"
|
|
||||||
#include "low_level_gui.h"
|
#include "low_level_gui.h"
|
||||||
|
#include "low_level_render.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
||||||
LowLevelWindow* validate_or_create_window(LowLevelWindow* window, LowLevelRenderer* renderer) {
|
LowLevelWindow *validate_or_create_window(LowLevelWindow *window,
|
||||||
|
LowLevelRenderer *renderer) {
|
||||||
if (window == nullptr) {
|
if (window == nullptr) {
|
||||||
return new LowLevelWindow(0, 0, renderer->get_width(), renderer->get_height(), "Default Window");
|
return new LowLevelWindow(0, 0, renderer->get_width(),
|
||||||
|
renderer->get_height(), "Default Window");
|
||||||
}
|
}
|
||||||
return 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 *LowLevelGUI::draw_new_window(int x, int y, int width,
|
||||||
{
|
int height, const char *title) {
|
||||||
LowLevelWindow *w = new LowLevelWindow(x, y, width, height, title);
|
LowLevelWindow *w = new LowLevelWindow(x, y, width, height, title);
|
||||||
draw_window(w);
|
draw_window(w);
|
||||||
return w;
|
return w;
|
||||||
@@ -22,28 +25,39 @@ LowLevelWindow* LowLevelGUI::draw_new_window(int x, int y, int width, int height
|
|||||||
void LowLevelGUI::draw_window(LowLevelWindow *window) {
|
void LowLevelGUI::draw_window(LowLevelWindow *window) {
|
||||||
// Draw window border
|
// Draw window border
|
||||||
|
|
||||||
if (use_rounded_corners)
|
if (use_rounded_corners) {
|
||||||
{
|
|
||||||
// shadow
|
// shadow
|
||||||
renderer->draw_rounded_rectangle(window->x+3, window->y+3, window->width, window->height, 10, true, true);
|
renderer->draw_rounded_rectangle(window->x + 3, window->y + 3,
|
||||||
renderer->draw_rounded_rectangle(window->x-2, window->y-2, window->width+2, window->height+2, 10, false, true);
|
window->width, window->height, 10, true,
|
||||||
renderer->draw_rounded_rectangle(window->x, window->y, window->width, window->height, 10, true);
|
true);
|
||||||
}
|
renderer->draw_rounded_rectangle(window->x - 2, window->y - 2,
|
||||||
else
|
window->width + 2, window->height + 2, 10,
|
||||||
{
|
false, true);
|
||||||
renderer->draw_filled_rectangle(window->x + 3, window->y + 3, window->width + 2, window->height + 2, true, 2);
|
renderer->draw_rounded_rectangle(window->x, window->y, window->width,
|
||||||
renderer->draw_filled_rectangle(window->x - 2, window->y - 2, window->width + 2, window->height + 2, false, 2);
|
window->height, 10, true);
|
||||||
renderer->draw_rectangle(window->x, window->y, window->width, window->height, true, 2);
|
} 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->draw_line(window->x, window->y + 20, window->x + window->width - 1, window->y + 20, true, 1);
|
renderer->draw_line(window->x, window->y + 20, window->x + window->width - 1,
|
||||||
|
window->y + 20, true, 1);
|
||||||
// draw closing 'X' button
|
// draw closing 'X' button
|
||||||
int close_size = 12;
|
int close_size = 12;
|
||||||
int close_x = window->x + window->width - close_size - 4;
|
int close_x = window->x + window->width - close_size - 4;
|
||||||
int close_y = window->y + 4;
|
int close_y = window->y + 4;
|
||||||
//renderer->draw_rectangle(close_x, close_y, close_size, close_size, true, 1);
|
// renderer->draw_rectangle(close_x, close_y, close_size, close_size, true,
|
||||||
renderer->draw_line(close_x + 3, close_y + 3, close_x + close_size - 4, close_y + close_size - 4, true, 1);
|
// 1);
|
||||||
renderer->draw_line(close_x + close_size - 4, close_y + 3, close_x + 3, close_y + close_size - 4, 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);
|
||||||
|
|
||||||
const Font *original_font = renderer->get_current_font();
|
const Font *original_font = renderer->get_current_font();
|
||||||
renderer->set_font(current_font);
|
renderer->set_font(current_font);
|
||||||
@@ -52,8 +66,8 @@ void LowLevelGUI::draw_window(LowLevelWindow* window){
|
|||||||
renderer->set_font(original_font);
|
renderer->set_font(original_font);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LowLevelGUI::draw_button(LowLevelWindow* window, int x, int y, const char *label, bool pressed, bool rounded)
|
void LowLevelGUI::draw_button(LowLevelWindow *window, int x, int y,
|
||||||
{
|
const char *label, bool pressed, bool rounded) {
|
||||||
window = validate_or_create_window(window, renderer);
|
window = validate_or_create_window(window, renderer);
|
||||||
const Font *original_font = renderer->get_current_font();
|
const Font *original_font = renderer->get_current_font();
|
||||||
bool original_text_color = renderer->get_current_text_color();
|
bool original_text_color = renderer->get_current_text_color();
|
||||||
@@ -61,19 +75,26 @@ void LowLevelGUI::draw_button(LowLevelWindow* window, int x, int y, const char *
|
|||||||
int text_x = window->x + x + 5;
|
int text_x = window->x + x + 5;
|
||||||
int text_y = window->y + y + 5;
|
int text_y = window->y + y + 5;
|
||||||
int height = renderer->get_current_font()->get_char_height() * 2 + 10;
|
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;
|
int width = renderer->get_string_width_scaled(label, 2) + 30;
|
||||||
|
|
||||||
if (pressed)
|
if (pressed) {
|
||||||
{
|
renderer->draw_rounded_rectangle(window->x + x - 1, window->y + y - 1,
|
||||||
renderer->draw_rounded_rectangle(window->x + x - 1, window->y + y -1, width + 2, height+ 2, rounded ? 5 : 0, false, true);
|
width + 2, height + 2, rounded ? 5 : 0,
|
||||||
renderer->draw_rounded_rectangle(window->x + x, window->y + y, width, height, rounded ? 5 : 0, true, true);
|
false, true);
|
||||||
renderer->draw_rounded_rectangle(window->x + x + 2, window->y + y + 2, width - 4, height - 4, rounded ? 5 : 0, false, false);
|
renderer->draw_rounded_rectangle(window->x + x, window->y + y, width,
|
||||||
}
|
height, rounded ? 5 : 0, true, true);
|
||||||
else
|
renderer->draw_rounded_rectangle(window->x + x + 2, window->y + y + 2,
|
||||||
{
|
width - 4, height - 4, rounded ? 5 : 0,
|
||||||
renderer->draw_rounded_rectangle(window->x + x - 1, window->y + y -1, width + 2, height+ 2, rounded ? 5 : 0, false, true);
|
false, false);
|
||||||
renderer->draw_rounded_rectangle(window->x + x, window->y + y, width, height, rounded ? 5 : 0, false, true);
|
} else {
|
||||||
renderer->draw_rounded_rectangle(window->x + x + 2, window->y + y + 2, width - 4, height - 4, rounded ? 5 : 0, true, false);
|
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->set_text_color(!pressed);
|
||||||
@@ -83,7 +104,8 @@ void LowLevelGUI::draw_button(LowLevelWindow* window, int x, int y, const char *
|
|||||||
renderer->set_text_color(original_text_color);
|
renderer->set_text_color(original_text_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LowLevelGUI::draw_checkbox(LowLevelWindow* window, int x, int y, const char *label, bool checked) {
|
void LowLevelGUI::draw_checkbox(LowLevelWindow *window, int x, int y,
|
||||||
|
const char *label, bool checked) {
|
||||||
window = validate_or_create_window(window, renderer);
|
window = validate_or_create_window(window, renderer);
|
||||||
const Font *original_font = renderer->get_current_font();
|
const Font *original_font = renderer->get_current_font();
|
||||||
renderer->set_font(current_font);
|
renderer->set_font(current_font);
|
||||||
@@ -92,11 +114,12 @@ void LowLevelGUI::draw_checkbox(LowLevelWindow* window, int x, int y, const char
|
|||||||
int box_y = window->y + y;
|
int box_y = window->y + y;
|
||||||
// Draw checkbox square
|
// Draw checkbox square
|
||||||
renderer->draw_rectangle(box_x, box_y, box_size, box_size, true, 1);
|
renderer->draw_rectangle(box_x, box_y, box_size, box_size, true, 1);
|
||||||
if (checked)
|
if (checked) {
|
||||||
{
|
|
||||||
// Draw check mark
|
// 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 + 2, box_y + box_size / 2, box_x + box_size / 2,
|
||||||
renderer->draw_line(box_x + box_size / 2, box_y + box_size - 3, box_x + box_size - 2, box_y + 2, true, 1);
|
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
|
// Draw label
|
||||||
renderer->set_text_color(true);
|
renderer->set_text_color(true);
|
||||||
@@ -104,7 +127,8 @@ void LowLevelGUI::draw_checkbox(LowLevelWindow* window, int x, int y, const char
|
|||||||
renderer->set_font(original_font);
|
renderer->set_font(original_font);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LowLevelGUI::draw_radio_button(LowLevelWindow* window, int x, int y, const char *label, bool selected) {
|
void LowLevelGUI::draw_radio_button(LowLevelWindow *window, int x, int y,
|
||||||
|
const char *label, bool selected) {
|
||||||
window = validate_or_create_window(window, renderer);
|
window = validate_or_create_window(window, renderer);
|
||||||
const Font *original_font = renderer->get_current_font();
|
const Font *original_font = renderer->get_current_font();
|
||||||
renderer->set_font(current_font);
|
renderer->set_font(current_font);
|
||||||
@@ -113,63 +137,64 @@ void LowLevelGUI::draw_radio_button(LowLevelWindow* window, int x, int y, const
|
|||||||
int center_y = window->y + y + radius;
|
int center_y = window->y + y + radius;
|
||||||
// Draw outer circle
|
// Draw outer circle
|
||||||
renderer->draw_circle(center_x, center_y, radius, true);
|
renderer->draw_circle(center_x, center_y, radius, true);
|
||||||
if (selected)
|
if (selected) {
|
||||||
{
|
|
||||||
// Draw inner filled circle
|
// Draw inner filled circle
|
||||||
renderer->draw_filled_circle(center_x, center_y, radius - 4, true);
|
renderer->draw_filled_circle(center_x, center_y, radius - 4, true);
|
||||||
}
|
}
|
||||||
// Draw label
|
// Draw label
|
||||||
renderer->set_text_color(true);
|
renderer->set_text_color(true);
|
||||||
renderer->draw_string_scaled(center_x + radius + 5, center_y - radius / 2, label, 2);
|
renderer->draw_string_scaled(center_x + radius + 5, center_y - radius / 2,
|
||||||
|
label, 2);
|
||||||
renderer->set_font(original_font);
|
renderer->set_font(original_font);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LowLevelGUI::draw_slider(LowLevelWindow* window, int x, int y, int width, int height, int position, char* label) {
|
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);
|
window = validate_or_create_window(window, renderer);
|
||||||
int slider_x = window->x + x;
|
int slider_x = window->x + x;
|
||||||
int slider_y = window->y + y + (label != nullptr ? 20 : 0);
|
int slider_y = window->y + y + (label != nullptr ? 20 : 0);
|
||||||
position = std::max(0, std::min(100, position));
|
position = std::max(0, std::min(100, position));
|
||||||
// Draw slider track
|
// Draw slider track
|
||||||
renderer->draw_filled_rectangle(slider_x, slider_y + height / 2 - 2, width, 4, true, 1);
|
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]
|
// Draw slider handle, considering position to be within [0, 100]
|
||||||
int handle_x = slider_x + (position * width / 100);
|
int handle_x = slider_x + (position * width / 100);
|
||||||
renderer->draw_filled_rectangle(handle_x - 5, slider_y, 10, height, true, 1);
|
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);
|
renderer->draw_rectangle(handle_x - 6, slider_y - 1, 12, height + 2, true, 1);
|
||||||
|
|
||||||
|
|
||||||
const Font *original_font = renderer->get_current_font();
|
const Font *original_font = renderer->get_current_font();
|
||||||
renderer->set_font(current_font);
|
renderer->set_font(current_font);
|
||||||
// draw current position value label on top of the slider
|
// draw current position value label on top of the slider
|
||||||
char pos_label[10];
|
char pos_label[10];
|
||||||
snprintf(pos_label, sizeof(pos_label), "%d", position);
|
snprintf(pos_label, sizeof(pos_label), "%d", position);
|
||||||
renderer->draw_string_scaled(slider_x + width + 10, slider_y + (height / 2) - 5, pos_label, 1);
|
renderer->draw_string_scaled(slider_x + width + 10,
|
||||||
|
slider_y + (height / 2) - 5, pos_label, 1);
|
||||||
// Draw label if provided
|
// Draw label if provided
|
||||||
if (label != nullptr) {
|
if (label != nullptr) {
|
||||||
renderer->draw_string_scaled(slider_x, window->y + y, label, 2);
|
renderer->draw_string_scaled(slider_x, window->y + y, label, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->set_font(original_font);
|
renderer->set_font(original_font);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LowLevelGUI::draw_calendar(LowLevelWindow* window, int x, int y, int month, int year) {
|
void LowLevelGUI::draw_calendar(LowLevelWindow *window, int x, int y, int month,
|
||||||
|
int year) {
|
||||||
window = validate_or_create_window(window, renderer);
|
window = validate_or_create_window(window, renderer);
|
||||||
const Font *original_font = renderer->get_current_font();
|
const Font *original_font = renderer->get_current_font();
|
||||||
renderer->set_font(current_font);
|
renderer->set_font(current_font);
|
||||||
|
|
||||||
// 1. Draw Month and Year Header
|
// 1. Draw Month and Year Header
|
||||||
char title[32];
|
char title[32];
|
||||||
const char* month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
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);
|
snprintf(title, sizeof(title), "%s %04d", month_names[month - 1], year);
|
||||||
renderer->draw_string_scaled(window->x + x, window->y + y, title, 1);
|
renderer->draw_string_scaled(window->x + x, window->y + y, title, 1);
|
||||||
|
|
||||||
// 2. Draw Days of the Week labels
|
// 2. Draw Days of the Week labels
|
||||||
const char *days[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"};
|
const char *days[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"};
|
||||||
for (int i = 0; i < 7; i++) {
|
for (int i = 0; i < 7; i++) {
|
||||||
renderer->draw_string_scaled(window->x + x + (i * 20), window->y + y + 15, days[i], 1);
|
renderer->draw_string_scaled(window->x + x + (i * 20), window->y + y + 15,
|
||||||
|
days[i], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Calculate Month Metadata
|
// 3. Calculate Month Metadata
|
||||||
@@ -212,17 +237,20 @@ void LowLevelGUI::draw_calendar(LowLevelWindow* window, int x, int y, int month,
|
|||||||
renderer->set_font(original_font);
|
renderer->set_font(original_font);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LowLevelGUI::draw_textbox(LowLevelWindow* window, int x, int y, int width, int height, const char* content, bool focused) {
|
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);
|
window = validate_or_create_window(window, renderer);
|
||||||
// Draw textbox border
|
// Draw textbox border
|
||||||
int box_x = window->x + x;
|
int box_x = window->x + x;
|
||||||
int box_y = window->y + y;
|
int box_y = window->y + y;
|
||||||
if (focused) {
|
if (focused) {
|
||||||
renderer->draw_filled_rectangle(box_x, box_y, width, height, true, 1);
|
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);
|
renderer->draw_rectangle(box_x - 1, box_y - 1, width + 2, height + 2, false,
|
||||||
|
1);
|
||||||
} else {
|
} else {
|
||||||
renderer->draw_filled_rectangle(box_x, box_y, width, height, false, 1);
|
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);
|
renderer->draw_rectangle(box_x - 1, box_y - 1, width + 2, height + 2, true,
|
||||||
|
1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw content text inside the textbox
|
// Draw content text inside the textbox
|
||||||
@@ -237,16 +265,19 @@ void LowLevelGUI::draw_textbox(LowLevelWindow* window, int x, int y, int width,
|
|||||||
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) {
|
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);
|
window = validate_or_create_window(window, renderer);
|
||||||
int tab_x = window->x + x;
|
int tab_x = window->x + x;
|
||||||
int tab_y = window->y + y;
|
int tab_y = window->y + y;
|
||||||
if (selected) {
|
if (selected) {
|
||||||
renderer->draw_filled_rectangle(tab_x, tab_y, width, height, true, 1);
|
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);
|
renderer->draw_rectangle(tab_x - 1, tab_y - 1, width + 2, height + 2, false,
|
||||||
|
1);
|
||||||
} else {
|
} else {
|
||||||
renderer->draw_filled_rectangle(tab_x, tab_y, width, height, false, 1);
|
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);
|
renderer->draw_rectangle(tab_x - 1, tab_y - 1, width + 2, height + 2, true,
|
||||||
|
1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Font *original_font = renderer->get_current_font();
|
const Font *original_font = renderer->get_current_font();
|
||||||
@@ -254,15 +285,19 @@ void LowLevelGUI::draw_tab(LowLevelWindow* window, int x, int y, int width, int
|
|||||||
renderer->set_text_color(true);
|
renderer->set_text_color(true);
|
||||||
|
|
||||||
// Center the label within the tab
|
// Center the label within the tab
|
||||||
int text_width = int(renderer->draw_string_scaled(0, 0, label, 1) * 0.75);
|
int text_width = renderer->get_string_width_scaled(label, 1);
|
||||||
int text_x = tab_x + (width - text_width) / 2;
|
int text_x = tab_x + (width - text_width) / 2;
|
||||||
int text_y = tab_y + (height - renderer->get_current_font()->get_char_height()) / 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->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) {
|
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);
|
window = validate_or_create_window(window, renderer);
|
||||||
int base_x = window->x + x;
|
int base_x = window->x + x;
|
||||||
int base_y = window->y + y;
|
int base_y = window->y + y;
|
||||||
@@ -276,7 +311,8 @@ void LowLevelGUI::draw_status_bar(LowLevelWindow* window, int x, int y, int widt
|
|||||||
renderer->draw_string_scaled(base_x, base_y + 15, sublabel, 1);
|
renderer->draw_string_scaled(base_x, base_y + 15, sublabel, 1);
|
||||||
|
|
||||||
int val_width = strlen(value_text) * 8; // Approximation
|
int val_width = strlen(value_text) * 8; // Approximation
|
||||||
renderer->draw_string_scaled(base_x + width - val_width, base_y + 15, value_text, 1);
|
renderer->draw_string_scaled(base_x + width - val_width, base_y + 15,
|
||||||
|
value_text, 1);
|
||||||
|
|
||||||
// Draw Bar Container (Rounded)
|
// Draw Bar Container (Rounded)
|
||||||
int bar_y = base_y + 30;
|
int bar_y = base_y + 30;
|
||||||
@@ -286,20 +322,24 @@ void LowLevelGUI::draw_status_bar(LowLevelWindow* window, int x, int y, int widt
|
|||||||
// Draw Progress Fill
|
// Draw Progress Fill
|
||||||
int fill_width = (percentage * width) / 100;
|
int fill_width = (percentage * width) / 100;
|
||||||
if (fill_width > 4) {
|
if (fill_width > 4) {
|
||||||
renderer->draw_rounded_rectangle(base_x + 2, bar_y + 2, fill_width - 4, bar_height - 4, 4, true, true);
|
renderer->draw_rounded_rectangle(base_x + 2, bar_y + 2, fill_width - 4,
|
||||||
|
bar_height - 4, 4, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->set_font(original_font);
|
renderer->set_font(original_font);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LowLevelGUI::draw_circular_gauge(LowLevelWindow* window, int x, int y, int width, const char* label, int percentage) {
|
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);
|
window = validate_or_create_window(window, renderer);
|
||||||
int base_x = window->x + x;
|
int base_x = window->x + x;
|
||||||
int base_y = window->y + y;
|
int base_y = window->y + y;
|
||||||
int height = 50;
|
int height = 50;
|
||||||
|
|
||||||
// Draw pill-shaped container
|
// Draw pill-shaped container
|
||||||
renderer->draw_rounded_rectangle(base_x, base_y, width, height, height/2, true);
|
renderer->draw_rounded_rectangle(base_x, base_y, width, height, height / 2,
|
||||||
|
true);
|
||||||
|
|
||||||
const Font *original_font = renderer->get_current_font();
|
const Font *original_font = renderer->get_current_font();
|
||||||
renderer->set_font(current_font);
|
renderer->set_font(current_font);
|
||||||
@@ -326,8 +366,9 @@ void LowLevelGUI::draw_circular_gauge(LowLevelWindow* window, int x, int y, int
|
|||||||
renderer->set_font(original_font);
|
renderer->set_font(original_font);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LowLevelGUI::draw_notification(LowLevelWindow *window, int x, int y,
|
||||||
void LowLevelGUI::draw_notification(LowLevelWindow* window, int x, int y, int width, const char* time, const char* message) {
|
int width, const char *time,
|
||||||
|
const char *message) {
|
||||||
// window = validate_or_create_window(window, renderer);
|
// window = validate_or_create_window(window, renderer);
|
||||||
int base_x = window->x + x;
|
int base_x = window->x + x;
|
||||||
int base_y = window->y + y;
|
int base_y = window->y + y;
|
||||||
@@ -350,7 +391,8 @@ void LowLevelGUI::draw_notification(LowLevelWindow* window, int x, int y, int wi
|
|||||||
renderer->set_font(original_font);
|
renderer->set_font(original_font);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LowLevelGUI::draw_large_clock(LowLevelWindow* window, int x, int y, const char* time_str) {
|
void LowLevelGUI::draw_large_clock(LowLevelWindow *window, int x, int y,
|
||||||
|
const char *time_str) {
|
||||||
window = validate_or_create_window(window, renderer);
|
window = validate_or_create_window(window, renderer);
|
||||||
const Font *original_font = renderer->get_current_font();
|
const Font *original_font = renderer->get_current_font();
|
||||||
renderer->set_font(current_font);
|
renderer->set_font(current_font);
|
||||||
@@ -358,4 +400,3 @@ void LowLevelGUI::draw_large_clock(LowLevelWindow* window, int x, int y, const c
|
|||||||
renderer->draw_string_scaled(window->x + x, window->y + y, time_str, 6);
|
renderer->draw_string_scaled(window->x + x, window->y + y, time_str, 6);
|
||||||
renderer->set_font(original_font);
|
renderer->set_font(original_font);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -671,3 +671,57 @@ int LowLevelRenderer::draw_string_scaled(int x, int y, const char* text, int sca
|
|||||||
}
|
}
|
||||||
return current_x;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
// class that handles low-level rendering operations, such as drawing pixels and shapes to the display.
|
// class that handles low-level rendering operations, such as drawing pixels and
|
||||||
// This class is framework-agnostic and focuses solely on manipulating a 1-bit per pixel buffer.
|
// shapes to the display. This class is framework-agnostic and focuses solely on
|
||||||
// Constructor Args:
|
// manipulating a 1-bit per pixel buffer. Constructor Args: uint8_t* buffer:
|
||||||
// uint8_t* buffer: Pointer to the bit buffer
|
// Pointer to the bit buffer int width: Display width in pixels int height:
|
||||||
// int width: Display width in pixels
|
// Display height in pixels
|
||||||
// int height: Display height in pixels
|
|
||||||
|
|
||||||
#ifndef LOW_LEVEL_RENDER_H
|
#ifndef LOW_LEVEL_RENDER_H
|
||||||
#define LOW_LEVEL_RENDER_H
|
#define LOW_LEVEL_RENDER_H
|
||||||
@@ -21,7 +20,8 @@ private:
|
|||||||
int char_height;
|
int char_height;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Font(const unsigned char* font_data, int num_chars, int bytes_per_char, int 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),
|
: data(font_data), num_chars(num_chars), bytes_per_char(bytes_per_char),
|
||||||
char_height(char_height) {}
|
char_height(char_height) {}
|
||||||
|
|
||||||
@@ -32,7 +32,8 @@ public:
|
|||||||
|
|
||||||
// Get a specific character's data
|
// Get a specific character's data
|
||||||
const unsigned char *get_char_data(int char_index) const {
|
const unsigned char *get_char_data(int char_index) const {
|
||||||
if (char_index < 0 || char_index >= num_chars) return nullptr;
|
if (char_index < 0 || char_index >= num_chars)
|
||||||
|
return nullptr;
|
||||||
return data + (char_index * bytes_per_char);
|
return data + (char_index * bytes_per_char);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -100,9 +101,12 @@ private:
|
|||||||
bool clipping_enabled;
|
bool clipping_enabled;
|
||||||
int clip_x, clip_y, clip_width, clip_height;
|
int clip_x, clip_y, clip_width, clip_height;
|
||||||
bool text_color;
|
bool text_color;
|
||||||
void draw_corner_arc(int center_x, int center_y, int radius, int quadrant, bool on);
|
void draw_corner_arc(int center_x, int center_y, int radius, int quadrant,
|
||||||
void fill_bottom_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on);
|
bool on);
|
||||||
void fill_top_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, 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);
|
bool is_point_in_clip_rect(int x, int y);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -119,19 +123,28 @@ public:
|
|||||||
// --- 1-BIT DRAWING PRIMITIVES ---
|
// --- 1-BIT DRAWING PRIMITIVES ---
|
||||||
void set_pixel(int x, int y, bool on);
|
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_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_rectangle(int x, int y, int width, int height, bool on,
|
||||||
void draw_filled_rectangle(int x, int y, int width, int height, bool on, int line_width);
|
int line_width);
|
||||||
void draw_rounded_rectangle(int x, int y, int width, int height, int radius, bool on, bool filled = false);
|
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_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_filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3,
|
||||||
void draw_ellipse(int center_x, int center_y, int radius_x, int radius_y, bool on);
|
bool on);
|
||||||
void draw_filled_ellipse(int center_x, int center_y, int radius_x, int radius_y, 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<std::pair<int, int>> &points, bool on);
|
void draw_polygon(const std::vector<std::pair<int, int>> &points, bool on);
|
||||||
void draw_filled_polygon(const std::vector<std::pair<int, int>>& points, bool on);
|
void draw_filled_polygon(const std::vector<std::pair<int, int>> &points,
|
||||||
void draw_arc(int center_x, int center_y, int radius, int start_angle, int end_angle, bool on);
|
bool on);
|
||||||
|
void draw_arc(int center_x, int center_y, int radius, int start_angle,
|
||||||
|
int end_angle, bool on);
|
||||||
|
|
||||||
// Bitmap drawing
|
// Bitmap drawing
|
||||||
void draw_bitmap(const unsigned char* bitmap, int x, int y, int width, int height, bool invert = false);
|
void draw_bitmap(const unsigned char *bitmap, int x, int y, int width,
|
||||||
|
int height, bool invert = false);
|
||||||
|
|
||||||
// Clipping functions
|
// Clipping functions
|
||||||
void set_clip_rect(int x, int y, int width, int height);
|
void set_clip_rect(int x, int y, int width, int height);
|
||||||
@@ -147,7 +160,12 @@ public:
|
|||||||
int draw_char_vcol(int x, int y, char c);
|
int draw_char_vcol(int x, int y, char c);
|
||||||
void draw_string(int x, int y, const std::string &text, int spacing = 1);
|
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_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);
|
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
|
#endif // LOW_LEVEL_RENDER_H
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "[Emulator] Cleaning old build..."
|
echo "[Emulator] Generating build files..."
|
||||||
make clean
|
cmake .
|
||||||
|
|
||||||
|
|
||||||
echo "[Emulator] Building..."
|
echo "[Emulator] Building..."
|
||||||
make
|
make
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
// Copy of game_launcher.cpp for emulator build
|
// Copy of game_launcher.cpp for emulator build
|
||||||
#include "game_launcher.h"
|
#include "game_launcher.h"
|
||||||
#include "input_manager.h"
|
|
||||||
#include "../display/low_level_render.h"
|
|
||||||
#include "../display/low_level_gui.h"
|
#include "../display/low_level_gui.h"
|
||||||
|
#include "../display/low_level_render.h"
|
||||||
|
#include "input_manager.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
extern Font font_5x5_obj;
|
extern Font font_5x5_obj;
|
||||||
GameLauncher::GameLauncher(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager)
|
GameLauncher::GameLauncher(uint16_t width, uint16_t height,
|
||||||
: width(width), height(height), renderer(renderer), gui(gui), input_manager(input_manager),
|
LowLevelRenderer *renderer, LowLevelGUI *gui,
|
||||||
selected_index(0), selected_game(nullptr), current_page(0) {}
|
InputManager *input_manager)
|
||||||
void GameLauncher::register_game(const char* name, const char* description,
|
: width(width), height(height), renderer(renderer), gui(gui),
|
||||||
std::function<Game*(uint16_t, uint16_t, LowLevelRenderer*, LowLevelGUI*, InputManager*)> factory) {
|
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<Game *(uint16_t, uint16_t, LowLevelRenderer *, LowLevelGUI *,
|
||||||
|
InputManager *)>
|
||||||
|
factory) {
|
||||||
GameEntry entry;
|
GameEntry entry;
|
||||||
entry.name = name;
|
entry.name = name;
|
||||||
entry.description = description;
|
entry.description = description;
|
||||||
@@ -18,12 +24,14 @@ void GameLauncher::register_game(const char* name, const char* description,
|
|||||||
printf("Registered game: %s - %s\n", name, description);
|
printf("Registered game: %s - %s\n", name, description);
|
||||||
}
|
}
|
||||||
void GameLauncher::draw() {
|
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);
|
renderer->set_font(&font_5x5_obj);
|
||||||
|
|
||||||
int total_pages = get_total_pages();
|
int total_pages = get_total_pages();
|
||||||
char title[64];
|
char title[64];
|
||||||
snprintf(title, sizeof(title), "Select a Game: (Page %d/%d)", current_page + 1, total_pages);
|
snprintf(title, sizeof(title), "Select a Game: (Page %d/%d)",
|
||||||
|
current_page + 1, total_pages);
|
||||||
renderer->draw_string_scaled(30, 40, title, 2);
|
renderer->draw_string_scaled(30, 40, title, 2);
|
||||||
|
|
||||||
int page_start = get_page_start_index();
|
int page_start = get_page_start_index();
|
||||||
@@ -41,13 +49,16 @@ void GameLauncher::draw() {
|
|||||||
|
|
||||||
if (total_pages > 1) {
|
if (total_pages > 1) {
|
||||||
int button_y = height - 65;
|
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->set_font(&font_5x5_obj);
|
||||||
renderer->draw_string_scaled(30, height - 25, "Touch buttons or KEY0/KEY1", 1);
|
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 {
|
} else {
|
||||||
renderer->set_font(&font_5x5_obj);
|
renderer->set_font(&font_5x5_obj);
|
||||||
renderer->draw_string_scaled(30, height - 35, "KEY0: Navigate | KEY1: Select | Touch to play", 1);
|
renderer->draw_string_scaled(
|
||||||
|
30, height - 35, "KEY0: Navigate | KEY1: Select | Touch to play", 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool GameLauncher::update(const InputEvent &event) {
|
bool GameLauncher::update(const InputEvent &event) {
|
||||||
@@ -85,7 +96,8 @@ bool GameLauncher::update(const InputEvent& event) {
|
|||||||
int item_index = i - page_start;
|
int item_index = i - page_start;
|
||||||
int y = MENU_Y_START + (item_index * MENU_ITEM_HEIGHT);
|
int y = MENU_Y_START + (item_index * MENU_ITEM_HEIGHT);
|
||||||
if (event.y >= y - 5 && event.y < y + MENU_ITEM_HEIGHT - 5) {
|
if (event.y >= y - 5 && event.y < y + MENU_ITEM_HEIGHT - 5) {
|
||||||
selected_game = games[i].factory(width, height, renderer, gui, input_manager);
|
selected_game =
|
||||||
|
games[i].factory(width, height, renderer, gui, input_manager);
|
||||||
if (selected_game) {
|
if (selected_game) {
|
||||||
selected_game->init();
|
selected_game->init();
|
||||||
return true;
|
return true;
|
||||||
@@ -127,7 +139,8 @@ bool GameLauncher::update(const InputEvent& event) {
|
|||||||
}
|
}
|
||||||
case INPUT_BUTTON_1: {
|
case INPUT_BUTTON_1: {
|
||||||
if (selected_index >= 0 && selected_index < (int)games.size()) {
|
if (selected_index >= 0 && selected_index < (int)games.size()) {
|
||||||
selected_game = games[selected_index].factory(width, height, renderer, gui, input_manager);
|
selected_game = games[selected_index].factory(width, height, renderer,
|
||||||
|
gui, input_manager);
|
||||||
if (selected_game) {
|
if (selected_game) {
|
||||||
selected_game->init();
|
selected_game->init();
|
||||||
return true;
|
return true;
|
||||||
@@ -151,7 +164,8 @@ void GameLauncher::reset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int GameLauncher::get_total_pages() const {
|
int GameLauncher::get_total_pages() const {
|
||||||
if (games.empty()) return 1;
|
if (games.empty())
|
||||||
|
return 1;
|
||||||
return (games.size() + GAMES_PER_PAGE - 1) / GAMES_PER_PAGE;
|
return (games.size() + GAMES_PER_PAGE - 1) / GAMES_PER_PAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ bool SerialUploader::complete_launch() {
|
|||||||
return launched;
|
return launched;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SerialUploader::process() {
|
bool SerialUploader::process(bool spi_busy) {
|
||||||
if (state == IDLE) {
|
if (state == IDLE) {
|
||||||
// Check for "UPLOAD" command
|
// Check for "UPLOAD" command
|
||||||
int c = getchar_timeout_us(0);
|
int c = getchar_timeout_us(0);
|
||||||
@@ -311,6 +311,9 @@ bool SerialUploader::process() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state == WRITING_FILE) {
|
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()) {
|
if (write_file_to_sd()) {
|
||||||
state = LAUNCHING_GAME;
|
state = LAUNCHING_GAME;
|
||||||
// Prepare for launch by scanning games, but don't actually launch yet
|
// Prepare for launch by scanning games, but don't actually launch yet
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ public:
|
|||||||
|
|
||||||
// Process incoming serial data (call this frequently in main loop)
|
// Process incoming serial data (call this frequently in main loop)
|
||||||
// Returns true if a game was launched
|
// 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)
|
// Check if uploader wants to launch a game (after upload complete)
|
||||||
bool wants_to_launch_game() const { return state == LAUNCHING_GAME; }
|
bool wants_to_launch_game() const { return state == LAUNCHING_GAME; }
|
||||||
|
|||||||
Reference in New Issue
Block a user