abstracting display, touch and sd

This commit is contained in:
Adolfo Reyna
2026-01-28 20:12:41 -05:00
parent 57426c6e7d
commit adfbef7228
396 changed files with 101836 additions and 272 deletions

View File

@@ -0,0 +1,42 @@
#ifndef LOW_LEVEL_DISPLAY_H
#define LOW_LEVEL_DISPLAY_H
#include <stdint.h>
#include <stdbool.h>
// Display type enumeration
typedef enum {
DISPLAY_TYPE_ST7796,
DISPLAY_TYPE_ST7789,
DISPLAY_TYPE_EPAPER
} DisplayType;
// Abstract display interface
class LowLevelDisplay {
public:
virtual ~LowLevelDisplay() {}
// Core display operations - all work with 1-bit monochrome buffers
virtual bool init() = 0;
virtual void clear(bool white = true) = 0; // Clear to white or black
virtual void draw_pixel(int x, int y, bool white) = 0;
virtual void draw_buffer(const uint8_t* bit_buffer) = 0; // 1-bit buffer (width*height/8 bytes)
virtual void refresh() = 0; // Update display (converts 1-bit to native format)
// Display properties
virtual int get_width() const = 0;
virtual int get_height() const = 0;
virtual DisplayType get_type() const = 0;
virtual bool is_color() const = 0;
// Optional: Backlight control (if supported)
virtual void set_backlight(bool on) { (void)on; }
// Optional: Orientation control (not commonly needed for bitmap displays)
virtual void set_rotation(uint8_t rotation) { (void)rotation; }
// Factory method - creates display based on type, using board_config.h for pins
static LowLevelDisplay* create(DisplayType type, int width, int height);
};
#endif // LOW_LEVEL_DISPLAY_H

View File

@@ -0,0 +1,97 @@
#include "low_level_display_epaper.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Note: This is a placeholder implementation
// You'll need to add the actual e-paper driver code to lib/epaper/
LowLevelDisplayEPaper::LowLevelDisplayEPaper(const epaper_config* cfg, int w, int h)
: config(cfg), width(w), height(h), initialized(false), framebuffer(nullptr), dirty(false) {
// Allocate framebuffer (1 bit per pixel for monochrome e-paper)
int buffer_size = (width * height + 7) / 8; // Round up to nearest byte
framebuffer = (uint8_t*)malloc(buffer_size);
if (framebuffer) {
memset(framebuffer, 0xFF, buffer_size); // White
}
}
LowLevelDisplayEPaper::~LowLevelDisplayEPaper() {
if (framebuffer) {
free(framebuffer);
}
}
bool LowLevelDisplayEPaper::init() {
if (initialized) {
return true;
}
if (!framebuffer) {
printf("Failed to allocate framebuffer for e-paper display\n");
return false;
}
// TODO: Implement e-paper initialization
// epaper_init(config, width, height);
printf("E-paper display initialized: %dx%d (stub)\n", width, height);
initialized = true;
return false; // Return false until actual driver is implemented
}
void LowLevelDisplayEPaper::clear(bool white) {
if (!framebuffer) return;
int buffer_size = (width * height + 7) / 8;
memset(framebuffer, white ? 0xFF : 0x00, buffer_size);
dirty = true;
}
void LowLevelDisplayEPaper::draw_pixel(int x, int y, bool white) {
if (!framebuffer || x < 0 || x >= width || y < 0 || y >= height) {
return;
}
int byte_index = (y * width + x) / 8;
int bit_index = 7 - ((y * width + x) % 8);
if (white) {
framebuffer[byte_index] |= (1 << bit_index);
} else {
framebuffer[byte_index] &= ~(1 << bit_index);
}
dirty = true;
}
void LowLevelDisplayEPaper::draw_buffer(const uint8_t* bit_buffer) {
if (!bit_buffer || !framebuffer) return;
// Direct copy of 1-bit buffer
int buffer_size = (width * height + 7) / 8;
memcpy(framebuffer, bit_buffer, buffer_size);
dirty = true;
}
void LowLevelDisplayEPaper::refresh() {
if (!dirty || !framebuffer) {
return;
}
// TODO: Implement actual e-paper refresh
// epaper_update(framebuffer);
printf("E-paper refresh (stub)\n");
dirty = false;
}
void LowLevelDisplayEPaper::clear_display() {
clear(true); // Fill with white
refresh();
}
void LowLevelDisplayEPaper::sleep() {
// TODO: Implement e-paper sleep mode
// epaper_sleep();
}

View File

@@ -0,0 +1,48 @@
#ifndef LOW_LEVEL_DISPLAY_EPAPER_H
#define LOW_LEVEL_DISPLAY_EPAPER_H
#include "low_level_display.h"
// E-paper display configuration structure
struct epaper_config {
void* spi; // SPI instance
int gpio_din; // MOSI pin
int gpio_clk; // Clock pin
int gpio_cs; // Chip select pin
int gpio_dc; // Data/Command pin
int gpio_rst; // Reset pin
int gpio_busy; // Busy pin
};
class LowLevelDisplayEPaper : public LowLevelDisplay {
private:
const epaper_config* config;
int width;
int height;
bool initialized;
uint8_t* framebuffer; // E-paper needs a framebuffer
bool dirty; // Track if display needs refresh
public:
LowLevelDisplayEPaper(const epaper_config* cfg, int w, int h);
~LowLevelDisplayEPaper() override;
// Core display operations - native 1-bit monochrome
bool init() override;
void clear(bool white = true) override;
void draw_pixel(int x, int y, bool white) override;
void draw_buffer(const uint8_t* bit_buffer) override;
void refresh() override; // Actually update the e-paper display
// Display properties
int get_width() const override { return width; }
int get_height() const override { return height; }
DisplayType get_type() const override { return DISPLAY_TYPE_EPAPER; }
bool is_color() const override { return false; } // Most e-paper is monochrome
// E-paper specific
void clear_display();
void sleep(); // Put display in low power mode
};
#endif // LOW_LEVEL_DISPLAY_EPAPER_H

View File

