Initial Demo OS

This commit is contained in:
dark98
2026-09-03 00:46:04 +01:00
commit 1491821e96
45 changed files with 2908 additions and 0 deletions
+305
View File
@@ -0,0 +1,305 @@
#include "os_gfx.h"
#include "os_font_5x7.h"
#include "os_font_3x5.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "esp_timer.h"
static char s_toast_msg[32] = {0};
static uint64_t s_toast_expire_us = 0;
esp_err_t sys_gfx_init(void)
{
return oled_driver_init(OLED_PIN_SDA, OLED_PIN_SCL, OLED_COLUMN_OFFSET);
}
void sys_gfx_clear(void)
{
oled_driver_clear();
}
void sys_gfx_clear_viewport(void)
{
for (int16_t y = VIEWPORT_Y_TOP; y < OLED_HEIGHT; y++) {
for (int16_t x = 0; x < OLED_WIDTH; x++) {
oled_driver_draw_pixel(x, y, COLOR_BLACK);
}
}
}
esp_err_t sys_gfx_flush(void)
{
return oled_driver_flush();
}
void sys_gfx_draw_pixel(int16_t x, int16_t y, uint8_t color)
{
oled_driver_draw_pixel(x, y, color);
}
void sys_gfx_draw_line(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t color)
{
int16_t dx = abs(x1 - x0);
int16_t dy = abs(y1 - y0);
int16_t sx = (x0 < x1) ? 1 : -1;
int16_t sy = (y0 < y1) ? 1 : -1;
int16_t err = dx - dy;
while (1) {
oled_driver_draw_pixel(x0, y0, color);
if (x0 == x1 && y0 == y1) break;
int16_t e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
void sys_gfx_draw_rect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color)
{
if (w <= 0 || h <= 0) return;
sys_gfx_draw_line(x, y, x + w - 1, y, color);
sys_gfx_draw_line(x, y + h - 1, x + w - 1, y + h - 1, color);
sys_gfx_draw_line(x, y, x, y + h - 1, color);
sys_gfx_draw_line(x + w - 1, y, x + w - 1, y + h - 1, color);
}
void sys_gfx_fill_rect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color)
{
for (int16_t i = x; i < x + w; i++) {
for (int16_t j = y; j < y + h; j++) {
oled_driver_draw_pixel(i, j, color);
}
}
}
void sys_gfx_draw_char_5x7(int16_t x, int16_t y, char c, uint8_t color)
{
if (c < 32 || c > 127) c = '?';
const uint8_t *char_data = font_5x7[c - 32];
for (uint8_t col = 0; col < 5; col++) {
uint8_t line = char_data[col];
for (uint8_t row = 0; row < 7; row++) {
if (line & (1 << row)) {
oled_driver_draw_pixel(x + col, y + row, color);
}
}
}
}
void sys_gfx_draw_str_5x7(int16_t x, int16_t y, const char *str, uint8_t color)
{
if (!str) return;
int16_t cur_x = x;
int16_t cur_y = y;
while (*str) {
if (*str == '\n') {
cur_x = x;
cur_y += 8;
} else {
sys_gfx_draw_char_5x7(cur_x, cur_y, *str, color);
cur_x += 6;
}
str++;
}
}
void sys_gfx_draw_char_3x5(int16_t x, int16_t y, char c, uint8_t color)
{
if (c < 32 || c > 127) c = '?';
const uint8_t *char_data = font_3x5[c - 32];
for (uint8_t col = 0; col < 3; col++) {
uint8_t line = char_data[col];
for (uint8_t row = 0; row < 5; row++) {
if (line & (1 << row)) {
oled_driver_draw_pixel(x + col, y + row, color);
}
}
}
}
void sys_gfx_draw_str_3x5(int16_t x, int16_t y, const char *str, uint8_t color)
{
if (!str) return;
int16_t cur_x = x;
int16_t cur_y = y;
while (*str) {
if (*str == '\n') {
cur_x = x;
cur_y += 6;
} else {
sys_gfx_draw_char_3x5(cur_x, cur_y, *str, color);
cur_x += 4;
}
str++;
}
}
void sys_gfx_draw_bitmap(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *bitmap, uint8_t color)
{
if (!bitmap) return;
int16_t byte_width = (w + 7) / 8;
for (int16_t j = 0; j < h; j++) {
for (int16_t i = 0; i < w; i++) {
if (bitmap[j * byte_width + i / 8] & (128 >> (i & 7))) {
oled_driver_draw_pixel(x + i, y + j, color);
}
}
}
}
/* UI Framework: Micro Status Bar (Top 8 pixels) */
void sys_gfx_draw_status_bar(const sys_status_t *status)
{
if (!status || !status->status_bar_visible) return;
sys_gfx_fill_rect(0, 0, OLED_WIDTH, 7, COLOR_BLACK);
sys_gfx_draw_line(0, 7, OLED_WIDTH - 1, 7, COLOR_WHITE);
if (status->active_app_name) {
char short_name[7];
snprintf(short_name, sizeof(short_name), "%.5s", status->active_app_name);
sys_gfx_draw_str_3x5(1, 1, short_name, COLOR_WHITE);
}
if (status->wifi_state == WIFI_STATE_CONNECTED) {
sys_gfx_draw_line(43, 4, 43, 5, COLOR_WHITE);
sys_gfx_draw_line(45, 3, 45, 5, COLOR_WHITE);
sys_gfx_draw_line(47, 1, 47, 5, COLOR_WHITE);
} else if (status->wifi_state == WIFI_STATE_CONNECTING) {
uint32_t t = (uint32_t)(esp_timer_get_time() / 500000ULL);
if (t % 2) sys_gfx_draw_pixel(45, 3, COLOR_WHITE);
}
if (status->ble_state != BLE_STATE_OFF) {
sys_gfx_draw_pixel(52, 1, COLOR_WHITE);
sys_gfx_draw_pixel(52, 3, COLOR_WHITE);
sys_gfx_draw_pixel(52, 5, COLOR_WHITE);
}
sys_gfx_draw_rect(58, 1, 11, 5, COLOR_WHITE);
sys_gfx_draw_pixel(69, 2, COLOR_WHITE);
sys_gfx_draw_pixel(69, 3, COLOR_WHITE);
if (status->is_charging) {
sys_gfx_draw_pixel(63, 2, COLOR_WHITE);
sys_gfx_draw_pixel(62, 3, COLOR_WHITE);
sys_gfx_draw_pixel(64, 3, COLOR_WHITE);
sys_gfx_draw_pixel(63, 4, COLOR_WHITE);
} else {
uint8_t fill_w = (status->battery_pct * 7) / 100;
if (fill_w > 7) fill_w = 7;
sys_gfx_fill_rect(60, 2, fill_w, 3, COLOR_WHITE);
}
}
/* UI Framework: Action Bar (rows 32-39) */
void sys_gfx_draw_action_bar(const char *left_hint, const char *right_hint)
{
sys_gfx_draw_line(0, 32, OLED_WIDTH - 1, 32, COLOR_WHITE);
if (left_hint) {
sys_gfx_draw_str_3x5(1, 34, left_hint, COLOR_WHITE);
}
if (right_hint) {
int16_t str_len = (int16_t)strlen(right_hint);
/* 3x5 font: 3px wide + 1px gap per char, no trailing gap on last char */
int16_t str_w = (str_len > 0) ? ((str_len - 1) * 4 + 3) : 0;
int16_t rx = OLED_WIDTH - 1 - str_w;
sys_gfx_draw_str_3x5(rx, 34, right_hint, COLOR_WHITE);
}
}
/* UI Framework: Scrollable List Menu */
void sys_gfx_draw_menu(int16_t y_start, const char *items[], size_t count, size_t selected_idx)
{
if (!items || count == 0) return;
size_t visible_count = 3;
size_t top_idx = 0;
if (selected_idx >= visible_count) {
top_idx = selected_idx - visible_count + 1;
}
for (size_t i = 0; i < visible_count && (top_idx + i) < count; i++) {
size_t item_idx = top_idx + i;
int16_t y = y_start + (i * 9);
if (item_idx == selected_idx) {
sys_gfx_fill_rect(0, y - 1, OLED_WIDTH - 3, 9, COLOR_WHITE);
sys_gfx_draw_char_3x5(2, y + 1, '>', COLOR_BLACK);
sys_gfx_draw_str_5x7(8, y, items[item_idx], COLOR_BLACK);
} else {
sys_gfx_draw_str_5x7(8, y, items[item_idx], COLOR_WHITE);
}
}
if (count > visible_count) {
sys_gfx_draw_line(OLED_WIDTH - 1, y_start, OLED_WIDTH - 1, y_start + 26, COLOR_WHITE);
int16_t thumb_h = 27 / count;
if (thumb_h < 3) thumb_h = 3;
int16_t thumb_y = y_start + (selected_idx * (27 - thumb_h)) / (count - 1);
sys_gfx_fill_rect(OLED_WIDTH - 2, thumb_y, 2, thumb_h, COLOR_WHITE);
}
}
/* UI Framework: Progress Bar */
void sys_gfx_draw_progress_bar(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t pct)
{
if (pct > 100) pct = 100;
sys_gfx_draw_rect(x, y, w, h, COLOR_WHITE);
int16_t inner_w = ((w - 2) * pct) / 100;
if (inner_w > 0 && h > 2) {
sys_gfx_fill_rect(x + 1, y + 1, inner_w, h - 2, COLOR_WHITE);
}
}
/* UI Framework: Sparkline */
void sys_gfx_draw_sparkline(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *data, size_t count, uint8_t max_val)
{
if (!data || count < 2 || max_val == 0) return;
sys_gfx_draw_rect(x, y, w, h, COLOR_WHITE);
size_t points = (count < (size_t)(w - 2)) ? count : (size_t)(w - 2);
int16_t prev_px = x + 1;
int16_t prev_py = y + h - 2 - ((data[0] * (h - 3)) / max_val);
for (size_t i = 1; i < points; i++) {
int16_t px = x + 1 + i;
int16_t val = (data[i] > max_val) ? max_val : data[i];
int16_t py = y + h - 2 - ((val * (h - 3)) / max_val);
sys_gfx_draw_line(prev_px, prev_py, px, py, COLOR_WHITE);
prev_px = px;
prev_py = py;
}
}
/* Toast Notifications */
void sys_gfx_show_toast(const char *msg, uint32_t duration_ms)
{
if (!msg) return;
snprintf(s_toast_msg, sizeof(s_toast_msg), "%s", msg);
s_toast_expire_us = esp_timer_get_time() + ((uint64_t)duration_ms * 1000ULL);
}
void sys_gfx_render_toast_if_active(void)
{
if (s_toast_expire_us == 0) return;
uint64_t now = esp_timer_get_time();
if (now > s_toast_expire_us) {
s_toast_expire_us = 0;
return;
}
int16_t box_h = 13;
int16_t box_y = (OLED_HEIGHT - box_h) / 2;
sys_gfx_fill_rect(2, box_y, OLED_WIDTH - 4, box_h, COLOR_BLACK);
sys_gfx_draw_rect(2, box_y, OLED_WIDTH - 4, box_h, COLOR_WHITE);
sys_gfx_draw_str_5x7(6, box_y + 3, s_toast_msg, COLOR_WHITE);
}