#include "ui_screen.h" #include #include #include #include #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 SCREEN_W 400 #define PANEL_W (SCREEN_W / 2) #define PANEL_PAD_X 8 #define PANEL_EDGE_GAP 4 #define MID_X PANEL_W #define LEFT_X PANEL_PAD_X #define RIGHT_X (PANEL_W + PANEL_PAD_X) #define PANEL_CONTENT_W (PANEL_W - PANEL_PAD_X - PANEL_EDGE_GAP) #define PANEL_H_RULE_X (PANEL_PAD_X - 2) #define PANEL_H_RULE_W (SCREEN_W - 2 * PANEL_H_RULE_X) #define PANEL_TODAY_X_OFFSET 118 #define QUOTA_BAR_X_OFFSET 30 #define QUOTA_VALUE_X_OFFSET 114 #define QUOTA_RESET_X_OFFSET 150 #define CODEX_DAILY_BAR_W 8 #define CODEX_DAILY_BAR_GAP 5 #define CODEX_DAILY_BAR_BASE_X (LEFT_X + 2) #define ZHIPU_DAILY_BAR_BASE_X (RIGHT_X + 2) #define CODEX_DAILY_BAR_BASE_Y 292 #define CODEX_DAILY_BAR_MAX_H 46 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 = CODEX_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_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, MID_X, 6, 1, 288); make_rule(screen, PANEL_H_RULE_X, 174, PANEL_H_RULE_W, 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, RIGHT_X, 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, RIGHT_X, 24); s_memory_bar = make_bar(screen, 180, 10); lv_obj_align(s_memory_bar, LV_ALIGN_TOP_LEFT, RIGHT_X, 46); lv_obj_t *disk_title = make_label(screen, "DSK", &lv_font_montserrat_14); lv_obj_align(disk_title, LV_ALIGN_TOP_LEFT, RIGHT_X, 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, RIGHT_X, y); s_disk_bars[i] = make_bar(screen, 84, 8); lv_obj_align(s_disk_bars[i], LV_ALIGN_TOP_LEFT, RIGHT_X + 16, 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], PANEL_CONTENT_W - 104); lv_obj_set_style_text_align(s_disk_rate_labels[i], LV_TEXT_ALIGN_RIGHT, 0); lv_obj_align(s_disk_rate_labels[i], LV_ALIGN_TOP_LEFT, RIGHT_X + 104, 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, RIGHT_X, 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, RIGHT_X + 16, 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, RIGHT_X + 92, 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, RIGHT_X + 108, 146); lv_obj_t *codex_title = make_label(screen, "CODEX", &lv_font_montserrat_14); lv_obj_align(codex_title, LV_ALIGN_TOP_LEFT, LEFT_X, 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, LEFT_X + PANEL_TODAY_X_OFFSET, 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, LEFT_X, 204); s_codex_5h_bar = make_quota_bar(screen, 80, 8); lv_obj_align(s_codex_5h_bar, LV_ALIGN_TOP_LEFT, LEFT_X + QUOTA_BAR_X_OFFSET, 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, LEFT_X + QUOTA_VALUE_X_OFFSET, 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, LEFT_X + QUOTA_RESET_X_OFFSET, 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, LEFT_X, 224); s_codex_week_bar = make_quota_bar(screen, 80, 8); lv_obj_align(s_codex_week_bar, LV_ALIGN_TOP_LEFT, LEFT_X + QUOTA_BAR_X_OFFSET, 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, LEFT_X + QUOTA_VALUE_X_OFFSET, 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, LEFT_X + QUOTA_RESET_X_OFFSET, 224); make_rule(screen, LEFT_X, CODEX_DAILY_BAR_BASE_Y, PANEL_CONTENT_W, 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, CODEX_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_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, RIGHT_X, 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, RIGHT_X + PANEL_TODAY_X_OFFSET, 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, RIGHT_X, 204); s_zhipu_5h_bar = make_quota_bar(screen, 80, 8); lv_obj_align(s_zhipu_5h_bar, LV_ALIGN_TOP_LEFT, RIGHT_X + QUOTA_BAR_X_OFFSET, 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, RIGHT_X + QUOTA_VALUE_X_OFFSET, 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, RIGHT_X + QUOTA_RESET_X_OFFSET, 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, RIGHT_X, 224); s_zhipu_week_bar = make_quota_bar(screen, 80, 8); lv_obj_align(s_zhipu_week_bar, LV_ALIGN_TOP_LEFT, RIGHT_X + QUOTA_BAR_X_OFFSET, 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, RIGHT_X + QUOTA_VALUE_X_OFFSET, 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, RIGHT_X + QUOTA_RESET_X_OFFSET, 224); make_rule(screen, RIGHT_X, CODEX_DAILY_BAR_BASE_Y, PANEL_CONTENT_W, 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"); }