@@ -0,0 +1,62 @@
#include "low_level_display.h"
#include "board_config.h"
#include <stdio.h>
// Include display implementations based on what's available
#include "low_level_display_st7796.h"
#include "low_level_display_st7789.h"
#include "low_level_display_epaper.h"
// Factory method - creates display with board-specific configuration
LowLevelDisplay* LowLevelDisplay::create(DisplayType type, int width, int height) {
switch (type) {
case DISPLAY_TYPE_ST7796: {
// Create ST7796 configuration from board_config.h
static const st7796_config lcd_config = {
.spi = DISPLAY_SPI_PORT,
.gpio_din = DISPLAY_MOSI_PIN,
.gpio_clk = DISPLAY_SCK_PIN,
.gpio_cs = DISPLAY_CS_PIN,
.gpio_dc = DISPLAY_DC_PIN,
.gpio_rst = DISPLAY_RST_PIN,
.gpio_bl = DISPLAY_BL_PIN,
};
printf("Creating ST7796 display (%dx%d)\n", width, height);
return new LowLevelDisplayST7796(&lcd_config, width, height);
}
case DISPLAY_TYPE_ST7789: {
// Create ST7789 configuration from board_config.h
static const st7789_config st7789_cfg = {
.spi = DISPLAY_SPI_PORT,
.gpio_din = DISPLAY_MOSI_PIN,
.gpio_clk = DISPLAY_SCK_PIN,
.gpio_cs = DISPLAY_CS_PIN,
.gpio_dc = DISPLAY_DC_PIN,
.gpio_rst = DISPLAY_RST_PIN,
.gpio_bl = DISPLAY_BL_PIN,
};
printf("Creating ST7789 display (%dx%d) - STUB (driver not implemented)\n", width, height);
return new LowLevelDisplayST7789(&st7789_cfg, width, height);
}
case DISPLAY_TYPE_EPAPER: {
// Create E-Paper configuration from board_config.h
static const epaper_config epaper_cfg = {
.spi = DISPLAY_SPI_PORT,
.gpio_din = DISPLAY_MOSI_PIN,
.gpio_clk = DISPLAY_SCK_PIN,
.gpio_cs = DISPLAY_CS_PIN,
.gpio_dc = DISPLAY_DC_PIN,
.gpio_rst = DISPLAY_RST_PIN,
.gpio_busy = DISPLAY_BUSY_PIN,
};
printf("Creating E-Paper display (%dx%d) - STUB (driver not implemented)\n", width, height);
return new LowLevelDisplayEPaper(&epaper_cfg, width, height);
}
default:
printf("Error: Unknown display type %d\n", type);
return nullptr;
}
}

View File

@@ -0,0 +1,54 @@
#include "low_level_display_st7789.h"
#include <stdio.h>
// Note: This is a placeholder implementation
// You'll need to add the actual ST7789 driver code to lib/st7789/
LowLevelDisplayST7789::LowLevelDisplayST7789(const st7789_config* cfg, int w, int h)
: config(cfg), width(w), height(h), initialized(false) {
}
LowLevelDisplayST7789::~LowLevelDisplayST7789() {
// Cleanup if needed
}
bool LowLevelDisplayST7789::init() {
if (initialized) {
return true;
}
// TODO: Implement ST7789 initialization
// st7789_init(config, width, height);
printf("ST7789 display initialized: %dx%d (stub)\n", width, height);
initialized = true;
return false; // Return false until actual driver is implemented
}
void LowLevelDisplayST7789::clear(bool white) {
// TODO: Implement
(void)white;
}
void LowLevelDisplayST7789::draw_pixel(int x, int y, bool white) {
// TODO: Implement
(void)x; (void)y; (void)white;
}
void LowLevelDisplayST7789::draw_buffer(const uint8_t* bit_buffer) {
// TODO: Implement - convert 1-bit to RGB565 and write
(void)bit_buffer;
}
void LowLevelDisplayST7789::refresh() {
// ST7789 updates immediately
}
void LowLevelDisplayST7789::set_backlight(bool on) {
// TODO: Implement
(void)on;
}
void LowLevelDisplayST7789::set_rotation(uint8_t rotation) {
// TODO: Implement
(void)rotation;
}

View File

@@ -0,0 +1,48 @@
#ifndef LOW_LEVEL_DISPLAY_ST7789_H
#define LOW_LEVEL_DISPLAY_ST7789_H
#include "low_level_display.h"
// ST7789 configuration structure (similar to ST7796)
struct st7789_config {
void* spi; // SPI instance
int gpio_din; // MOSI pin
int gpio_clk; // Clock pin
int gpio_cs; // Chip select pin
int gpio_dc; // Data/Command pin
int gpio_rst; // Reset pin
int gpio_bl; // Backlight pin
};
class LowLevelDisplayST7789 : public LowLevelDisplay {
private:
const st7789_config* config;
int width;
int height;
bool initialized;
public:
LowLevelDisplayST7789(const st7789_config* cfg, int w, int h);
~LowLevelDisplayST7789() override;
// Core display operations - converts 1-bit to RGB565 internally
bool init() override;
void clear(bool white = true) override;
void draw_pixel(int x, int y, bool white) override;
void draw_buffer(const uint8_t* bit_buffer) override;
void refresh() override;
// Display properties
int get_width() const override { return width; }
int get_height() const override { return height; }
DisplayType get_type() const override { return DISPLAY_TYPE_ST7789; }
bool is_color() const override { return true; }
// Backlight control
void set_backlight(bool on) override;
// Orientation control
void set_rotation(uint8_t rotation) override;
};
#endif // LOW_LEVEL_DISPLAY_ST7789_H

View File

