ESP32/main/ui_screen.c
2026-06-24 10:35:22 +08:00

959 lines
34 KiB
C

#include "ui_screen.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lvgl.h"
#define CPU_HISTORY_POINTS 64
#define CPU_CHART_W 184
#define CPU_CHART_H 78
#define CPU_TOP_CORE_COUNT 4
#define HALO_LABEL_COUNT 8
#define DISK_ROW_COUNT 4
#define CODEX_DAILY_BAR_W 8
#define CODEX_DAILY_BAR_GAP 5
#define CODEX_DAILY_BAR_BASE_Y 292
#define CODEX_DAILY_BAR_MAX_H 46
#define ZHIPU_DAILY_BAR_BASE_X 210
typedef enum {
CONN_USB = 0,
CONN_WIFI,
CONN_BLE,
} connection_slot_t;
static lv_obj_t *s_status_label;
static lv_obj_t *s_battery_label;
static lv_obj_t *s_conn_label;
static lv_obj_t *s_env_label;
static lv_obj_t *s_cpu_value_label;
static lv_obj_t *s_cpu_freq_label;
static lv_obj_t *s_cpu_core_type_labels[CPU_TOP_CORE_COUNT];
static lv_obj_t *s_cpu_core_freq_labels[CPU_TOP_CORE_COUNT];
static lv_obj_t *s_cpu_value_halo[HALO_LABEL_COUNT];
static lv_obj_t *s_cpu_freq_halo[HALO_LABEL_COUNT];
static lv_obj_t *s_cpu_line;
static lv_point_precise_t s_cpu_points[CPU_HISTORY_POINTS];
static int s_cpu_history[CPU_HISTORY_POINTS];
static lv_obj_t *s_memory_value_label;
static lv_obj_t *s_memory_value_halo[HALO_LABEL_COUNT];
static lv_obj_t *s_memory_detail_label;
static lv_obj_t *s_memory_bar;
static lv_obj_t *s_disk_name_labels[DISK_ROW_COUNT];
static lv_obj_t *s_disk_rate_labels[DISK_ROW_COUNT];
static lv_obj_t *s_disk_bars[DISK_ROW_COUNT];
static lv_obj_t *s_net_rx_label;
static lv_obj_t *s_net_tx_label;
static lv_obj_t *s_codex_5h_label;
static lv_obj_t *s_codex_week_label;
static lv_obj_t *s_codex_5h_value_label;
static lv_obj_t *s_codex_week_value_label;
static lv_obj_t *s_codex_5h_reset_label;
static lv_obj_t *s_codex_week_reset_label;
static lv_obj_t *s_codex_today_label;
static lv_obj_t *s_codex_5h_bar;
static lv_obj_t *s_codex_week_bar;
static lv_obj_t *s_codex_daily_bars[UI_CODEX_DAILY_BAR_COUNT];
static lv_obj_t *s_zhipu_today_label;
static lv_obj_t *s_zhipu_5h_label;
static lv_obj_t *s_zhipu_week_label;
static lv_obj_t *s_zhipu_5h_value_label;
static lv_obj_t *s_zhipu_week_value_label;
static lv_obj_t *s_zhipu_5h_reset_label;
static lv_obj_t *s_zhipu_week_reset_label;
static lv_obj_t *s_zhipu_5h_bar;
static lv_obj_t *s_zhipu_week_bar;
static lv_obj_t *s_zhipu_daily_bars[UI_ZHIPU_DAILY_BAR_COUNT];
static int s_age_seconds;
static connection_slot_t s_active_connection = CONN_USB;
typedef struct {
int x;
int y;
} halo_offset_t;
static const halo_offset_t s_halo_offsets[HALO_LABEL_COUNT] = {
{ -1, 0 },
{ 1, 0 },
{ 0, -1 },
{ 0, 1 },
{ -1, -1 },
{ 1, -1 },
{ -1, 1 },
{ 1, 1 },
};
static const lv_point_precise_t s_net_down_icon_points[] = {
{ 6, 0 },
{ 6, 12 },
{ 2, 8 },
{ 6, 12 },
{ 10, 8 },
};
static const lv_point_precise_t s_net_up_icon_points[] = {
{ 6, 12 },
{ 6, 0 },
{ 2, 4 },
{ 6, 0 },
{ 10, 4 },
};
static lv_color_t ink(void)
{
return lv_color_black();
}
static lv_color_t paper(void)
{
return lv_color_white();
}
static lv_color_t soft_fill(void)
{
return lv_color_hex(0xe6e6e6);
}
static int clamp_percent(int value)
{
if (value < 0) {
return 0;
}
if (value > 100) {
return 100;
}
return value;
}
static void format_percent_compact(int percent, char *out, size_t out_size);
static lv_obj_t *make_colored_label(lv_obj_t *parent, const char *text, const lv_font_t *font, lv_color_t color)
{
lv_obj_t *label = lv_label_create(parent);
lv_label_set_text(label, text);
lv_label_set_long_mode(label, LV_LABEL_LONG_DOT);
lv_obj_set_style_text_font(label, font, 0);
lv_obj_set_style_text_color(label, color, 0);
lv_obj_set_style_pad_all(label, 0, 0);
return label;
}
static lv_obj_t *make_label(lv_obj_t *parent, const char *text, const lv_font_t *font)
{
return make_colored_label(parent, text, font, ink());
}
static void make_label_halo(lv_obj_t *parent,
lv_obj_t *halos[HALO_LABEL_COUNT],
const char *text,
const lv_font_t *font,
int width,
lv_align_t align,
int x,
int y,
int spread,
lv_text_align_t text_align)
{
for (int i = 0; i < HALO_LABEL_COUNT; i++) {
halos[i] = make_colored_label(parent, text, font, paper());
lv_obj_set_width(halos[i], width);
lv_obj_set_style_text_align(halos[i], text_align, 0);
lv_obj_align(halos[i], align, x + s_halo_offsets[i].x * spread, y + s_halo_offsets[i].y * spread);
}
}
static void set_label_with_halo(lv_obj_t *label, lv_obj_t *halos[HALO_LABEL_COUNT], const char *text)
{
for (int i = 0; i < HALO_LABEL_COUNT; i++) {
if (halos[i] != NULL) {
lv_label_set_text(halos[i], text);
}
}
lv_label_set_text(label, text);
}
static void set_bar_percent(lv_obj_t *bar, int percent)
{
if (bar == NULL) {
return;
}
lv_bar_set_value(bar, percent >= 0 ? clamp_percent(percent) : 0, LV_ANIM_OFF);
}
static int remaining_percent_from_used(int used_percent)
{
if (used_percent < 0) {
return -1;
}
return 100 - clamp_percent(used_percent);
}
static void set_remaining_bar(lv_obj_t *bar, lv_obj_t *label, int used_percent)
{
int remaining = remaining_percent_from_used(used_percent);
char value[12];
format_percent_compact(remaining, value, sizeof(value));
lv_label_set_text(label, value);
set_bar_percent(bar, remaining);
}
static lv_obj_t *make_rule(lv_obj_t *parent, int x, int y, int w, int h)
{
lv_obj_t *rule = lv_obj_create(parent);
lv_obj_set_size(rule, w, h);
lv_obj_align(rule, LV_ALIGN_TOP_LEFT, x, y);
lv_obj_set_style_bg_color(rule, ink(), 0);
lv_obj_set_style_bg_opa(rule, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(rule, 0, 0);
lv_obj_set_style_radius(rule, 0, 0);
lv_obj_set_style_pad_all(rule, 0, 0);
lv_obj_clear_flag(rule, LV_OBJ_FLAG_SCROLLABLE);
return rule;
}
static lv_obj_t *make_bar(lv_obj_t *parent, int w, int h)
{
lv_obj_t *bar = lv_bar_create(parent);
lv_obj_set_size(bar, w, h);
lv_bar_set_range(bar, 0, 100);
lv_bar_set_value(bar, 0, LV_ANIM_OFF);
lv_obj_set_style_bg_color(bar, paper(), 0);
lv_obj_set_style_border_color(bar, ink(), 0);
lv_obj_set_style_border_width(bar, 1, 0);
lv_obj_set_style_radius(bar, 1, 0);
lv_obj_set_style_bg_color(bar, ink(), LV_PART_INDICATOR);
lv_obj_set_style_radius(bar, 1, LV_PART_INDICATOR);
return bar;
}
static lv_obj_t *make_quota_bar(lv_obj_t *parent, int w, int h)
{
lv_obj_t *bar = make_bar(parent, w, h);
lv_obj_set_style_bg_color(bar, soft_fill(), 0);
lv_obj_set_style_border_color(bar, ink(), 0);
lv_obj_set_style_border_width(bar, 1, 0);
lv_obj_set_style_radius(bar, 1, 0);
lv_obj_set_style_pad_all(bar, 0, 0);
lv_obj_set_style_radius(bar, 1, LV_PART_INDICATOR);
return bar;
}
static lv_obj_t *make_net_arrow(lv_obj_t *parent, const lv_point_precise_t *points, uint32_t count)
{
lv_obj_t *line = lv_line_create(parent);
lv_obj_set_size(line, 12, 14);
lv_line_set_points(line, points, count);
lv_obj_set_style_line_color(line, ink(), 0);
lv_obj_set_style_line_width(line, 2, 0);
lv_obj_set_style_pad_all(line, 0, 0);
lv_obj_clear_flag(line, LV_OBJ_FLAG_SCROLLABLE);
return line;
}
static void format_capacity(int mb, char *out, size_t out_size)
{
if (mb <= 0) {
snprintf(out, out_size, "--");
return;
}
if (mb >= 1024) {
int gb10 = (mb * 10 + 512) / 1024;
snprintf(out, out_size, "%d.%dGB", gb10 / 10, gb10 % 10);
} else {
snprintf(out, out_size, "%dMB", mb);
}
}
static void format_rate_short(int kbps, char *out, size_t out_size)
{
if (kbps < 0) {
snprintf(out, out_size, "--");
return;
}
if (kbps >= 1024 * 1024) {
int gb10 = (kbps * 10 + (1024 * 512)) / (1024 * 1024);
if (gb10 >= 100) {
snprintf(out, out_size, "%dG", gb10 / 10);
} else {
snprintf(out, out_size, "%d.%dG", gb10 / 10, gb10 % 10);
}
} else if (kbps >= 1000) {
int mb10 = (kbps * 10 + 512) / 1024;
if (mb10 >= 100) {
snprintf(out, out_size, "%dM", mb10 / 10);
} else {
snprintf(out, out_size, "%d.%dM", mb10 / 10, mb10 % 10);
}
} else {
snprintf(out, out_size, "%dK", kbps);
}
}
static void format_reset_mins(int mins, char *out, size_t out_size)
{
if (mins < 0) {
snprintf(out, out_size, "--");
} else if (mins >= 1440) {
snprintf(out, out_size, "%dD", mins / 1440);
} else if (mins >= 60) {
snprintf(out, out_size, "%dH", mins / 60);
} else {
snprintf(out, out_size, "%dM", mins);
}
}
static void format_percent_compact(int percent, char *out, size_t out_size)
{
if (percent < 0) {
snprintf(out, out_size, "--");
} else {
snprintf(out, out_size, "%d%%", clamp_percent(percent));
}
}
static void format_tokens_m10(int tokens_m10, char *out, size_t out_size)
{
if (tokens_m10 < 0) {
snprintf(out, out_size, "--");
} else if (tokens_m10 >= 10000) {
snprintf(out, out_size, "%d.%dB", tokens_m10 / 10000, (tokens_m10 % 10000) / 1000);
} else if (tokens_m10 >= 1000) {
snprintf(out, out_size, "%dM", (tokens_m10 + 5) / 10);
} else {
snprintf(out, out_size, "%d.%dM", tokens_m10 / 10, tokens_m10 % 10);
}
}
static const char *zhipu_status_text(int status)
{
switch (status) {
case 1:
return "KEY OK";
case 2:
return "ERR";
default:
return "WAIT";
}
}
static const char *connection_symbol(connection_slot_t active)
{
switch (active) {
case CONN_BLE:
return LV_SYMBOL_BLUETOOTH;
case CONN_WIFI:
return LV_SYMBOL_WIFI;
case CONN_USB:
default:
return LV_SYMBOL_USB;
}
}
static void set_connection(connection_slot_t active)
{
s_active_connection = active;
if (s_conn_label != NULL) {
lv_label_set_text(s_conn_label, connection_symbol(active));
}
}
static connection_slot_t connection_from_source(const char *source)
{
if (source == NULL) {
return CONN_USB;
}
if (strstr(source, "BLE") != NULL) {
return CONN_BLE;
}
if (strstr(source, "WIFI") != NULL || strstr(source, "AP") != NULL) {
return CONN_WIFI;
}
return CONN_USB;
}
static void set_history_curve(int *history, lv_point_precise_t *points, int count,
int chart_w, int chart_h, int percent, lv_obj_t *line)
{
for (int i = 0; i < count - 1; i++) {
history[i] = history[i + 1];
}
history[count - 1] = clamp_percent(percent);
for (int i = 0; i < count; i++) {
points[i].x = (i * chart_w) / (count - 1);
points[i].y = chart_h - ((history[i] * chart_h) / 100);
}
lv_line_set_points_mutable(line, points, count);
lv_obj_invalidate(line);
}
static void set_cpu_curve(int cpu_percent)
{
set_history_curve(s_cpu_history, s_cpu_points, CPU_HISTORY_POINTS,
CPU_CHART_W, CPU_CHART_H, cpu_percent, s_cpu_line);
}
static int parse_int_field(char *field, int fallback)
{
if (field == NULL || field[0] == '\0') {
return fallback;
}
char *end = NULL;
long value = strtol(field, &end, 10);
if (end == field) {
return fallback;
}
return (int)value;
}
static int parse_disk_token(const char *token, char *name, size_t name_size,
int *percent, int *read_kbps, int *write_kbps)
{
char buffer[40];
snprintf(buffer, sizeof(buffer), "%s", token);
char *cursor = buffer;
char *disk_name = strsep(&cursor, ":");
char *usage = strsep(&cursor, ":");
char *read_rate = strsep(&cursor, ":");
char *write_rate = strsep(&cursor, ":");
if (disk_name == NULL || disk_name[0] == '\0' || usage == NULL) {
return 0;
}
size_t len = strlen(disk_name);
if (len >= name_size) {
len = name_size - 1;
}
memcpy(name, disk_name, len);
name[len] = '\0';
*percent = clamp_percent(parse_int_field(usage, 0));
*read_kbps = parse_int_field(read_rate, -1);
*write_kbps = parse_int_field(write_rate, -1);
return 1;
}
static void update_disk_rows(const char *disk_text)
{
char buffer[UI_MAX_DISK_TEXT];
char name[8];
int row = 0;
snprintf(buffer, sizeof(buffer), "%s", disk_text && disk_text[0] ? disk_text : "");
char *cursor = buffer;
char *token = strtok(cursor, " ");
while (token != NULL && row < DISK_ROW_COUNT) {
int percent = 0;
int read_kbps = -1;
int write_kbps = -1;
if (parse_disk_token(token, name, sizeof(name), &percent, &read_kbps, &write_kbps)) {
char text[28];
char read[10];
char write[10];
snprintf(text, sizeof(text), "%s", name);
lv_label_set_text(s_disk_name_labels[row], text);
format_rate_short(read_kbps, read, sizeof(read));
format_rate_short(write_kbps, write, sizeof(write));
snprintf(text, sizeof(text), "%s/%s", read, write);
lv_label_set_text(s_disk_rate_labels[row], text);
lv_bar_set_value(s_disk_bars[row], percent, LV_ANIM_OFF);
lv_obj_clear_flag(s_disk_name_labels[row], LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(s_disk_rate_labels[row], LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(s_disk_bars[row], LV_OBJ_FLAG_HIDDEN);
row++;
}
token = strtok(NULL, " ");
}
for (int i = row; i < DISK_ROW_COUNT; i++) {
lv_obj_add_flag(s_disk_name_labels[i], LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(s_disk_rate_labels[i], LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(s_disk_bars[i], LV_OBJ_FLAG_HIDDEN);
}
}
static void update_codex_panel(const ui_metrics_t *metrics)
{
char value[12];
char reset_5h[12];
char reset_week[12];
set_remaining_bar(s_codex_5h_bar, s_codex_5h_value_label, metrics->codex_5h_percent);
set_remaining_bar(s_codex_week_bar, s_codex_week_value_label, metrics->codex_week_percent);
format_reset_mins(metrics->codex_5h_reset_mins, reset_5h, sizeof(reset_5h));
format_reset_mins(metrics->codex_week_reset_mins, reset_week, sizeof(reset_week));
lv_label_set_text(s_codex_5h_reset_label, reset_5h);
lv_label_set_text(s_codex_week_reset_label, reset_week);
format_tokens_m10(metrics->codex_today_tokens_m10, value, sizeof(value));
lv_label_set_text(s_codex_today_label, value);
for (int i = 0; i < UI_CODEX_DAILY_BAR_COUNT; i++) {
lv_obj_t *bar = s_codex_daily_bars[i];
if (bar == NULL) {
continue;
}
if (i >= metrics->codex_daily_count || metrics->codex_daily_percent[i] < 0) {
lv_obj_add_flag(bar, LV_OBJ_FLAG_HIDDEN);
continue;
}
int percent = clamp_percent(metrics->codex_daily_percent[i]);
int height = percent <= 0 ? 1 : 1 + ((percent * (CODEX_DAILY_BAR_MAX_H - 1)) + 50) / 100;
int x = 10 + i * (CODEX_DAILY_BAR_W + CODEX_DAILY_BAR_GAP);
int y = CODEX_DAILY_BAR_BASE_Y - height;
lv_obj_set_size(bar, CODEX_DAILY_BAR_W, height);
lv_obj_align(bar, LV_ALIGN_TOP_LEFT, x, y);
lv_obj_clear_flag(bar, LV_OBJ_FLAG_HIDDEN);
}
}
static void update_zhipu_panel(const ui_metrics_t *metrics)
{
char reset[12];
char text[24];
if (metrics->zhipu_today_tokens_m10 >= 0) {
format_tokens_m10(metrics->zhipu_today_tokens_m10, text, sizeof(text));
} else {
snprintf(text, sizeof(text), "%s", zhipu_status_text(metrics->zhipu_status));
}
lv_label_set_text(s_zhipu_today_label, text);
set_remaining_bar(s_zhipu_5h_bar, s_zhipu_5h_value_label, metrics->zhipu_5h_percent);
format_reset_mins(metrics->zhipu_5h_reset_mins, reset, sizeof(reset));
lv_label_set_text(s_zhipu_5h_reset_label, reset);
set_remaining_bar(s_zhipu_week_bar, s_zhipu_week_value_label, metrics->zhipu_week_percent);
format_reset_mins(metrics->zhipu_week_reset_mins, reset, sizeof(reset));
lv_label_set_text(s_zhipu_week_reset_label, reset);
for (int i = 0; i < UI_ZHIPU_DAILY_BAR_COUNT; i++) {
lv_obj_t *bar = s_zhipu_daily_bars[i];
if (bar == NULL) {
continue;
}
if (i >= metrics->zhipu_daily_count || metrics->zhipu_daily_percent[i] < 0) {
lv_obj_add_flag(bar, LV_OBJ_FLAG_HIDDEN);
continue;
}
int percent = clamp_percent(metrics->zhipu_daily_percent[i]);
int height = percent <= 0 ? 1 : 1 + ((percent * (CODEX_DAILY_BAR_MAX_H - 1)) + 50) / 100;
int x = ZHIPU_DAILY_BAR_BASE_X + i * (CODEX_DAILY_BAR_W + CODEX_DAILY_BAR_GAP);
int y = CODEX_DAILY_BAR_BASE_Y - height;
lv_obj_set_size(bar, CODEX_DAILY_BAR_W, height);
lv_obj_align(bar, LV_ALIGN_TOP_LEFT, x, y);
lv_obj_clear_flag(bar, LV_OBJ_FLAG_HIDDEN);
}
}
static void update_cpu_core_labels(const char *core_text)
{
char buffer[UI_MAX_CPU_CORE_TEXT];
int row = 0;
snprintf(buffer, sizeof(buffer), "%s", core_text != NULL ? core_text : "");
char *cursor = buffer;
char *token = strtok(cursor, " ");
while (token != NULL && row < CPU_TOP_CORE_COUNT) {
char *freq = "--";
char *sep = strchr(token, ':');
if (sep != NULL) {
*sep = '\0';
freq = sep + 1;
}
lv_label_set_text(s_cpu_core_type_labels[row], token);
lv_label_set_text(s_cpu_core_freq_labels[row], freq);
lv_obj_clear_flag(s_cpu_core_type_labels[row], LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(s_cpu_core_freq_labels[row], LV_OBJ_FLAG_HIDDEN);
row++;
token = strtok(NULL, " ");
}
for (int i = row; i < CPU_TOP_CORE_COUNT; i++) {
lv_obj_add_flag(s_cpu_core_type_labels[i], LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(s_cpu_core_freq_labels[i], LV_OBJ_FLAG_HIDDEN);
}
}
static void update_net_line(const ui_metrics_t *metrics)
{
char rx[12];
char tx[12];
format_rate_short(metrics->rx_kbps, rx, sizeof(rx));
format_rate_short(metrics->tx_kbps, tx, sizeof(tx));
const lv_font_t *rx_font = strlen(rx) >= 5 ? &lv_font_montserrat_14 : &lv_font_montserrat_20;
const lv_font_t *tx_font = strlen(tx) >= 5 ? &lv_font_montserrat_14 : &lv_font_montserrat_20;
lv_obj_set_style_text_font(s_net_rx_label, rx_font, 0);
lv_obj_set_style_text_font(s_net_tx_label, tx_font, 0);
lv_label_set_text(s_net_rx_label, rx);
lv_label_set_text(s_net_tx_label, tx);
}
void ui_screen_create(void)
{
lv_obj_t *screen = lv_screen_active();
lv_obj_set_style_bg_color(screen, paper(), 0);
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0);
lv_obj_clear_flag(screen, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_clean(screen);
make_rule(screen, 200, 6, 1, 288);
make_rule(screen, 6, 174, 388, 1);
s_status_label = make_label(screen, "WAITING", &lv_font_montserrat_14);
lv_obj_add_flag(s_status_label, LV_OBJ_FLAG_HIDDEN);
s_cpu_line = lv_line_create(screen);
lv_obj_set_size(s_cpu_line, CPU_CHART_W + 2, CPU_CHART_H + 2);
lv_obj_align(s_cpu_line, LV_ALIGN_TOP_LEFT, 8, 88);
lv_obj_set_style_line_color(s_cpu_line, ink(), 0);
lv_obj_set_style_line_width(s_cpu_line, 3, 0);
for (int i = 0; i < CPU_HISTORY_POINTS; i++) {
s_cpu_history[i] = 0;
}
set_cpu_curve(0);
s_battery_label = make_label(screen, LV_SYMBOL_BATTERY_EMPTY " --%", &lv_font_montserrat_14);
lv_obj_set_width(s_battery_label, 54);
lv_obj_align(s_battery_label, LV_ALIGN_TOP_LEFT, 8, 8);
s_conn_label = make_label(screen, LV_SYMBOL_USB, &lv_font_montserrat_14);
lv_obj_set_width(s_conn_label, 22);
lv_obj_align(s_conn_label, LV_ALIGN_TOP_LEFT, 58, 8);
s_env_label = make_label(screen, "--C --%", &lv_font_montserrat_14);
lv_obj_set_width(s_env_label, 110);
lv_obj_align(s_env_label, LV_ALIGN_TOP_LEFT, 82, 8);
make_label_halo(screen, s_cpu_value_halo, "--%", &lv_font_montserrat_48, 126,
LV_ALIGN_TOP_LEFT, 8, 30, 3, LV_TEXT_ALIGN_LEFT);
s_cpu_value_label = make_label(screen, "--%", &lv_font_montserrat_48);
lv_obj_set_width(s_cpu_value_label, 126);
lv_obj_set_style_text_align(s_cpu_value_label, LV_TEXT_ALIGN_LEFT, 0);
lv_obj_align(s_cpu_value_label, LV_ALIGN_TOP_LEFT, 8, 30);
make_label_halo(screen, s_cpu_freq_halo, "--GHz", &lv_font_montserrat_28, 128,
LV_ALIGN_TOP_LEFT, 8, 72, 2, LV_TEXT_ALIGN_LEFT);
s_cpu_freq_label = make_label(screen, "--GHz", &lv_font_montserrat_28);
lv_obj_set_width(s_cpu_freq_label, 128);
lv_obj_align(s_cpu_freq_label, LV_ALIGN_TOP_LEFT, 8, 72);
for (int i = 0; i < CPU_TOP_CORE_COUNT; i++) {
int y = 28 + i * 16;
s_cpu_core_type_labels[i] = make_label(screen, "-", &lv_font_montserrat_14);
lv_label_set_long_mode(s_cpu_core_type_labels[i], LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_cpu_core_type_labels[i], 14);
lv_obj_set_style_text_align(s_cpu_core_type_labels[i], LV_TEXT_ALIGN_LEFT, 0);
lv_obj_align(s_cpu_core_type_labels[i], LV_ALIGN_TOP_LEFT, 158, y);
lv_obj_add_flag(s_cpu_core_type_labels[i], LV_OBJ_FLAG_HIDDEN);
s_cpu_core_freq_labels[i] = make_label(screen, "--", &lv_font_montserrat_14);
lv_label_set_long_mode(s_cpu_core_freq_labels[i], LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_cpu_core_freq_labels[i], 42);
lv_obj_set_style_text_align(s_cpu_core_freq_labels[i], LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_cpu_core_freq_labels[i], LV_ALIGN_TOP_LEFT, 154, y);
lv_obj_add_flag(s_cpu_core_freq_labels[i], LV_OBJ_FLAG_HIDDEN);
}
lv_obj_t *memory_title = make_label(screen, "MEM", &lv_font_montserrat_14);
lv_obj_align(memory_title, LV_ALIGN_TOP_LEFT, 208, 8);
make_label_halo(screen, s_memory_value_halo, "--%", &lv_font_montserrat_20, 50,
LV_ALIGN_TOP_RIGHT, -8, 8, 2, LV_TEXT_ALIGN_RIGHT);
s_memory_value_label = make_label(screen, "--%", &lv_font_montserrat_20);
lv_obj_set_width(s_memory_value_label, 50);
lv_obj_set_style_text_align(s_memory_value_label, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_memory_value_label, LV_ALIGN_TOP_RIGHT, -8, 8);
s_memory_detail_label = make_label(screen, "--/--", &lv_font_montserrat_14);
lv_obj_set_width(s_memory_detail_label, 178);
lv_obj_align(s_memory_detail_label, LV_ALIGN_TOP_LEFT, 208, 24);
s_memory_bar = make_bar(screen, 180, 10);
lv_obj_align(s_memory_bar, LV_ALIGN_TOP_LEFT, 208, 46);
lv_obj_t *disk_title = make_label(screen, "DSK", &lv_font_montserrat_14);
lv_obj_align(disk_title, LV_ALIGN_TOP_LEFT, 208, 64);
for (int i = 0; i < DISK_ROW_COUNT; i++) {
int y = 80 + i * 16;
s_disk_name_labels[i] = make_label(screen, "--", &lv_font_montserrat_14);
lv_obj_set_width(s_disk_name_labels[i], 14);
lv_obj_align(s_disk_name_labels[i], LV_ALIGN_TOP_LEFT, 208, y);
s_disk_bars[i] = make_bar(screen, 84, 8);
lv_obj_align(s_disk_bars[i], LV_ALIGN_TOP_LEFT, 224, y + 4);
s_disk_rate_labels[i] = make_label(screen, "--/--", &lv_font_montserrat_14);
lv_label_set_long_mode(s_disk_rate_labels[i], LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_disk_rate_labels[i], 80);
lv_obj_align(s_disk_rate_labels[i], LV_ALIGN_TOP_LEFT, 312, y);
}
lv_obj_t *net_down_icon = make_net_arrow(screen, s_net_down_icon_points,
sizeof(s_net_down_icon_points) / sizeof(s_net_down_icon_points[0]));
lv_obj_align(net_down_icon, LV_ALIGN_TOP_LEFT, 208, 152);
s_net_rx_label = make_label(screen, "--", &lv_font_montserrat_20);
lv_label_set_long_mode(s_net_rx_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_net_rx_label, 62);
lv_obj_set_height(s_net_rx_label, 24);
lv_obj_align(s_net_rx_label, LV_ALIGN_TOP_LEFT, 224, 146);
lv_obj_t *net_up_icon = make_net_arrow(screen, s_net_up_icon_points,
sizeof(s_net_up_icon_points) / sizeof(s_net_up_icon_points[0]));
lv_obj_align(net_up_icon, LV_ALIGN_TOP_LEFT, 300, 152);
s_net_tx_label = make_label(screen, "--", &lv_font_montserrat_20);
lv_label_set_long_mode(s_net_tx_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_net_tx_label, 76);
lv_obj_set_height(s_net_tx_label, 24);
lv_obj_align(s_net_tx_label, LV_ALIGN_TOP_LEFT, 316, 146);
lv_obj_t *codex_title = make_label(screen, "CODEX", &lv_font_montserrat_14);
lv_obj_align(codex_title, LV_ALIGN_TOP_LEFT, 8, 184);
s_codex_today_label = make_label(screen, "--", &lv_font_montserrat_14);
lv_label_set_long_mode(s_codex_today_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_codex_today_label, 70);
lv_obj_set_style_text_align(s_codex_today_label, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_codex_today_label, LV_ALIGN_TOP_LEFT, 126, 184);
s_codex_5h_label = make_label(screen, "5H", &lv_font_montserrat_14);
lv_label_set_long_mode(s_codex_5h_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_codex_5h_label, 30);
lv_obj_align(s_codex_5h_label, LV_ALIGN_TOP_LEFT, 8, 204);
s_codex_5h_bar = make_quota_bar(screen, 80, 8);
lv_obj_align(s_codex_5h_bar, LV_ALIGN_TOP_LEFT, 38, 208);
s_codex_5h_value_label = make_label(screen, "--", &lv_font_montserrat_14);
lv_obj_set_width(s_codex_5h_value_label, 34);
lv_obj_set_style_text_align(s_codex_5h_value_label, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_codex_5h_value_label, LV_ALIGN_TOP_LEFT, 122, 204);
s_codex_5h_reset_label = make_label(screen, "--", &lv_font_montserrat_14);
lv_label_set_long_mode(s_codex_5h_reset_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_codex_5h_reset_label, 38);
lv_obj_set_style_text_align(s_codex_5h_reset_label, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_codex_5h_reset_label, LV_ALIGN_TOP_LEFT, 158, 204);
s_codex_week_label = make_label(screen, "WK", &lv_font_montserrat_14);
lv_label_set_long_mode(s_codex_week_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_codex_week_label, 30);
lv_obj_align(s_codex_week_label, LV_ALIGN_TOP_LEFT, 8, 224);
s_codex_week_bar = make_quota_bar(screen, 80, 8);
lv_obj_align(s_codex_week_bar, LV_ALIGN_TOP_LEFT, 38, 228);
s_codex_week_value_label = make_label(screen, "--", &lv_font_montserrat_14);
lv_obj_set_width(s_codex_week_value_label, 34);
lv_obj_set_style_text_align(s_codex_week_value_label, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_codex_week_value_label, LV_ALIGN_TOP_LEFT, 122, 224);
s_codex_week_reset_label = make_label(screen, "--", &lv_font_montserrat_14);
lv_label_set_long_mode(s_codex_week_reset_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_codex_week_reset_label, 38);
lv_obj_set_style_text_align(s_codex_week_reset_label, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_codex_week_reset_label, LV_ALIGN_TOP_LEFT, 158, 224);
make_rule(screen, 8, CODEX_DAILY_BAR_BASE_Y, 188, 1);
for (int i = 0; i < UI_CODEX_DAILY_BAR_COUNT; i++) {
s_codex_daily_bars[i] = lv_obj_create(screen);
lv_obj_set_size(s_codex_daily_bars[i], CODEX_DAILY_BAR_W, 1);
lv_obj_align(s_codex_daily_bars[i], LV_ALIGN_TOP_LEFT,
10 + i * (CODEX_DAILY_BAR_W + CODEX_DAILY_BAR_GAP),
CODEX_DAILY_BAR_BASE_Y - 1);
lv_obj_set_style_bg_color(s_codex_daily_bars[i], ink(), 0);
lv_obj_set_style_bg_opa(s_codex_daily_bars[i], LV_OPA_COVER, 0);
lv_obj_set_style_border_width(s_codex_daily_bars[i], 0, 0);
lv_obj_set_style_radius(s_codex_daily_bars[i], 0, 0);
lv_obj_set_style_pad_all(s_codex_daily_bars[i], 0, 0);
lv_obj_clear_flag(s_codex_daily_bars[i], LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(s_codex_daily_bars[i], LV_OBJ_FLAG_HIDDEN);
}
lv_obj_t *zhipu_title = make_label(screen, "ZHIPU", &lv_font_montserrat_14);
lv_obj_align(zhipu_title, LV_ALIGN_TOP_LEFT, 208, 184);
s_zhipu_today_label = make_label(screen, "--", &lv_font_montserrat_14);
lv_label_set_long_mode(s_zhipu_today_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_zhipu_today_label, 70);
lv_obj_set_style_text_align(s_zhipu_today_label, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_zhipu_today_label, LV_ALIGN_TOP_LEFT, 326, 184);
s_zhipu_5h_label = make_label(screen, "5H", &lv_font_montserrat_14);
lv_label_set_long_mode(s_zhipu_5h_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_zhipu_5h_label, 30);
lv_obj_align(s_zhipu_5h_label, LV_ALIGN_TOP_LEFT, 208, 204);
s_zhipu_5h_bar = make_quota_bar(screen, 80, 8);
lv_obj_align(s_zhipu_5h_bar, LV_ALIGN_TOP_LEFT, 238, 208);
s_zhipu_5h_value_label = make_label(screen, "--", &lv_font_montserrat_14);
lv_obj_set_width(s_zhipu_5h_value_label, 34);
lv_obj_set_style_text_align(s_zhipu_5h_value_label, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_zhipu_5h_value_label, LV_ALIGN_TOP_LEFT, 322, 204);
s_zhipu_5h_reset_label = make_label(screen, "--", &lv_font_montserrat_14);
lv_label_set_long_mode(s_zhipu_5h_reset_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_zhipu_5h_reset_label, 38);
lv_obj_set_style_text_align(s_zhipu_5h_reset_label, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_zhipu_5h_reset_label, LV_ALIGN_TOP_LEFT, 358, 204);
s_zhipu_week_label = make_label(screen, "WK", &lv_font_montserrat_14);
lv_label_set_long_mode(s_zhipu_week_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_zhipu_week_label, 30);
lv_obj_align(s_zhipu_week_label, LV_ALIGN_TOP_LEFT, 208, 224);
s_zhipu_week_bar = make_quota_bar(screen, 80, 8);
lv_obj_align(s_zhipu_week_bar, LV_ALIGN_TOP_LEFT, 238, 228);
s_zhipu_week_value_label = make_label(screen, "--", &lv_font_montserrat_14);
lv_obj_set_width(s_zhipu_week_value_label, 34);
lv_obj_set_style_text_align(s_zhipu_week_value_label, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_zhipu_week_value_label, LV_ALIGN_TOP_LEFT, 322, 224);
s_zhipu_week_reset_label = make_label(screen, "--", &lv_font_montserrat_14);
lv_label_set_long_mode(s_zhipu_week_reset_label, LV_LABEL_LONG_CLIP);
lv_obj_set_width(s_zhipu_week_reset_label, 38);
lv_obj_set_style_text_align(s_zhipu_week_reset_label, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_align(s_zhipu_week_reset_label, LV_ALIGN_TOP_LEFT, 358, 224);
make_rule(screen, 208, CODEX_DAILY_BAR_BASE_Y, 188, 1);
for (int i = 0; i < UI_ZHIPU_DAILY_BAR_COUNT; i++) {
s_zhipu_daily_bars[i] = lv_obj_create(screen);
lv_obj_set_size(s_zhipu_daily_bars[i], CODEX_DAILY_BAR_W, 1);
lv_obj_align(s_zhipu_daily_bars[i], LV_ALIGN_TOP_LEFT,
ZHIPU_DAILY_BAR_BASE_X + i * (CODEX_DAILY_BAR_W + CODEX_DAILY_BAR_GAP),
CODEX_DAILY_BAR_BASE_Y - 1);
lv_obj_set_style_bg_color(s_zhipu_daily_bars[i], ink(), 0);
lv_obj_set_style_bg_opa(s_zhipu_daily_bars[i], LV_OPA_COVER, 0);
lv_obj_set_style_border_width(s_zhipu_daily_bars[i], 0, 0);
lv_obj_set_style_radius(s_zhipu_daily_bars[i], 0, 0);
lv_obj_set_style_pad_all(s_zhipu_daily_bars[i], 0, 0);
lv_obj_clear_flag(s_zhipu_daily_bars[i], LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(s_zhipu_daily_bars[i], LV_OBJ_FLAG_HIDDEN);
}
}
void ui_screen_tick(void)
{
s_age_seconds++;
if (s_age_seconds > 5 && s_status_label != NULL) {
lv_label_set_text(s_status_label, "STALE");
}
}
void ui_screen_set_status(const char *status)
{
if (s_status_label == NULL || status == NULL) {
return;
}
lv_lock();
lv_label_set_text(s_status_label, status);
lv_unlock();
}
void ui_screen_update_battery(int percent, int millivolts)
{
if (s_battery_label == NULL) {
return;
}
const char *symbol = LV_SYMBOL_BATTERY_EMPTY;
percent = clamp_percent(percent);
if (percent >= 90) {
symbol = LV_SYMBOL_BATTERY_FULL;
} else if (percent >= 65) {
symbol = LV_SYMBOL_BATTERY_3;
} else if (percent >= 35) {
symbol = LV_SYMBOL_BATTERY_2;
} else if (percent >= 10) {
symbol = LV_SYMBOL_BATTERY_1;
}
char text[24];
if (millivolts > 0) {
snprintf(text, sizeof(text), "%s %d%%", symbol, percent);
} else {
snprintf(text, sizeof(text), "%s --%%", symbol);
}
lv_lock();
lv_label_set_text(s_battery_label, text);
lv_unlock();
}
void ui_screen_update_environment(int temperature_c10, int humidity_percent)
{
if (s_env_label == NULL) {
return;
}
if (humidity_percent < 0) {
humidity_percent = 0;
} else if (humidity_percent > 100) {
humidity_percent = 100;
}
char text[32];
int abs_temp_c10 = abs(temperature_c10);
if (temperature_c10 < 0) {
snprintf(text, sizeof(text), "-%d.%dC %d%%", abs_temp_c10 / 10, abs_temp_c10 % 10, humidity_percent);
} else {
snprintf(text, sizeof(text), "%d.%dC %d%%", abs_temp_c10 / 10, abs_temp_c10 % 10, humidity_percent);
}
lv_lock();
lv_label_set_text(s_env_label, text);
lv_unlock();
}
void ui_screen_update_metrics_from_source(const ui_metrics_t *metrics, const char *source)
{
if (metrics == NULL || s_cpu_value_label == NULL) {
return;
}
char text[96];
char used[16];
char total[16];
lv_lock();
s_age_seconds = 0;
lv_label_set_text(s_status_label, source ? source : "LIVE");
set_connection(connection_from_source(source));
snprintf(text, sizeof(text), "%d%%", clamp_percent(metrics->cpu_percent));
set_label_with_halo(s_cpu_value_label, s_cpu_value_halo, text);
if (metrics->cpu_mhz > 0) {
snprintf(text, sizeof(text), "%d.%02dGHz", metrics->cpu_mhz / 1000, (metrics->cpu_mhz % 1000) / 10);
} else {
snprintf(text, sizeof(text), "--GHz");
}
set_label_with_halo(s_cpu_freq_label, s_cpu_freq_halo, text);
update_cpu_core_labels(metrics->cpu_core_text);
set_cpu_curve(metrics->cpu_percent);
snprintf(text, sizeof(text), "%d%%", clamp_percent(metrics->memory_percent));
set_label_with_halo(s_memory_value_label, s_memory_value_halo, text);
format_capacity(metrics->memory_used_mb, used, sizeof(used));
format_capacity(metrics->memory_total_mb, total, sizeof(total));
snprintf(text, sizeof(text), "%s / %s", used, total);
lv_label_set_text(s_memory_detail_label, text);
set_bar_percent(s_memory_bar, metrics->memory_percent);
update_disk_rows(metrics->disk_text);
update_net_line(metrics);
update_codex_panel(metrics);
update_zhipu_panel(metrics);
lv_obj_invalidate(lv_screen_active());
lv_unlock();
}
void ui_screen_update_metrics(const ui_metrics_t *metrics)
{
ui_screen_update_metrics_from_source(metrics, "USB");
}