#include #include #include #include "ui_main.h" enum ui_status_id { UI_STATUS_USB = 0, UI_STATUS_BLE, UI_STATUS_NUMLOCK, UI_STATUS_CAPSLOCK, UI_STATUS_COUNT, }; enum { UI_LED_MASK_NUM_LOCK = BIT(0), UI_LED_MASK_CAPS_LOCK = BIT(1), }; struct ui_main_ctx { lv_obj_t *content; lv_obj_t *status_badges[UI_STATUS_COUNT]; lv_obj_t *status_labels[UI_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 ui_main_ctx g_ui; static bool ui_initialized; static const struct ui_main_model *page_model; static const char *page_date_text; static const char *page_time_text; static const char *const status_texts[UI_STATUS_COUNT] = { LV_SYMBOL_USB, LV_SYMBOL_BLUETOOTH, "1", "A", }; static lv_color_t ui_main_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 *ui_main_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 ui_main_status_is_active(enum ui_status_id id, const struct ui_main_model *model) { switch (id) { case UI_STATUS_USB: return model->mode == MODE_SWITCH_USB; case UI_STATUS_BLE: return model->mode == MODE_SWITCH_BLE; case UI_STATUS_NUMLOCK: return (model->led_mask & UI_LED_MASK_NUM_LOCK) != 0U; case UI_STATUS_CAPSLOCK: return (model->led_mask & UI_LED_MASK_CAPS_LOCK) != 0U; default: return false; } } static void ui_main_create_status_chip(lv_obj_t *parent, enum ui_status_id id) { lv_obj_t *badge = lv_obj_create(parent); lv_obj_t *label = lv_label_create(badge); 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); lv_label_set_text(label, status_texts[id]); lv_obj_set_width(label, LV_PCT(100)); lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0); lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); lv_obj_center(label); g_ui.status_badges[id] = badge; g_ui.status_labels[id] = label; } void ui_main_refresh_status_bar(const struct ui_main_model *model) { if (!ui_initialized) { return; } for (uint32_t i = 0; i < UI_STATUS_COUNT; i++) { lv_obj_t *badge = g_ui.status_badges[i]; lv_obj_t *label = g_ui.status_labels[i]; bool active = ui_main_status_is_active((enum ui_status_id)i, model); if ((badge == NULL) || (label == NULL)) { continue; } lv_obj_set_style_border_width(badge, 3, 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 ui_main_refresh_battery(const struct ui_main_model *model) { char battery_text[8]; const char *state_symbol = ""; lv_color_t battery_color; lv_color_t state_color = lv_color_white(); if (!ui_initialized || (g_ui.battery_icon == NULL) || (g_ui.battery_label == NULL) || (g_ui.battery_state_label == NULL)) { return; } battery_color = ui_main_get_battery_color(model->battery_level); snprintk(battery_text, sizeof(battery_text), "%u%%", model->battery_level); if (model->full) { state_symbol = LV_SYMBOL_USB; state_color = lv_color_hex(0x4C9EF5); } else if (model->charging) { state_symbol = LV_SYMBOL_CHARGE; state_color = lv_color_hex(0xF4D35E); } lv_label_set_text(g_ui.battery_icon, ui_main_get_battery_symbol(model->battery_level)); lv_obj_set_style_text_color(g_ui.battery_icon, battery_color, 0); lv_label_set_text(g_ui.battery_label, battery_text); lv_label_set_text(g_ui.battery_state_label, state_symbol); lv_obj_set_style_text_color(g_ui.battery_state_label, state_color, 0); } void ui_main_refresh_datetime(const char *date_text, const char *time_text) { if (!ui_initialized || (g_ui.date_label == NULL) || (g_ui.time_label == NULL)) { return; } lv_label_set_text(g_ui.date_label, date_text); lv_label_set_text(g_ui.time_label, time_text); } void ui_main_refresh_all(const struct ui_main_model *model, const char *date_text, const char *time_text) { ui_main_refresh_status_bar(model); ui_main_refresh_battery(model); ui_main_refresh_datetime(date_text, time_text); } void ui_main_init(const struct ui_main_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; if (ui_initialized) { ui_main_refresh_all(model, date_text, time_text); return; } memset(&g_ui, 0, sizeof(g_ui)); lv_obj_clean(screen); lv_obj_set_style_bg_color(screen, lv_color_hex(0x0F1115), 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); g_ui.content = content; lv_obj_remove_style_all(content); lv_obj_set_size(content, LV_PCT(100), LV_PCT(100)); 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_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_ui.date_label = lv_label_create(top_row); lv_obj_set_style_text_font(g_ui.date_label, &lv_font_montserrat_14, 0); lv_obj_set_style_text_color(g_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_ui.battery_icon = lv_label_create(battery_wrap); lv_obj_set_style_text_font(g_ui.battery_icon, &lv_font_montserrat_14, 0); g_ui.battery_label = lv_label_create(battery_wrap); lv_obj_set_style_text_font(g_ui.battery_label, &lv_font_montserrat_14, 0); lv_obj_set_style_text_color(g_ui.battery_label, lv_color_hex(0xD8DEE9), 0); g_ui.battery_state_label = lv_label_create(battery_wrap); lv_obj_set_style_text_font(g_ui.battery_state_label, &lv_font_montserrat_14, 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_opa(middle_row, LV_OPA_TRANSP, 0); g_ui.time_label = lv_label_create(middle_row); lv_obj_set_style_text_font(g_ui.time_label, &lv_font_montserrat_32, 0); lv_obj_set_style_text_color(g_ui.time_label, lv_color_white(), 0); lv_obj_center(g_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_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 < UI_STATUS_COUNT; i++) { ui_main_create_status_chip(bottom_row, (enum ui_status_id)i); } ui_main_refresh_all(model, date_text, time_text); ui_initialized = true; } void ui_main_deinit(void) { if (!ui_initialized) { return; } if (g_ui.content != NULL) { lv_obj_delete(g_ui.content); } memset(&g_ui, 0, sizeof(g_ui)); ui_initialized = false; } static void main_page_init(struct ui_page *page) { ARG_UNUSED(page); ui_main_init(page_model, page_date_text, page_time_text); } static void main_page_deinit(struct ui_page *page) { ARG_UNUSED(page); ui_main_deinit(); } static void main_page_refresh(struct ui_page *page) { ARG_UNUSED(page); ui_main_refresh_all(page_model, page_date_text, page_time_text); } static const struct ui_page_ops main_page_ops = { .init = main_page_init, .deinit = main_page_deinit, .refresh = main_page_refresh, }; static struct ui_page main_page = { .ops = &main_page_ops, }; struct ui_page *ui_main_page_get(const struct ui_main_model *model, const char *date_text, const char *time_text) { page_model = model; page_date_text = date_text; page_time_text = time_text; return &main_page; }