@@ -0,0 +1,77 @@
#include "low_level_display_st7796.h"
#include <stdio.h>
#include <stdlib.h>
// RGB565 color definitions
#define COLOR_BLACK 0x0000
#define COLOR_WHITE 0xFFFF
LowLevelDisplayST7796::LowLevelDisplayST7796(const st7796_config* cfg, int w, int h)
: config(cfg), width(w), height(h), initialized(false) {
}
LowLevelDisplayST7796::~LowLevelDisplayST7796() {
// Cleanup if needed
}
bool LowLevelDisplayST7796::init() {
if (initialized) {
return true;
}
st7796_init(config, width, height);
initialized = true;
printf("ST7796 display initialized: %dx%d\n", width, height);
return true;
}
void LowLevelDisplayST7796::clear(bool white) {
st7796_fill(white ? COLOR_WHITE : COLOR_BLACK);
}
void LowLevelDisplayST7796::draw_pixel(int x, int y, bool white) {
st7796_draw_pixel(x, y, white ? COLOR_WHITE : COLOR_BLACK);
}
void LowLevelDisplayST7796::draw_buffer(const uint8_t* bit_buffer) {
if (!bit_buffer) return;
// Allocate RGB565 buffer for entire screen
uint16_t *rgb_buffer = (uint16_t *)malloc(width * height * sizeof(uint16_t));
if (!rgb_buffer) {
printf("Error: Failed to allocate RGB buffer\n");
return;
}
// Convert 1-bit buffer to RGB565
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int byte_index = (y * width + x) / 8;
int bit_index = 7 - (x % 8);
bool pixel_white = (bit_buffer[byte_index] >> bit_index) & 0x01;
rgb_buffer[y * width + x] = pixel_white ? COLOR_WHITE : COLOR_BLACK;
}
}
// Draw entire buffer at once
st7796_set_cursor(0, 0);
st7796_write(rgb_buffer, width * height);
free(rgb_buffer);
}
void LowLevelDisplayST7796::refresh() {
// ST7796 updates immediately, no refresh needed
}
void LowLevelDisplayST7796::set_backlight(bool on) {
// ST7796 driver doesn't have backlight control yet
// TODO: Add GPIO control for backlight pin
(void)on;
}
void LowLevelDisplayST7796::set_rotation(uint8_t rotation) {
// ST7796 driver doesn't have rotation control yet
// TODO: Add MADCTL register manipulation for rotation
(void)rotation;
}

View File

@@ -0,0 +1,38 @@
#ifndef LOW_LEVEL_DISPLAY_ST7796_H
#define LOW_LEVEL_DISPLAY_ST7796_H
#include "low_level_display.h"
#include "st7796.h"
class LowLevelDisplayST7796 : public LowLevelDisplay {
private:
const st7796_config* config;
int width;
int height;
bool initialized;
public:
LowLevelDisplayST7796(const st7796_config* cfg, int w, int h);
~LowLevelDisplayST7796() override;
// Core display operations - converts 1-bit to RGB565 internally
bool init() override;
void clear(bool white = true) override;
void draw_pixel(int x, int y, bool white) override;
void draw_buffer(const uint8_t* bit_buffer) override;
void refresh() override;
// Display properties
int get_width() const override { return width; }
int get_height() const override { return height; }
DisplayType get_type() const override { return DISPLAY_TYPE_ST7796; }
bool is_color() const override { return true; }
// Backlight control
void set_backlight(bool on) override;
// Orientation control
void set_rotation(uint8_t rotation) override;
};
#endif // LOW_LEVEL_DISPLAY_ST7796_H

361
display/low_level_gui.cpp Normal file
View File

@@ -0,0 +1,361 @@
#include "low_level_render.h"
#include "low_level_gui.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);
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);
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;
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);
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);
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);
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);
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
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);
}
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);
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;
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);
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);
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);
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);
}

35
display/low_level_gui.h Normal file
View File

@@ -0,0 +1,35 @@
#include "low_level_render.h"
class LowLevelWindow
{
public:
int x, y, width, height;
const char* title;
LowLevelWindow(int x_pos, int y_pos, int w, int h, const char* t)
: x(x_pos), y(y_pos), width(w), height(h), title(t) {}
};
class LowLevelGUI
{
private:
LowLevelRenderer* renderer;
bool use_rounded_corners = true;
const Font* current_font;
public:
LowLevelGUI(LowLevelRenderer* rend, const Font& font);
LowLevelWindow* draw_new_window(int x, int y, int width, int height, const char *title);
void draw_window(LowLevelWindow* window);
void draw_button(LowLevelWindow* window, int x, int y, const char *label, bool pressed = false, bool rounded = true);
void draw_checkbox(LowLevelWindow* window, int x, int y, const char *label, bool checked = false);
void draw_radio_button(LowLevelWindow* window, int x, int y, const char *label, bool selected = false);
void draw_slider(LowLevelWindow* window, int x, int y, int width, int height, int position, char* label = nullptr);
void draw_calendar(LowLevelWindow* window, int x, int y, int month, int year);
void draw_textbox(LowLevelWindow* window, int x, int y, int width, int height, const char* content, bool focused = false);
void draw_tab(LowLevelWindow* window, int x, int y, int width, int height, const char* label, bool selected = false);
void draw_status_bar(LowLevelWindow* window, int x, int y, int width, const char* label, const char* sublabel, int percentage, const char* value_text);
void draw_circular_gauge(LowLevelWindow* window, int x, int y, int width, const char* label, int percentage);
void draw_notification(LowLevelWindow* window, int x, int y, int width, const char* time, const char* message);
void draw_large_clock(LowLevelWindow* window, int x, int y, const char* time_str);
};

View File

