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
|
||||
emulator/build/
|
||||
.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;
|
||||
|
||||
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<LowLevelDisplayEPaper*>(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<LowLevelDisplayEPaper*>(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<LowLevelDisplayEPaper*>(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<LowLevelDisplayST7796*>(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);
|
||||
|
||||
|
||||
@@ -1,79 +1,100 @@
|
||||
#include "low_level_render.h"
|
||||
#include "low_level_gui.h"
|
||||
#include "low_level_render.h"
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
LowLevelWindow* validate_or_create_window(LowLevelWindow* window, LowLevelRenderer* renderer) {
|
||||
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 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);
|
||||
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){
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
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->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);
|
||||
|
||||
const Font* original_font = renderer->get_current_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)
|
||||
{
|
||||
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();
|
||||
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;
|
||||
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);
|
||||
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);
|
||||
@@ -83,20 +104,22 @@ void LowLevelGUI::draw_button(LowLevelWindow* window, int x, int y, const char *
|
||||
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);
|
||||
const Font* original_font = renderer->get_current_font();
|
||||
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_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)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
@@ -104,72 +127,74 @@ void LowLevelGUI::draw_checkbox(LowLevelWindow* window, int x, int y, const char
|
||||
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);
|
||||
const Font* original_font = renderer->get_current_font();
|
||||
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)
|
||||
{
|
||||
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->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) {
|
||||
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);
|
||||
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();
|
||||
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);
|
||||
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) {
|
||||
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();
|
||||
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"};
|
||||
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"};
|
||||
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);
|
||||
renderer->draw_string_scaled(window->x + x + (i * 20), window->y + y + 15,
|
||||
days[i], 1);
|
||||
}
|
||||
|
||||
// 3. Calculate Month Metadata
|
||||
@@ -212,21 +237,24 @@ void LowLevelGUI::draw_calendar(LowLevelWindow* window, int x, int y, int month,
|
||||
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);
|
||||
// Draw textbox border
|
||||
int box_x = window->x + x;
|
||||
int box_y = window->y + y;
|
||||
if(focused) {
|
||||
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);
|
||||
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);
|
||||
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();
|
||||
const Font *original_font = renderer->get_current_font();
|
||||
renderer->set_font(current_font);
|
||||
renderer->set_text_color(true);
|
||||
|
||||
@@ -237,37 +265,44 @@ void LowLevelGUI::draw_textbox(LowLevelWindow* window, int x, int y, int width,
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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();
|
||||
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_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;
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
int base_x = window->x + x;
|
||||
int base_y = window->y + y;
|
||||
|
||||
const Font* original_font = renderer->get_current_font();
|
||||
const Font *original_font = renderer->get_current_font();
|
||||
renderer->set_font(current_font);
|
||||
|
||||
// Draw Main Label (e.g., "PANELS")
|
||||
@@ -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);
|
||||
|
||||
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)
|
||||
int bar_y = base_y + 30;
|
||||
@@ -286,22 +322,26 @@ void LowLevelGUI::draw_status_bar(LowLevelWindow* window, int x, int y, int widt
|
||||
// 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->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) {
|
||||
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);
|
||||
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);
|
||||
|
||||
// Draw Label
|
||||
@@ -326,8 +366,9 @@ void LowLevelGUI::draw_circular_gauge(LowLevelWindow* window, int x, int y, int
|
||||
renderer->set_font(original_font);
|
||||
}
|
||||
|
||||
|
||||
void LowLevelGUI::draw_notification(LowLevelWindow* window, int x, int y, int width, const char* time, const char* message) {
|
||||
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;
|
||||
@@ -335,7 +376,7 @@ void LowLevelGUI::draw_notification(LowLevelWindow* window, int x, int y, int wi
|
||||
// Draw dark background
|
||||
renderer->draw_rounded_rectangle(base_x, base_y, width, 100, 15, true, true);
|
||||
|
||||
const Font* original_font = renderer->get_current_font();
|
||||
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
|
||||
|
||||
@@ -350,12 +391,12 @@ void LowLevelGUI::draw_notification(LowLevelWindow* window, int x, int y, int wi
|
||||
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);
|
||||
const Font* original_font = renderer->get_current_font();
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,24 +14,26 @@
|
||||
// Font class that holds font data and dimensions
|
||||
class Font {
|
||||
private:
|
||||
const unsigned char* data;
|
||||
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)
|
||||
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; }
|
||||
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;
|
||||
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);
|
||||
}
|
||||
};
|
||||
@@ -93,25 +94,28 @@ extern Font font_zxpix_obj;
|
||||
|
||||
class LowLevelRenderer {
|
||||
private:
|
||||
uint8_t* bit_buffer;
|
||||
uint8_t *bit_buffer;
|
||||
int V_WIDTH;
|
||||
int V_HEIGHT;
|
||||
const Font* current_font;
|
||||
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);
|
||||
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_font(const Font *font);
|
||||
void set_text_color(bool color);
|
||||
const Font* get_current_font() const { return current_font; }
|
||||
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; }
|
||||
@@ -119,19 +123,28 @@ public:
|
||||
// --- 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_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<std::pair<int, int>>& points, bool on);
|
||||
void draw_filled_polygon(const std::vector<std::pair<int, int>>& points, bool on);
|
||||
void draw_arc(int center_x, int center_y, int radius, int start_angle, int end_angle, 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<std::pair<int, int>> &points, bool on);
|
||||
void draw_filled_polygon(const std::vector<std::pair<int, int>> &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);
|
||||
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);
|
||||
@@ -147,7 +160,12 @@ public:
|
||||
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);
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
// 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 <stdio.h>
|
||||
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<Game*(uint16_t, uint16_t, LowLevelRenderer*, LowLevelGUI*, InputManager*)> factory) {
|
||||
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<Game *(uint16_t, uint16_t, LowLevelRenderer *, LowLevelGUI *,
|
||||
InputManager *)>
|
||||
factory) {
|
||||
GameEntry entry;
|
||||
entry.name = name;
|
||||
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);
|
||||
}
|
||||
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);
|
||||
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();
|
||||
@@ -41,16 +49,19 @@ void GameLauncher::draw() {
|
||||
|
||||
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);
|
||||
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);
|
||||
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) {
|
||||
bool needs_refresh = false;
|
||||
int total_pages = get_total_pages();
|
||||
|
||||
@@ -85,7 +96,8 @@ bool GameLauncher::update(const InputEvent& event) {
|
||||
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);
|
||||
selected_game =
|
||||
games[i].factory(width, height, renderer, gui, input_manager);
|
||||
if (selected_game) {
|
||||
selected_game->init();
|
||||
return true;
|
||||
@@ -127,7 +139,8 @@ bool GameLauncher::update(const InputEvent& event) {
|
||||
}
|
||||
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);
|
||||
selected_game = games[selected_index].factory(width, height, renderer,
|
||||
gui, input_manager);
|
||||
if (selected_game) {
|
||||
selected_game->init();
|
||||
return true;
|
||||
@@ -140,7 +153,7 @@ bool GameLauncher::update(const InputEvent& event) {
|
||||
}
|
||||
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;
|
||||
@@ -151,7 +164,8 @@ void GameLauncher::reset() {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user