abstracting display, touch and sd
This commit is contained in:
42
display/low_level_display.h
Normal file
42
display/low_level_display.h
Normal 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
|
||||
97
display/low_level_display_epaper.cpp
Normal file
97
display/low_level_display_epaper.cpp
Normal 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();
|
||||
}
|
||||
48
display/low_level_display_epaper.h
Normal file
48
display/low_level_display_epaper.h
Normal 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
|
||||
62
display/low_level_display_factory.cpp
Normal file
62
display/low_level_display_factory.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
54
display/low_level_display_st7789.cpp
Normal file
54
display/low_level_display_st7789.cpp
Normal 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;
|
||||
}
|
||||
48
display/low_level_display_st7789.h
Normal file
48
display/low_level_display_st7789.h
Normal 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
|
||||
77
display/low_level_display_st7796.cpp
Normal file
77
display/low_level_display_st7796.cpp
Normal 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;
|
||||
}
|
||||
38
display/low_level_display_st7796.h
Normal file
38
display/low_level_display_st7796.h
Normal 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
361
display/low_level_gui.cpp
Normal 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
35
display/low_level_gui.h
Normal 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);
|
||||
};
|
||||
668
display/low_level_render.cpp
Normal file
668
display/low_level_render.cpp
Normal 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
153
display/low_level_render.h
Normal 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
83
display/low_level_touch.h
Normal 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
|
||||
31
display/low_level_touch_factory.cpp
Normal file
31
display/low_level_touch_factory.cpp
Normal 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;
|
||||
}
|
||||
107
display/low_level_touch_ft6336u.cpp
Normal file
107
display/low_level_touch_ft6336u.cpp
Normal 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();
|
||||
}
|
||||
47
display/low_level_touch_ft6336u.h
Normal file
47
display/low_level_touch_ft6336u.h
Normal 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
|
||||
Reference in New Issue
Block a user