@@ -0,0 +1,668 @@
#include "low_level_render.h"
#include <cmath>
#include <algorithm>
#include <vector>
#include "./fonts/5x5_font.h"
#include "./fonts/BMplain_font.h"
#include "./fonts/Blokus_font.h"
#include "./fonts/HISKYF21_font.h"
#include "./fonts/Minimum_font.h"
#include "./fonts/SUPERDIG_font.h"
#include "./fonts/acme_5_outlines_font.h"
#include "./fonts/aztech_font.h"
#include "./fonts/crackers_font.h"
#include "./fonts/haiku_font.h"
#include "./fonts/sloth_font.h"
#include "./fonts/zxpix_font.h"
#include "./fonts/Commo-Monospaced_font.h"
#include "./fonts/7linedigital_font.h"
#include "./fonts/BMSPA_font.h"
#include "./fonts/HUNTER_font.h"
#include "./fonts/Raumsond_font.h"
#include "./fonts/bubblesstandard_font.h"
#include "./fonts/formplex12_font.h"
#include "./fonts/homespun_font.h"
#include "./fonts/Minimum_1_font.h"
#include "./fonts/m38_font.h"
#include "./fonts/pzim3x5_font.h"
#include "./fonts/renew_font.h"
#include "./fonts/tama_mini02_font.h"
// Font object definitions
Font font_5x5_obj(reinterpret_cast<const unsigned char*>(font_5x5), 96, 6, 8);
Font font_7linedigital_obj(reinterpret_cast<const unsigned char*>(font_7linedigital), 96, 4, 8);
Font font_acme_5_outlines_obj(reinterpret_cast<const unsigned char*>(font_acme_5_outlines), 96, 6, 8);
Font font_aztech_obj(reinterpret_cast<const unsigned char*>(font_aztech), 96, 6, 8);
Font font_BMplain_obj(reinterpret_cast<const unsigned char*>(font_BMplain), 96, 6, 8);
Font font_BMSPA_obj(reinterpret_cast<const unsigned char*>(font_BMSPA), 96, 8, 8);
Font font_Blokus_obj(reinterpret_cast<const unsigned char*>(font_Blokus), 96, 6, 8);
Font font_bubblesstandard_obj(reinterpret_cast<const unsigned char*>(font_bubblesstandard), 96, 7, 8);
Font font_Commo_Monospaced_obj(reinterpret_cast<const unsigned char*>(font_Commo_Monospaced), 96, 8, 8);
Font font_crackers_obj(reinterpret_cast<const unsigned char*>(font_crackers), 96, 6, 8);
Font font_formplex12_obj(reinterpret_cast<const unsigned char*>(font_formplex12), 96, 8, 8);
Font font_haiku_obj(reinterpret_cast<const unsigned char*>(font_haiku), 96, 6, 8);
Font font_HISKYF21_obj(reinterpret_cast<const unsigned char*>(font_HISKYF21), 96, 6, 8);
Font font_homespun_obj(reinterpret_cast<const unsigned char*>(font_homespun), 96, 7, 8);
Font font_HUNTER_obj(reinterpret_cast<const unsigned char*>(font_HUNTER), 96, 8, 8);
Font font_m38_obj(reinterpret_cast<const unsigned char*>(font_m38), 96, 8, 8);
Font font_Minimum_obj(reinterpret_cast<const unsigned char*>(font_Minimum), 96, 6, 8);
Font font_Minimum_1_obj(reinterpret_cast<const unsigned char*>(font_Minimum_1), 96, 7, 8);
Font font_pzim3x5_obj(reinterpret_cast<const unsigned char*>(font_pzim3x5), 96, 3, 8);
Font font_Raumsond_obj(reinterpret_cast<const unsigned char*>(font_Raumsond), 96, 5, 8);
Font font_renew_obj(reinterpret_cast<const unsigned char*>(font_renew), 96, 7, 8);
Font font_sloth_obj(reinterpret_cast<const unsigned char*>(font_sloth), 96, 6, 8);
Font font_SUPERDIG_obj(reinterpret_cast<const unsigned char*>(font_SUPERDIG), 96, 6, 8);
Font font_tama_mini02_obj(reinterpret_cast<const unsigned char*>(font_tama_mini02), 96, 5, 8);
Font font_zxpix_obj(reinterpret_cast<const unsigned char*>(font_zxpix), 96, 6, 8);
LowLevelRenderer::LowLevelRenderer(uint8_t* buffer, int width, int height)
: bit_buffer(buffer), V_WIDTH(width), V_HEIGHT(height), current_font(nullptr),
clipping_enabled(false), clip_x(0), clip_y(0), clip_width(width), clip_height(height), text_color(true) {}
void LowLevelRenderer::set_font(const Font* font) {
current_font = font;
}
void LowLevelRenderer::set_text_color(bool color) {
text_color = color;
}
// Clipping functions
void LowLevelRenderer::set_clip_rect(int x, int y, int width, int height) {
clip_x = x;
clip_y = y;
clip_width = width;
clip_height = height;
clipping_enabled = true;
}
void LowLevelRenderer::reset_clip_rect() {
clipping_enabled = false;
clip_x = 0;
clip_y = 0;
clip_width = V_WIDTH;
clip_height = V_HEIGHT;
}
bool LowLevelRenderer::is_clipping_enabled() const {
return clipping_enabled;
}
bool LowLevelRenderer::is_point_in_clip_rect(int x, int y) {
if (!clipping_enabled) return true;
return (x >= clip_x && x < clip_x + clip_width &&
y >= clip_y && y < clip_y + clip_height);
}
// Buffer operations
void LowLevelRenderer::invert_buffer() {
int buffer_size = (V_WIDTH * V_HEIGHT + 7) / 8; // Round up for bit buffer size
for (int i = 0; i < buffer_size; ++i) {
bit_buffer[i] = ~bit_buffer[i]; // Bitwise NOT to invert all bits
}
}
void LowLevelRenderer::clear_buffer() {
int buffer_size = (V_WIDTH * V_HEIGHT + 7) / 8;
for (int i = 0; i < buffer_size; ++i) {
bit_buffer[i] = 0;
}
}
void LowLevelRenderer::set_pixel(int x, int y, bool on)
{
if (x < 0 || x >= V_WIDTH || y < 0 || y >= V_HEIGHT)
return;
// Check clipping
if (!is_point_in_clip_rect(x, y))
return;
int bit_pos = y * V_WIDTH + x;
if (on)
bit_buffer[bit_pos / 8] |= (1 << (7 - (bit_pos % 8)));
else
bit_buffer[bit_pos / 8] &= ~(1 << (7 - (bit_pos % 8)));
}
void LowLevelRenderer::draw_line(int x0, int y0, int x1, int y1, bool on, int width)
{
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy, e2;
while (true)
{
// Draw a vertical line for the specified width
for (int w = -(width / 2); w <= (width / 2); w++)
{
set_pixel(x0 + w, y0, on);
}
for (int w = -(width / 2); w <= (width / 2); w++)
{
set_pixel(x0, y0 + w, on);
}
set_pixel(x0, y0, on);
if (x0 == x1 && y0 == y1)
break;
e2 = 2 * err;
if (e2 >= dy)
{
err += dy;
x0 += sx;
}
if (e2 <= dx)
{
err += dx;
y0 += sy;
}
}
}
void LowLevelRenderer::draw_rectangle(int x, int y, int width, int height, bool on, int line_width)
{
// Draw top line
draw_line(x, y, x + width - 1, y, on, line_width);
// Draw bottom line
draw_line(x, y + height - 1, x + width - 1, y + height - 1, on, line_width);
// Draw left line
draw_line(x, y, x, y + height - 1, on, line_width);
// Draw right line
draw_line(x + width - 1, y, x + width - 1, y + height - 1, on, line_width);
}
void LowLevelRenderer::draw_filled_rectangle(int x, int y, int width, int height, bool on, int line_width)
{
for (int i = 0; i < height; i++)
{
draw_line(x, y + i, x + width - 1, y + i, on, line_width);
}
}
void LowLevelRenderer::draw_rounded_rectangle(int x, int y, int width, int height, int radius, bool on, bool filled)
{
// Ensure radius doesn't exceed half the smaller dimension
int max_radius = std::min(width, height) / 2;
if (radius > max_radius) radius = max_radius;
if (radius < 0) radius = 0;
if (!filled) {
// --- Outline Logic ---
if (width > 2 * radius) {
draw_line(x + radius, y, x + width - radius - 1, y, on);
draw_line(x + radius, y + height - 1, x + width - radius - 1, y + height - 1, on);
}
if (height > 2 * radius) {
draw_line(x, y + radius, x, y + height - radius - 1, on);
draw_line(x + width - 1, y + radius, x + width - 1, y + height - radius - 1, on);
}
if (radius > 0) {
draw_corner_arc(x + radius, y + radius, radius, 2, on);
draw_corner_arc(x + width - radius - 1, y + radius, radius, 1, on);
draw_corner_arc(x + radius, y + height - radius - 1, radius, 3, on);
draw_corner_arc(x + width - radius - 1, y + height - radius - 1, radius, 0, on);
}
} else {
// --- Filling Logic ---
// 1. Fill the central rectangular body (excluding the top and bottom radius areas)
draw_filled_rectangle(x, y + radius, width, height - 2 * radius, on, 1);
// 2. Fill the top and bottom sections with horizontal lines of varying widths
for (int i = 0; i < radius; i++) {
// Calculate horizontal offset using Pythagorean theorem: offset = r - sqrt(r^2 - (r-i)^2)
int offset = radius - (int)sqrt((double)radius * radius - (double)(radius - i) * (radius - i));
// Top radius row
draw_line(x + offset, y + i, x + width - offset - 1, y + i, on);
// Bottom radius row
int bottom_y = y + height - radius + i;
// Mirroring the offset logic for the bottom
int b_offset = radius - (int)sqrt((double)radius * radius - (double)(i + 1) * (i + 1));
draw_line(x + b_offset, bottom_y, x + width - b_offset - 1, bottom_y, on);
}
}
}
void LowLevelRenderer::draw_corner_arc(int center_x, int center_y, int radius, int quadrant, bool on)
{
int x = radius;
int y = 0;
int err = 0;
while (x >= y)
{
// Depending on quadrant, set pixels in the appropriate octants
switch (quadrant)
{
case 0: // Bottom-right
set_pixel(center_x + x, center_y + y, on);
set_pixel(center_x + y, center_y + x, on);
break;
case 1: // Top-right
set_pixel(center_x + x, center_y - y, on);
set_pixel(center_x + y, center_y - x, on);
break;
case 2: // Top-left
set_pixel(center_x - x, center_y - y, on);
set_pixel(center_x - y, center_y - x, on);
break;
case 3: // Bottom-left
set_pixel(center_x - x, center_y + y, on);
set_pixel(center_x - y, center_y + x, on);
break;
}
if (err <= 0)
{
y += 1;
err += 2 * y + 1;
}
if (err > 0)
{
x -= 1;
err -= 2 * x + 1;
}
}
}
void LowLevelRenderer::draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on)
{
draw_line(x1, y1, x2, y2, on);
draw_line(x2, y2, x3, y3, on);
draw_line(x3, y3, x1, y1, on);
}
void LowLevelRenderer::draw_filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on)
{
// Sort points by y-coordinate
if (y1 > y2) { std::swap(x1, x2); std::swap(y1, y2); }
if (y1 > y3) { std::swap(x1, x3); std::swap(y1, y3); }
if (y2 > y3) { std::swap(x2, x3); std::swap(y2, y3); }
// Flat bottom triangle
if (y2 == y3) {
fill_bottom_flat_triangle(x1, y1, x2, y2, x3, y3, on);
}
// Flat top triangle
else if (y1 == y2) {
fill_top_flat_triangle(x1, y1, x2, y2, x3, y3, on);
}
// General triangle - split into flat bottom and flat top
else {
int x4 = x1 + ((y2 - y1) * (x3 - x1)) / (y3 - y1);
int y4 = y2;
fill_bottom_flat_triangle(x1, y1, x2, y2, x4, y4, on);
fill_top_flat_triangle(x2, y2, x4, y4, x3, y3, on);
}
}
void LowLevelRenderer::fill_bottom_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on)
{
float invslope1 = (float)(x2 - x1) / (y2 - y1);
float invslope2 = (float)(x3 - x1) / (y3 - y1);
float curx1 = x1;
float curx2 = x1;
for (int scanlineY = y1; scanlineY <= y2; scanlineY++) {
draw_line((int)curx1, scanlineY, (int)curx2, scanlineY, on);
curx1 += invslope1;
curx2 += invslope2;
}
}
void LowLevelRenderer::fill_top_flat_triangle(int x1, int y1, int x2, int y2, int x3, int y3, bool on)
{
float invslope1 = (float)(x3 - x1) / (y3 - y1);
float invslope2 = (float)(x3 - x2) / (y3 - y2);
float curx1 = x3;
float curx2 = x3;
for (int scanlineY = y3; scanlineY > y1; scanlineY--) {
draw_line((int)curx1, scanlineY, (int)curx2, scanlineY, on);
curx1 -= invslope1;
curx2 -= invslope2;
}
}
void LowLevelRenderer::draw_ellipse(int center_x, int center_y, int radius_x, int radius_y, bool on)
{
int x = 0;
int y = radius_y;
// Decision parameter for region 1
long long a2 = radius_x * radius_x;
long long b2 = radius_y * radius_y;
long long fa2 = 4 * a2, fb2 = 4 * b2;
long long sigma = 2 * b2 + a2 * (1 - 2 * radius_y);
// Region 1
while (b2 * x <= a2 * y) {
set_pixel(center_x + x, center_y + y, on);
set_pixel(center_x - x, center_y + y, on);
set_pixel(center_x + x, center_y - y, on);
set_pixel(center_x - x, center_y - y, on);
if (sigma >= 0) {
sigma += fa2 * (1 - y);
y--;
}
sigma += b2 * ((4 * x) + 6);
x++;
}
// Region 2
x = radius_x;
y = 0;
sigma = 2 * a2 + b2 * (1 - 2 * radius_x);
while (a2 * y <= b2 * x) {
set_pixel(center_x + x, center_y + y, on);
set_pixel(center_x - x, center_y + y, on);
set_pixel(center_x + x, center_y - y, on);
set_pixel(center_x - x, center_y - y, on);
if (sigma >= 0) {
sigma += fb2 * (1 - x);
x--;
}
sigma += a2 * ((4 * y) + 6);
y++;
}
}
void LowLevelRenderer::draw_filled_ellipse(int center_x, int center_y, int radius_x, int radius_y, bool on)
{
int hh = radius_y * radius_y;
int ww = radius_x * radius_x;
int hhww = hh * ww;
int x0 = radius_x;
int dx = 0;
// Do the horizontal diameter
draw_line(center_x - radius_x, center_y, center_x + radius_x, center_y, on);
// Now do both halves at the same time, away from the diameter
for (int y = 1; y <= radius_y; y++) {
int x1 = x0 - (dx - 1); // Try slopes of dx - 1 or more
for ( ; x1 > 0; x1--) {
if (x1*x1*hh + y*y*ww <= hhww)
break;
}
dx = x0 - x1; // Current approximation of the slope
x0 = x1;
draw_line(center_x - x0, center_y - y, center_x + x0, center_y - y, on);
draw_line(center_x - x0, center_y + y, center_x + x0, center_y + y, on);
}
}
void LowLevelRenderer::draw_polygon(const std::vector<std::pair<int, int>>& points, bool on)
{
if (points.size() < 3) return;
for (size_t i = 0; i < points.size(); ++i) {
size_t next = (i + 1) % points.size();
draw_line(points[i].first, points[i].second,
points[next].first, points[next].second, on);
}
}
void LowLevelRenderer::draw_filled_polygon(const std::vector<std::pair<int, int>>& points, bool on)
{
if (points.size() < 3) return;
// Simple triangulation: fan from first vertex
// This works for convex polygons
for (size_t i = 1; i < points.size() - 1; ++i) {
draw_filled_triangle(points[0].first, points[0].second,
points[i].first, points[i].second,
points[i+1].first, points[i+1].second, on);
}
}
void LowLevelRenderer::draw_arc(int center_x, int center_y, int radius, int start_angle, int end_angle, bool on)
{
// Normalize angles to 0-360 range
start_angle = start_angle % 360;
end_angle = end_angle % 360;
if (start_angle < 0) start_angle += 360;
if (end_angle < 0) end_angle += 360;
// Handle wrap-around
if (start_angle > end_angle) {
draw_arc(center_x, center_y, radius, start_angle, 360, on);
draw_arc(center_x, center_y, radius, 0, end_angle, on);
return;
}
int x = radius;
int y = 0;
int err = 0;
// Convert angles to radians for comparison
double start_rad = start_angle * M_PI / 180.0;
double end_rad = end_angle * M_PI / 180.0;
while (x >= y) {
// Check each octant point against angle range
double angles[8] = {
atan2(y, x), // 0-45 deg
atan2(x, y), // 45-90 deg
atan2(x, -y), // 90-135 deg
atan2(y, -x), // 135-180 deg
atan2(-y, -x), // 180-225 deg
atan2(-x, -y), // 225-270 deg
atan2(-x, y), // 270-315 deg
atan2(-y, x) // 315-360 deg
};
int dx[8] = {x, y, -y, -x, -x, -y, y, x};
int dy[8] = {y, x, x, y, -y, -x, -x, -y};
for (int i = 0; i < 8; ++i) {
double angle = angles[i];
if (angle < 0) angle += 2 * M_PI;
if (angle >= start_rad && angle <= end_rad) {
set_pixel(center_x + dx[i], center_y + dy[i], on);
}
}
if (err <= 0) {
y += 1;
err += 2 * y + 1;
}
if (err > 0) {
x -= 1;
err -= 2 * x + 1;
}
}
}
void LowLevelRenderer::draw_bitmap(const unsigned char* bitmap, int x, int y, int width, int height, bool invert)
{
for (int py = 0; py < height; ++py) {
for (int px = 0; px < width; ++px) {
int bit_index = py * width + px;
int byte_index = bit_index / 8;
int bit_offset = 7 - (bit_index % 8); // MSB first
bool pixel_on = (bitmap[byte_index] & (1 << bit_offset)) != 0;
if (invert) {
pixel_on = !pixel_on;
}
if (pixel_on) {
set_pixel(x + px, y + py, text_color);
}
}
}
}
void LowLevelRenderer::draw_circle(int x, int y, int radius, bool on)
{
int x_pos = radius;
int y_pos = 0;
int err = 0;
while (x_pos >= y_pos)
{
set_pixel(x + x_pos, y + y_pos, on);
set_pixel(x + y_pos, y + x_pos, on);
set_pixel(x - y_pos, y + x_pos, on);
set_pixel(x - x_pos, y + y_pos, on);
set_pixel(x - x_pos, y - y_pos, on);
set_pixel(x - y_pos, y - x_pos, on);
set_pixel(x + y_pos, y - x_pos, on);
set_pixel(x + x_pos, y - y_pos, on);
if (err <= 0)
{
y_pos += 1;
err += 2 * y_pos + 1;
}
if (err > 0)
{
x_pos -= 1;
err -= 2 * x_pos + 1;
}
}
}
void LowLevelRenderer::draw_filled_circle(int x, int y, int radius, bool on)
{
int radius_squared = radius * radius;
for (int dy = -radius; dy <= radius; dy++)
{
for (int dx = -radius; dx <= radius; dx++)
{
if (dx * dx + dy * dy <= radius_squared)
{
set_pixel(x + dx, y + dy, on);
}
}
}
}
int LowLevelRenderer::draw_char_vcol(int x, int y, char c)
{
if (!current_font) return 0;
// The font table starts at space (ASCII 32)
if (c < 32 || c > 127)
return 0;
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();
int char_height = current_font->get_char_height();
// 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 && c != ' ')
{
actual_width = col + 1;
break;
}
}
// Draw only up to the actual width
for (int col = 0; col < actual_width; col++)
{
unsigned char column_byte = char_data[col];
for (int row = 0; row < char_height; row++)
{
// Check if the bit for this row is set
if (column_byte & (1 << row))
{
set_pixel(x + col, y + row, text_color);
}
}
}
return actual_width;
}
void LowLevelRenderer::draw_string(int x, int y, const std::string &text, int spacing)
{
if (!current_font) return;
int current_x = x;
for (size_t i = 0; i < text.length(); i++)
{
int char_width = draw_char_vcol(current_x, y, text[i]);
current_x += char_width + spacing;
}
}
int LowLevelRenderer::draw_char_scaled(int x, int y, char c, int scale)
{
if (!current_font) return 0;
if (c < 32 || c > 127)
return 0;
if (scale < 1)
scale = 1; // Safety check
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();
int char_height = current_font->get_char_height();
// 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;
}
}
// Draw only up to the actual width, scaled
for (int col = 0; col < actual_width; col++)
{
unsigned char column_byte = char_data[col];
for (int row = 0; row < char_height; row++)
{
if (column_byte & (1 << row))
{
// Draw a square of size [scale x scale]
for (int sy = 0; sy < scale; sy++)
{
for (int sx = 0; sx < scale; sx++)
{
set_pixel(x + (col * scale) + sx,
y + (row * scale) + sy,
text_color);
}
}
}
}
}
return actual_width * scale;
}
int LowLevelRenderer::draw_string_scaled(int x, int y, const char* text, int scale, int spacing)
{
if (!current_font) return 0;
int current_x = x;
int i = 0;
while(text[i] != '\0')
{
int char_width = draw_char_scaled(current_x, y, text[i], scale);
current_x += char_width + (spacing * scale);
i++;
}
return current_x;
}

