custom fonts and further UI reworks

This commit is contained in:
Mikkeli Matlock
2026-02-15 18:13:53 +09:00
parent 9ca0227214
commit 12dbbd8942
17 changed files with 7286 additions and 311 deletions

View File

@@ -1,4 +1,4 @@
idf_component_register(
SRCS "dashboard_ui.c"
REQUIRES lvgl__lvgl ws_client
REQUIRES lvgl__lvgl ws_client user_fonts
INCLUDE_DIRS "./")

View File

@@ -1,23 +1,31 @@
#include "dashboard_ui.h"
#include "user_fonts.h"
#include <stdio.h>
#include <string.h>
/* ---------- Layout constants ---------- */
#define SCREEN_W 400
#define SCREEN_H 300
#define TOP_BAR_H 24
#define STATS_H 80
#define MID_H 140
#define BOT_H 32
#define SCREEN_W 400
#define SCREEN_H 300
#define TOP_BAR_H 24
#define TIME_BAR_H 50
#define BOT_H 24
#define MAIN_Y (TOP_BAR_H + TIME_BAR_H) /* 74 */
#define MAIN_H (SCREEN_H - MAIN_Y - BOT_H) /* 202 */
#define LEFT_COL_W 200
#define RIGHT_COL_X 200
/* ---------- Static widget handles ---------- */
/* Top bar */
static lv_obj_t *lbl_ip;
static lv_obj_t *lbl_batt;
static lv_obj_t *lbl_time;
static lv_obj_t *bar_batt_top;
static lv_obj_t *lbl_ws;
/* Time bar */
static lv_obj_t *lbl_clock;
static lv_obj_t *lbl_date;
/* Pi stats bars + labels */
static lv_obj_t *bar_cpu;
static lv_obj_t *lbl_cpu_val;
@@ -29,19 +37,24 @@ static lv_obj_t *lbl_cpu_temp;
/* Services table */
static lv_obj_t *tbl_services;
static int s_service_count;
/* Local sensors */
static lv_obj_t *lbl_room_temp;
static lv_obj_t *lbl_room_humi;
static lv_obj_t *lbl_local_batt;
static lv_obj_t *lbl_uptime;
/* Network */
static lv_obj_t *lbl_net;
/* ---------- Style ---------- */
/* Auto-scroll timer */
static lv_timer_t *s_scroll_timer;
/* ---------- Styles ---------- */
static lv_style_t style_bar_bg;
static lv_style_t style_bar_ind;
static lv_style_t style_bar_batt_bg;
static lv_style_t style_bar_batt_ind;
static void init_styles(void)
{
@@ -58,6 +71,19 @@ static void init_styles(void)
lv_style_set_bg_color(&style_bar_ind, lv_color_black());
lv_style_set_bg_opa(&style_bar_ind, LV_OPA_COVER);
lv_style_set_radius(&style_bar_ind, 0);
/* Battery bar on black top bar: inverted colors */
lv_style_init(&style_bar_batt_bg);
lv_style_set_bg_color(&style_bar_batt_bg, lv_color_black());
lv_style_set_bg_opa(&style_bar_batt_bg, LV_OPA_COVER);
lv_style_set_border_color(&style_bar_batt_bg, lv_color_white());
lv_style_set_border_width(&style_bar_batt_bg, 1);
lv_style_set_radius(&style_bar_batt_bg, 0);
lv_style_init(&style_bar_batt_ind);
lv_style_set_bg_color(&style_bar_batt_ind, lv_color_white());
lv_style_set_bg_opa(&style_bar_batt_ind, LV_OPA_COVER);
lv_style_set_radius(&style_bar_batt_ind, 0);
}
static lv_obj_t *create_bar(lv_obj_t *parent, int x, int y, int w, int h)
@@ -82,7 +108,29 @@ static lv_obj_t *create_label(lv_obj_t *parent, int x, int y, const lv_font_t *f
return lbl;
}
/* ---------- Create UI ---------- */
/* ---------- Auto-scroll callback ---------- */
static void scroll_timer_cb(lv_timer_t *timer)
{
(void)timer;
if (s_service_count <= 4) {
lv_obj_scroll_to_y(tbl_services, 0, LV_ANIM_OFF);
return;
}
lv_coord_t cur_y = lv_obj_get_scroll_y(tbl_services);
/* Each row is ~16px (12px font + 2px pad top + 2px pad bot) */
lv_coord_t row_h = 16;
lv_coord_t max_scroll = (s_service_count - 4) * row_h;
if (cur_y >= max_scroll) {
lv_obj_scroll_to_y(tbl_services, 0, LV_ANIM_ON);
} else {
lv_obj_scroll_to_y(tbl_services, cur_y + row_h, LV_ANIM_ON);
}
}
/* ---------- Create UI sections ---------- */
static void create_top_bar(lv_obj_t *parent)
{
@@ -96,97 +144,134 @@ static void create_top_bar(lv_obj_t *parent)
lv_obj_set_style_pad_all(bar_cont, 2, 0);
lv_obj_clear_flag(bar_cont, LV_OBJ_FLAG_SCROLLABLE);
/* IP address — left */
lbl_ip = lv_label_create(bar_cont);
lv_obj_set_style_text_color(lbl_ip, lv_color_white(), 0);
lv_obj_set_style_text_font(lbl_ip, &lv_font_montserrat_12, 0);
lv_obj_set_style_text_font(lbl_ip, &InziuIosevka_Slab_CC_12px, 0);
lv_obj_align(lbl_ip, LV_ALIGN_LEFT_MID, 2, 0);
lv_label_set_text(lbl_ip, "N/A");
lbl_batt = lv_label_create(bar_cont);
lv_obj_set_style_text_color(lbl_batt, lv_color_white(), 0);
lv_obj_set_style_text_font(lbl_batt, &lv_font_montserrat_12, 0);
lv_obj_align(lbl_batt, LV_ALIGN_LEFT_MID, 120, 0);
lv_label_set_text(lbl_batt, "Batt:--%");
lbl_time = lv_label_create(bar_cont);
lv_obj_set_style_text_color(lbl_time, lv_color_white(), 0);
lv_obj_set_style_text_font(lbl_time, &lv_font_montserrat_12, 0);
lv_obj_align(lbl_time, LV_ALIGN_LEFT_MID, 220, 0);
lv_label_set_text(lbl_time, "--:--");
/* WS status — center-left */
lbl_ws = lv_label_create(bar_cont);
lv_obj_set_style_text_color(lbl_ws, lv_color_white(), 0);
lv_obj_set_style_text_font(lbl_ws, &lv_font_montserrat_12, 0);
lv_obj_align(lbl_ws, LV_ALIGN_RIGHT_MID, -2, 0);
lv_obj_set_style_text_font(lbl_ws, &InziuIosevka_Slab_CC_12px, 0);
lv_obj_align(lbl_ws, LV_ALIGN_LEFT_MID, 140, 0);
lv_label_set_text(lbl_ws, "WS:---");
/* Battery bar (24x10) — right */
bar_batt_top = lv_bar_create(bar_cont);
lv_obj_set_size(bar_batt_top, 24, 10);
lv_obj_align(bar_batt_top, LV_ALIGN_RIGHT_MID, -40, 0);
lv_bar_set_range(bar_batt_top, 0, 100);
lv_bar_set_value(bar_batt_top, 0, LV_ANIM_OFF);
lv_obj_add_style(bar_batt_top, &style_bar_batt_bg, LV_PART_MAIN);
lv_obj_add_style(bar_batt_top, &style_bar_batt_ind, LV_PART_INDICATOR);
/* Battery text — right of bar */
lbl_batt = lv_label_create(bar_cont);
lv_obj_set_style_text_color(lbl_batt, lv_color_white(), 0);
lv_obj_set_style_text_font(lbl_batt, &InziuIosevka_Slab_CC_12px, 0);
lv_obj_align(lbl_batt, LV_ALIGN_RIGHT_MID, -2, 0);
lv_label_set_text(lbl_batt, "--%");
}
static void create_stats_section(lv_obj_t *parent)
static void create_time_bar(lv_obj_t *parent)
{
int y0 = TOP_BAR_H + 2;
int col_w = 80;
int bar_w = 50;
int bar_h = 40;
lv_obj_t *bar_cont = lv_obj_create(parent);
lv_obj_set_pos(bar_cont, 0, TOP_BAR_H);
lv_obj_set_size(bar_cont, SCREEN_W, TIME_BAR_H);
lv_obj_set_style_bg_color(bar_cont, lv_color_white(), 0);
lv_obj_set_style_bg_opa(bar_cont, LV_OPA_COVER, 0);
lv_obj_set_style_border_color(bar_cont, lv_color_black(), 0);
lv_obj_set_style_border_width(bar_cont, 1, 0);
lv_obj_set_style_border_side(bar_cont, LV_BORDER_SIDE_BOTTOM, 0);
lv_obj_set_style_radius(bar_cont, 0, 0);
lv_obj_set_style_pad_all(bar_cont, 0, 0);
lv_obj_clear_flag(bar_cont, LV_OBJ_FLAG_SCROLLABLE);
/* Section header */
create_label(parent, 4, y0, &lv_font_montserrat_12, "PI SERVER STATS");
/* Clock HH:MM:SS — left */
lbl_clock = lv_label_create(bar_cont);
lv_obj_set_style_text_font(lbl_clock, &DSEG14C_BI_32px, 0);
lv_obj_set_style_text_color(lbl_clock, lv_color_black(), 0);
lv_obj_align(lbl_clock, LV_ALIGN_LEFT_MID, 10, 0);
lv_label_set_text(lbl_clock, "--:--:--");
y0 += 14;
/* CPU */
create_label(parent, 10, y0, &lv_font_montserrat_12, "CPU");
bar_cpu = create_bar(parent, 10, y0 + 14, bar_w, bar_h);
lbl_cpu_val = create_label(parent, 10, y0 + 56, &lv_font_montserrat_14, "--%");
/* RAM */
create_label(parent, 10 + col_w, y0, &lv_font_montserrat_12, "RAM");
bar_ram = create_bar(parent, 10 + col_w, y0 + 14, bar_w, bar_h);
lbl_ram_val = create_label(parent, 10 + col_w, y0 + 56, &lv_font_montserrat_14, "--%");
/* DISK */
create_label(parent, 10 + col_w * 2, y0, &lv_font_montserrat_12, "DISK");
bar_disk = create_bar(parent, 10 + col_w * 2, y0 + 14, bar_w, bar_h);
lbl_disk_val = create_label(parent, 10 + col_w * 2, y0 + 56, &lv_font_montserrat_14, "--%");
/* CPU TEMP - no bar, just big value */
create_label(parent, 10 + col_w * 3, y0, &lv_font_montserrat_12, "TEMP");
lbl_cpu_temp = create_label(parent, 10 + col_w * 3, y0 + 24, &lv_font_montserrat_20, "--C");
/* Date YYYY/MM/DD DAY — right */
lbl_date = lv_label_create(bar_cont);
lv_obj_set_style_text_font(lbl_date, &InziuIosevka_Slab_CC_20px, 0);
lv_obj_set_style_text_color(lbl_date, lv_color_black(), 0);
lv_obj_align(lbl_date, LV_ALIGN_RIGHT_MID, -10, 0);
lv_label_set_text(lbl_date, "----/--/-- ---");
}
static void create_mid_section(lv_obj_t *parent)
static void create_main_section(lv_obj_t *parent)
{
int y0 = TOP_BAR_H + STATS_H + 4;
/* --- Left column: Services --- */
create_label(parent, 4, y0, &lv_font_montserrat_12, "SERVICES");
/* === Left column: Services === */
create_label(parent, 4, MAIN_Y + 2, &InziuIosevka_Slab_CC_12px, "SERVICES");
tbl_services = lv_table_create(parent);
lv_obj_set_pos(tbl_services, 4, y0 + 14);
lv_obj_set_size(tbl_services, 195, 110);
lv_obj_set_pos(tbl_services, 4, MAIN_Y + 16);
lv_obj_set_size(tbl_services, 190, 180);
lv_table_set_col_cnt(tbl_services, 2);
lv_table_set_col_width(tbl_services, 0, 110);
lv_table_set_col_width(tbl_services, 1, 70);
lv_obj_set_style_text_font(tbl_services, &lv_font_montserrat_12, 0);
lv_table_set_col_width(tbl_services, 1, 65);
lv_obj_set_style_text_font(tbl_services, &InziuIosevka_Slab_CC_12px, 0);
lv_obj_set_style_border_color(tbl_services, lv_color_black(), 0);
lv_obj_set_style_border_width(tbl_services, 1, 0);
lv_obj_set_style_pad_ver(tbl_services, 2, LV_PART_ITEMS);
lv_obj_set_style_pad_hor(tbl_services, 4, LV_PART_ITEMS);
lv_obj_clear_flag(tbl_services, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(tbl_services, LV_OBJ_FLAG_SCROLLABLE);
/* Pre-fill with empty rows */
for (int i = 0; i < WS_MAX_SERVICES; i++) {
lv_table_set_cell_value(tbl_services, i, 0, "---");
lv_table_set_cell_value(tbl_services, i, 1, "---");
}
/* --- Right column: Local sensors --- */
int rx = 205;
create_label(parent, rx, y0, &lv_font_montserrat_12, "LOCAL SENSORS");
/* Auto-scroll timer: 3 second period */
s_scroll_timer = lv_timer_create(scroll_timer_cb, 3000, NULL);
lbl_room_temp = create_label(parent, rx, y0 + 18, &lv_font_montserrat_14, "Room: --.-C");
lbl_room_humi = create_label(parent, rx, y0 + 38, &lv_font_montserrat_14, "Humi: --%");
lbl_local_batt = create_label(parent, rx, y0 + 58, &lv_font_montserrat_14, "Batt: --%");
lbl_uptime = create_label(parent, rx, y0 + 78, &lv_font_montserrat_14, "Uptime: --h");
/* === Right column: Pi Vitals + Local Sensors === */
int rx = RIGHT_COL_X;
int row_h = 18; /* vertical spacing per stat row */
int lbl_w = 36; /* width reserved for "CPU " etc */
int bar_w = 82;
int bar_h = 12;
int val_x = rx + 4 + lbl_w + bar_w + 4; /* value label after bar */
int temp_x = rx + 156; /* TEMP column, right of value labels */
/* Pi Vitals header */
create_label(parent, rx + 4, MAIN_Y + 2, &InziuIosevka_Slab_CC_12px, "PI VITALS");
int ry = MAIN_Y + 18;
/* CPU [========] 12% TEMP */
create_label(parent, rx + 4, ry, &InziuIosevka_Slab_CC_12px, "CPU");
bar_cpu = create_bar(parent, rx + 4 + lbl_w, ry, bar_w, bar_h);
lbl_cpu_val = create_label(parent, val_x, ry, &InziuIosevka_Slab_CC_12px, "--%");
create_label(parent, temp_x, ry, &InziuIosevka_Slab_CC_12px, "TEMP");
/* RAM [========] 58% 45 C */
ry += row_h;
create_label(parent, rx + 4, ry, &InziuIosevka_Slab_CC_12px, "RAM");
bar_ram = create_bar(parent, rx + 4 + lbl_w, ry, bar_w, bar_h);
lbl_ram_val = create_label(parent, val_x, ry, &InziuIosevka_Slab_CC_12px, "--%");
lbl_cpu_temp = create_label(parent, temp_x, ry, &InziuIosevka_Slab_CC_20px, "-- C");
/* DISK [========] 44% */
ry += row_h;
create_label(parent, rx + 4, ry, &InziuIosevka_Slab_CC_12px, "DISK");
bar_disk = create_bar(parent, rx + 4 + lbl_w, ry, bar_w, bar_h);
lbl_disk_val = create_label(parent, val_x, ry, &InziuIosevka_Slab_CC_12px, "--%");
/* uptime */
ry += row_h;
lbl_uptime = create_label(parent, rx + 4, ry, &InziuIosevka_Slab_CC_12px, "Uptime: --h");
/* === Local Sensors (below pi vitals) === */
int sy = ry + row_h + 4;
create_label(parent, rx + 4, sy, &InziuIosevka_Slab_CC_12px, "LOCAL SENSORS");
lbl_room_temp = create_label(parent, rx + 4, sy + 16, &InziuIosevka_Slab_CC_16px, "Room: --.-C");
lbl_room_humi = create_label(parent, rx + 4, sy + 42, &InziuIosevka_Slab_CC_16px, "Humi: --%");
}
static void create_bottom_bar(lv_obj_t *parent)
@@ -206,7 +291,7 @@ static void create_bottom_bar(lv_obj_t *parent)
lv_obj_clear_flag(bot_cont, LV_OBJ_FLAG_SCROLLABLE);
lbl_net = lv_label_create(bot_cont);
lv_obj_set_style_text_font(lbl_net, &lv_font_montserrat_12, 0);
lv_obj_set_style_text_font(lbl_net, &InziuIosevka_Slab_CC_12px, 0);
lv_obj_set_style_text_color(lbl_net, lv_color_black(), 0);
lv_obj_align(lbl_net, LV_ALIGN_LEFT_MID, 0, 0);
lv_label_set_text(lbl_net, "NETWORK RX: ---- kbps TX: ---- kbps");
@@ -222,8 +307,8 @@ void dashboard_ui_create(void)
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
create_top_bar(scr);
create_stats_section(scr);
create_mid_section(scr);
create_time_bar(scr);
create_main_section(scr);
create_bottom_bar(scr);
}
@@ -249,10 +334,11 @@ void dashboard_ui_update_stats(const pi_stats_t *stats)
lv_label_set_text(lbl_disk_val, buf);
/* CPU temp */
snprintf(buf, sizeof(buf), "%.0fC", stats->cpu_temp);
snprintf(buf, sizeof(buf), "%.0f C", stats->cpu_temp);
lv_label_set_text(lbl_cpu_temp, buf);
/* Services table */
s_service_count = stats->service_count;
for (int i = 0; i < stats->service_count && i < WS_MAX_SERVICES; i++) {
lv_table_set_cell_value(tbl_services, i, 0, stats->services[i].name);
lv_table_set_cell_value(tbl_services, i, 1,
@@ -285,19 +371,23 @@ void dashboard_ui_update_local(float temp, float humidity, uint8_t battery)
snprintf(buf, sizeof(buf), "Humi: %.0f%%", humidity);
lv_label_set_text(lbl_room_humi, buf);
snprintf(buf, sizeof(buf), "Batt: %d%%", battery);
lv_label_set_text(lbl_local_batt, buf);
/* Also update top bar battery */
snprintf(buf, sizeof(buf), "Batt:%d%%", battery);
/* Update top bar battery text + bar */
snprintf(buf, sizeof(buf), "%d%%", battery);
lv_label_set_text(lbl_batt, buf);
lv_bar_set_value(bar_batt_top, battery, LV_ANIM_OFF);
}
void dashboard_ui_update_time(int h, int m, int s)
void dashboard_ui_update_time(int h, int m, int s, int year, int month, int day, int weekday)
{
char buf[16];
snprintf(buf, sizeof(buf), "%02d:%02d", h, m);
lv_label_set_text(lbl_time, buf);
static const char *day_names[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
char buf[32];
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", h, m, s);
lv_label_set_text(lbl_clock, buf);
const char *wd = (weekday >= 0 && weekday <= 6) ? day_names[weekday] : "---";
snprintf(buf, sizeof(buf), "%04d/%02d/%02d %s", year, month, day, wd);
lv_label_set_text(lbl_date, buf);
}
void dashboard_ui_update_connection(ws_state_t ws_state, bool wifi_connected, const char *ip_str)

View File

@@ -24,9 +24,10 @@ void dashboard_ui_update_stats(const pi_stats_t *stats);
void dashboard_ui_update_local(float temp, float humidity, uint8_t battery);
/**
* Update time display. LVGL lock must be held by caller.
* Update clock and date display. LVGL lock must be held by caller.
* weekday: 0=Sun, 1=Mon, ..., 6=Sat
*/
void dashboard_ui_update_time(int h, int m, int s);
void dashboard_ui_update_time(int h, int m, int s, int year, int month, int day, int weekday);
/**
* Update connection status indicators. LVGL lock must be held by caller.