Fix emulator build script, UI rendering, and clean up repo

This commit is contained in:
Adolfo Reyna
2026-02-17 21:49:49 -05:00
parent 6d29e99394
commit b0ca1f1a55
9 changed files with 693 additions and 526 deletions

View File

@@ -1,361 +1,402 @@
#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) {
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);
}

View File

@@ -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;
}

View File

@@ -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<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);
// 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<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);
// 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