153
display/low_level_render.h Normal file
View File

@@ -0,0 +1,153 @@
// 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
#include <cstdint>
#include <string>
#include <vector>
// 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;
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) {}
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
extern const unsigned char font_5x5[96][6];
extern const unsigned char font_7linedigital[96][4];
extern const unsigned char font_acme_5_outlines[96][6];
extern const unsigned char font_aztech[96][6];
extern const unsigned char font_BMplain[96][6];
extern const unsigned char font_BMSPA[96][8];
extern const unsigned char font_Blokus[96][6];
extern const unsigned char font_bubblesstandard[96][7];
extern const unsigned char font_Commo_Monospaced[96][8];
extern const unsigned char font_crackers[96][6];
extern const unsigned char font_formplex12[96][8];
extern const unsigned char font_haiku[96][6];
extern const unsigned char font_HISKYF21[96][6];
extern const unsigned char font_homespun[96][7];
extern const unsigned char font_HUNTER[96][8];
extern const unsigned char font_m38[96][8];
extern const unsigned char font_Minimum[96][6];
extern const unsigned char font_Minimum_1[96][7];
extern const unsigned char font_pzim3x5[96][3];
extern const unsigned char font_Raumsond[96][5];
extern const unsigned char font_renew[96][7];
extern const unsigned char font_sloth[96][6];
extern const unsigned char font_SUPERDIG[96][6];
extern const unsigned char font_tama_mini02[96][5];
extern const unsigned char font_zxpix[96][6];
// Font object declarations
extern Font font_5x5_obj;
extern Font font_7linedigital_obj;
extern Font font_acme_5_outlines_obj;
extern Font font_aztech_obj;
extern Font font_BMplain_obj;
extern Font font_BMSPA_obj;
extern Font font_Blokus_obj;
extern Font font_bubblesstandard_obj;
extern Font font_Commo_Monospaced_obj;
extern Font font_crackers_obj;
extern Font font_formplex12_obj;
extern Font font_haiku_obj;
extern Font font_HISKYF21_obj;
extern Font font_homespun_obj;
extern Font font_HUNTER_obj;
extern Font font_m38_obj;
extern Font font_Minimum_obj;
extern Font font_Minimum_1_obj;
extern Font font_pzim3x5_obj;
extern Font font_Raumsond_obj;
extern Font font_renew_obj;
extern Font font_sloth_obj;
extern Font font_SUPERDIG_obj;
extern Font font_tama_mini02_obj;
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);
public:
LowLevelRenderer(uint8_t* buffer, int width, int height);
// Font management
void set_font(const Font* font);
void set_text_color(bool color);
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);
};
#endif // LOW_LEVEL_RENDER_H

