403 lines
16 KiB
C++
403 lines
16 KiB
C++
#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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
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 = 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);
|
|
}
|
|
|
|
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_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);
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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];
|
|
|
|
// 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);
|
|
|
|
// 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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// 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);
|
|
|
|
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);
|
|
}
|
|
|
|
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 = 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);
|
|
}
|
|
|
|
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);
|
|
|
|
// 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_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);
|
|
}
|
|
|
|
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);
|
|
}
|