#include #include #include #include "battery_status_event.h" #include "display_ui.h" #include "keyboard_led_event.h" #define DISPLAY_SYMBOL_PLUG "\xEF\x87\xA6" LV_FONT_DECLARE(ui_font_keyboard_small_18); LV_FONT_DECLARE(ui_font_keyboard_time_48); enum display_status_id { DISPLAY_STATUS_USB = 0, DISPLAY_STATUS_BLE, DISPLAY_STATUS_NUMLOCK, DISPLAY_STATUS_CAPSLOCK, DISPLAY_STATUS_COUNT, }; struct display_ui_ctx { lv_obj_t *status_badges[DISPLAY_STATUS_COUNT]; lv_obj_t *status_labels[DISPLAY_STATUS_COUNT]; lv_obj_t *battery_icon; lv_obj_t *battery_label; lv_obj_t *battery_state_label; lv_obj_t *date_label; lv_obj_t *time_label; }; static struct display_ui_ctx g_display_ui; static const char *const g_status_texts[DISPLAY_STATUS_COUNT] = { LV_SYMBOL_USB, LV_SYMBOL_BLUETOOTH, "1", "A", }; static lv_color_t display_ui_get_battery_color(uint8_t battery_level) { if (battery_level > 70U) { return lv_color_hex(0x8BD450); } if (battery_level >= 20U) { return lv_color_hex(0xF4D35E); } return lv_color_hex(0xE63946); } static const char *display_ui_get_battery_symbol(uint8_t battery_level) { if (battery_level > 85U) { return LV_SYMBOL_BATTERY_FULL; } if (battery_level > 60U) { return LV_SYMBOL_BATTERY_3; } if (battery_level > 35U) { return LV_SYMBOL_BATTERY_2; } if (battery_level >= 20U) { return LV_SYMBOL_BATTERY_1; } return LV_SYMBOL_BATTERY_EMPTY; } static bool display_ui_status_is_active(enum display_status_id id, const struct display_ui_model *model) { switch (id) { case DISPLAY_STATUS_USB: return model->mode == MODE_TYPE_USB; case DISPLAY_STATUS_BLE: return model->mode == MODE_TYPE_BLE; case DISPLAY_STATUS_NUMLOCK: return (model->led_mask & KEYBOARD_LED_MASK_NUM_LOCK) != 0U; case DISPLAY_STATUS_CAPSLOCK: return (model->led_mask & KEYBOARD_LED_MASK_CAPS_LOCK) != 0U; default: return false; } } static void display_ui_create_status_chip(lv_obj_t *parent, enum display_status_id id) { lv_obj_t *badge = lv_obj_create(parent); lv_obj_t *label; lv_obj_remove_style_all(badge); lv_obj_set_size(badge, 50, 32); lv_obj_set_style_radius(badge, 10, 0); lv_obj_set_style_bg_opa(badge, LV_OPA_COVER, 0); lv_obj_set_style_pad_all(badge, 0, 0); label = lv_label_create(badge); lv_label_set_text(label, g_status_texts[id]); lv_obj_set_width(label, LV_PCT(100)); lv_obj_set_style_text_font(label, &ui_font_keyboard_small_18, 0); lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); lv_obj_center(label); g_display_ui.status_badges[id] = badge; g_display_ui.status_labels[id] = label; } void display_ui_refresh_status_bar(const struct display_ui_model *model) { for (uint32_t i = 0; i < DISPLAY_STATUS_COUNT; i++) { lv_obj_t *badge = g_display_ui.status_badges[i]; lv_obj_t *label = g_display_ui.status_labels[i]; bool active = display_ui_status_is_active((enum display_status_id)i, model); if (!badge || !label) { continue; } lv_obj_set_style_border_width(badge, 4, 0); lv_obj_set_style_border_color(badge, active ? model->theme_color : model->inactive_border_color, 0); lv_obj_set_style_bg_color(badge, active ? lv_color_hex(0x1D2735) : lv_color_hex(0x161A20), 0); lv_obj_set_style_text_color(label, active ? lv_color_white() : lv_color_hex(0x7C8798), 0); } } void display_ui_refresh_battery(const struct display_ui_model *model) { char battery_text[8]; lv_color_t battery_color; const char *state_symbol = ""; lv_color_t state_color = lv_color_white(); if (!g_display_ui.battery_icon || !g_display_ui.battery_label || !g_display_ui.battery_state_label) { return; } battery_color = display_ui_get_battery_color(model->battery_level); snprintk(battery_text, sizeof(battery_text), "%u%%", model->battery_level); if ((model->battery_flags & BATTERY_STATUS_FLAG_FULL) != 0U) { state_symbol = DISPLAY_SYMBOL_PLUG; state_color = lv_color_hex(0x4C9EF5); } else if ((model->battery_flags & BATTERY_STATUS_FLAG_CHARGING) != 0U) { state_symbol = LV_SYMBOL_CHARGE; state_color = lv_color_hex(0xF4D35E); } lv_label_set_text(g_display_ui.battery_icon, display_ui_get_battery_symbol(model->battery_level)); lv_obj_set_style_text_color(g_display_ui.battery_icon, battery_color, 0); lv_label_set_text(g_display_ui.battery_label, battery_text); lv_label_set_text(g_display_ui.battery_state_label, state_symbol); lv_obj_set_style_text_color(g_display_ui.battery_state_label, state_color, 0); } void display_ui_refresh_datetime(const char *date_text, const char *time_text) { if (!g_display_ui.date_label || !g_display_ui.time_label) { return; } lv_label_set_text(g_display_ui.date_label, date_text); lv_label_set_text(g_display_ui.time_label, time_text); } void display_ui_refresh_all(const struct display_ui_model *model, const char *date_text, const char *time_text) { display_ui_refresh_status_bar(model); display_ui_refresh_battery(model); display_ui_refresh_datetime(date_text, time_text); } void display_ui_init(const struct display_ui_model *model, const char *date_text, const char *time_text) { lv_obj_t *screen = lv_screen_active(); lv_obj_t *content; lv_obj_t *top_row; lv_obj_t *battery_wrap; lv_obj_t *middle_row; lv_obj_t *bottom_row; memset(&g_display_ui, 0, sizeof(g_display_ui)); lv_obj_clean(screen); lv_obj_set_style_bg_color(screen, lv_color_hex(0x0F1115), 0); lv_obj_set_style_bg_grad_color(screen, lv_color_hex(0x1A1F29), 0); lv_obj_set_style_bg_grad_dir(screen, LV_GRAD_DIR_VER, 0); lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0); lv_obj_set_style_text_color(screen, lv_color_white(), 0); lv_obj_set_style_pad_all(screen, 0, 0); lv_obj_set_scrollbar_mode(screen, LV_SCROLLBAR_MODE_OFF); content = lv_obj_create(screen); lv_obj_remove_style_all(content); lv_obj_set_size(content, LV_PCT(100), LV_PCT(100)); lv_obj_set_style_bg_color(content, lv_color_hex(0x0F1115), 0); lv_obj_set_style_bg_opa(content, LV_OPA_TRANSP, 0); lv_obj_set_style_pad_left(content, 14, 0); lv_obj_set_style_pad_right(content, 14, 0); lv_obj_set_style_pad_top(content, 8, 0); lv_obj_set_style_pad_bottom(content, 8, 0); lv_obj_set_layout(content, LV_LAYOUT_FLEX); lv_obj_set_flex_flow(content, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(content, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); top_row = lv_obj_create(content); lv_obj_remove_style_all(top_row); lv_obj_set_width(top_row, LV_PCT(100)); lv_obj_set_flex_grow(top_row, 1); lv_obj_set_style_bg_color(top_row, lv_color_hex(0x0F1115), 0); lv_obj_set_style_bg_opa(top_row, LV_OPA_TRANSP, 0); lv_obj_set_layout(top_row, LV_LAYOUT_FLEX); lv_obj_set_flex_flow(top_row, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(top_row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); g_display_ui.date_label = lv_label_create(top_row); lv_obj_set_style_text_font(g_display_ui.date_label, &ui_font_keyboard_small_18, 0); lv_obj_set_style_text_color(g_display_ui.date_label, lv_color_hex(0xD8DEE9), 0); battery_wrap = lv_obj_create(top_row); lv_obj_remove_style_all(battery_wrap); lv_obj_set_width(battery_wrap, LV_SIZE_CONTENT); lv_obj_set_layout(battery_wrap, LV_LAYOUT_FLEX); lv_obj_set_flex_flow(battery_wrap, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(battery_wrap, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_column(battery_wrap, 4, 0); g_display_ui.battery_icon = lv_label_create(battery_wrap); lv_obj_set_style_text_font(g_display_ui.battery_icon, &ui_font_keyboard_small_18, 0); g_display_ui.battery_label = lv_label_create(battery_wrap); lv_obj_set_style_text_font(g_display_ui.battery_label, &ui_font_keyboard_small_18, 0); lv_obj_set_style_text_color(g_display_ui.battery_label, lv_color_hex(0xD8DEE9), 0); g_display_ui.battery_state_label = lv_label_create(battery_wrap); lv_obj_set_style_text_font(g_display_ui.battery_state_label, &ui_font_keyboard_small_18, 0); middle_row = lv_obj_create(content); lv_obj_remove_style_all(middle_row); lv_obj_set_width(middle_row, LV_PCT(100)); lv_obj_set_flex_grow(middle_row, 2); lv_obj_set_style_bg_color(middle_row, lv_color_hex(0x0F1115), 0); lv_obj_set_style_bg_opa(middle_row, LV_OPA_TRANSP, 0); g_display_ui.time_label = lv_label_create(middle_row); lv_obj_set_style_text_font(g_display_ui.time_label, &ui_font_keyboard_time_48, 0); lv_obj_set_style_text_color(g_display_ui.time_label, lv_color_white(), 0); lv_obj_center(g_display_ui.time_label); bottom_row = lv_obj_create(content); lv_obj_remove_style_all(bottom_row); lv_obj_set_width(bottom_row, LV_PCT(100)); lv_obj_set_flex_grow(bottom_row, 1); lv_obj_set_style_bg_color(bottom_row, lv_color_hex(0x0F1115), 0); lv_obj_set_style_bg_opa(bottom_row, LV_OPA_TRANSP, 0); lv_obj_set_layout(bottom_row, LV_LAYOUT_FLEX); lv_obj_set_flex_flow(bottom_row, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(bottom_row, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_column(bottom_row, 6, 0); for (uint32_t i = 0; i < DISPLAY_STATUS_COUNT; i++) { display_ui_create_status_chip(bottom_row, (enum display_status_id)i); } display_ui_refresh_all(model, date_text, time_text); }