83
display/low_level_touch.h Normal file
View File

@@ -0,0 +1,83 @@
#ifndef LOW_LEVEL_TOUCH_H
#define LOW_LEVEL_TOUCH_H
#include <cstdint>
#include <stdbool.h>
using std::int16_t;
using std::uint8_t;
using std::uint32_t;
// Touch driver type enumeration
typedef enum {
TOUCH_TYPE_FT6336U,
TOUCH_TYPE_NONE // For boards without touch
} TouchType;
// Maximum touch points (most capacitive touch controllers support 1-5 points)
#define TOUCH_MAX_POINTS 2
// Touch event types
typedef enum {
TOUCH_EVENT_PRESS_DOWN = 0,
TOUCH_EVENT_LIFT_UP = 1,
TOUCH_EVENT_CONTACT = 2,
TOUCH_EVENT_NO_EVENT = 3
} TouchEvent;
// Touch point structure
typedef struct {
int16_t x;
int16_t y;
uint8_t event;
uint8_t id;
uint8_t pressure; // Touch pressure/area/weight
} TouchPoint;
// Touch data structure
typedef struct {
uint8_t touch_count;
TouchPoint points[TOUCH_MAX_POINTS];
uint8_t gesture;
} TouchData;
// Abstract touch interface
class LowLevelTouch {
public:
virtual ~LowLevelTouch() {}
// Core touch operations
virtual bool init() = 0;
virtual bool read_touch(TouchData* data) = 0;
virtual bool is_touched() = 0;
// Touch properties
virtual int get_screen_width() const = 0;
virtual int get_screen_height() const = 0;
virtual TouchType get_type() const = 0;
virtual uint8_t get_max_touch_points() const = 0;
// Optional: Device information
virtual uint8_t get_chip_id() { return 0xFF; }
virtual uint8_t get_firmware_version() { return 0xFF; }
// Optional: Coordinate transformation (handled internally by driver)
virtual void set_coordinate_transform(bool swap_xy, bool invert_x, bool invert_y) {
(void)swap_xy; (void)invert_x; (void)invert_y;
}
// Optional: Interrupt callback
virtual void set_interrupt_callback(void (*callback)(unsigned int gpio, uint32_t events)) {
(void)callback;
}
// Optional: Test/diagnostic
virtual bool test_communication() { return false; }
// Factory method - creates touch controller based on type and screen dimensions
// Uses board_config.h for pin definitions and optional transform parameters
static LowLevelTouch* create(TouchType type, int screen_width, int screen_height,
bool swap_xy = false, bool invert_x = false, bool invert_y = false);
};
#endif // LOW_LEVEL_TOUCH_H

View File

@@ -0,0 +1,31 @@
#include "low_level_touch.h"
#include "low_level_touch_ft6336u.h"
#include <cstdio>
LowLevelTouch* LowLevelTouch::create(TouchType type, int screen_width, int screen_height,
bool swap_xy, bool invert_x, bool invert_y) {
LowLevelTouch* touch = nullptr;
switch (type) {
case TOUCH_TYPE_FT6336U:
printf("Creating FT6336U touch controller...\n");
touch = new LowLevelTouchFT6336U(screen_width, screen_height, swap_xy, invert_x, invert_y);
break;
case TOUCH_TYPE_NONE:
printf("No touch controller configured\n");
return nullptr;
default:
printf("Unknown touch type: %d\n", type);
return nullptr;
}
if (touch && !touch->init()) {
printf("Failed to initialize touch controller\n");
delete touch;
return nullptr;
}
return touch;
}

View File

@@ -0,0 +1,107 @@
#include "low_level_touch_ft6336u.h"
#include "board_config.h"
#include <cstdio>
LowLevelTouchFT6336U::LowLevelTouchFT6336U(int width, int height, bool swap_xy,
bool invert_x, bool invert_y)
: screen_width(width)
, screen_height(height)
, swap_xy(swap_xy)
, invert_x(invert_x)
, invert_y(invert_y)
, initialized(false)
{
}
LowLevelTouchFT6336U::~LowLevelTouchFT6336U() {
// Cleanup if needed
}
bool LowLevelTouchFT6336U::init() {
if (initialized) {
return true;
}
// Build configuration from board_config.h
ft6336u_config_t config = {
.i2c = TOUCH_I2C_PORT,
.gpio_sda = TOUCH_SDA_PIN,
.gpio_scl = TOUCH_SCL_PIN,
.gpio_rst = TOUCH_RST_PIN,
.gpio_int = TOUCH_INT_PIN,
.screen_width = (uint16_t)screen_width,
.screen_height = (uint16_t)screen_height,
.swap_xy = swap_xy,
.invert_x = invert_x,
.invert_y = invert_y
};
initialized = ft6336u_init(&config);
if (initialized) {
printf("Touch FT6336U initialized: Chip ID=0x%02X, FW Ver=0x%02X\n",
get_chip_id(), get_firmware_version());
} else {
printf("Touch FT6336U initialization failed!\n");
}
return initialized;
}
bool LowLevelTouchFT6336U::read_touch(TouchData* data) {
if (!initialized || !data) {
return false;
}
ft6336u_touch_data_t ft_data;
if (!ft6336u_read_touch(&ft_data)) {
return false;
}
// Convert from FT6336U format to our abstract format
data->touch_count = ft_data.touch_count;
data->gesture = ft_data.gesture;
for (int i = 0; i < ft_data.touch_count && i < TOUCH_MAX_POINTS; i++) {
data->points[i].x = ft_data.points[i].x;
data->points[i].y = ft_data.points[i].y;
data->points[i].event = ft_data.points[i].event;
data->points[i].id = ft_data.points[i].id;
data->points[i].pressure = ft_data.points[i].weight;
}
return true;
}
bool LowLevelTouchFT6336U::is_touched() {
if (!initialized) {
return false;
}
return ft6336u_is_touched();
}
uint8_t LowLevelTouchFT6336U::get_chip_id() {
return ft6336u_get_chip_id();
}
uint8_t LowLevelTouchFT6336U::get_firmware_version() {
return ft6336u_get_firmware_version();
}
void LowLevelTouchFT6336U::set_coordinate_transform(bool swap_xy, bool invert_x, bool invert_y) {
this->swap_xy = swap_xy;
this->invert_x = invert_x;
this->invert_y = invert_y;
// Note: This only updates internal state. To apply to the driver,
// you would need to reinitialize with the new settings.
printf("Touch coordinate transform updated (requires re-init to apply)\n");
}
void LowLevelTouchFT6336U::set_interrupt_callback(void (*callback)(unsigned int gpio, uint32_t events)) {
ft6336u_set_interrupt_callback(callback);
}
bool LowLevelTouchFT6336U::test_communication() {
return ft6336u_test_i2c();
}

View File

@@ -0,0 +1,47 @@
#ifndef LOW_LEVEL_TOUCH_FT6336U_H
#define LOW_LEVEL_TOUCH_FT6336U_H
#include "low_level_touch.h"
#include "../lib/ft6336u/ft6336u.h"
// FT6336U Touch Driver Implementation
class LowLevelTouchFT6336U : public LowLevelTouch {
private:
int screen_width;
int screen_height;
bool swap_xy;
bool invert_x;
bool invert_y;
bool initialized;
public:
LowLevelTouchFT6336U(int width, int height, bool swap_xy = false,
bool invert_x = false, bool invert_y = false);
virtual ~LowLevelTouchFT6336U();
// Core operations
bool init() override;
bool read_touch(TouchData* data) override;
bool is_touched() override;
// Touch properties
int get_screen_width() const override { return screen_width; }
int get_screen_height() const override { return screen_height; }
TouchType get_type() const override { return TOUCH_TYPE_FT6336U; }
uint8_t get_max_touch_points() const override { return 2; }
// Device information
uint8_t get_chip_id() override;
uint8_t get_firmware_version() override;
// Coordinate transformation
void set_coordinate_transform(bool swap_xy, bool invert_x, bool invert_y) override;
// Interrupt callback
void set_interrupt_callback(void (*callback)(unsigned int gpio, uint32_t events)) override;
// Test/diagnostic
bool test_communication() override;
};
#endif // LOW_LEVEL_TOUCH_FT6336U_H