feat: 添加设置模块和相关UI功能
- 新增settings_module.c实现设置菜单逻辑,包括蓝牙配对槽位管理和主题颜色选择 - 添加settings_mode_event.h/.c和settings_view_event.h/.c事件定义用于设置模式切换 - 创建settings_ui.h定义设置界面状态结构体和页面枚举 - 修改display_module.c集成设置UI显示逻辑,支持主界面和设置界面切换 - 在keyboard_core_module.c中添加设置活动状态检查,避免设置模式下键盘输入冲突 - 更新CMakeLists.txt包含新的源文件:settings_module.c、ui_settings.c及新事件文件 - 修改prj.conf调整LVGL内存池大小从16KB到32KB以支持更复杂UI渲染 - 移除BLE配对擦除相关配置选项并增加长按检测时间到1500毫秒 - 更新ui_main.c添加可见性控制函数用于界面切换
This commit is contained in:
@@ -35,7 +35,9 @@ target_sources(app PRIVATE
|
|||||||
src/led_strip_module.c
|
src/led_strip_module.c
|
||||||
src/mode_policy_module.c
|
src/mode_policy_module.c
|
||||||
src/time_sync_module.c
|
src/time_sync_module.c
|
||||||
|
src/settings_module.c
|
||||||
src/ui/ui_main.c
|
src/ui/ui_main.c
|
||||||
|
src/ui/ui_settings.c
|
||||||
src/protocol_module.c
|
src/protocol_module.c
|
||||||
src/usb_cdc_module.c
|
src/usb_cdc_module.c
|
||||||
src/usb_device_module.c
|
src/usb_device_module.c
|
||||||
@@ -58,6 +60,8 @@ target_sources(app PRIVATE
|
|||||||
src/events/proto_transport_state_event.c
|
src/events/proto_transport_state_event.c
|
||||||
src/events/proto_tx_event.c
|
src/events/proto_tx_event.c
|
||||||
src/events/set_protocol_event.c
|
src/events/set_protocol_event.c
|
||||||
|
src/events/settings_mode_event.c
|
||||||
|
src/events/settings_view_event.c
|
||||||
src/events/theme_rgb_update_event.c
|
src/events/theme_rgb_update_event.c
|
||||||
src/events/time_sync_event.c
|
src/events/time_sync_event.c
|
||||||
src/events/transport_policy_event.c
|
src/events/transport_policy_event.c
|
||||||
|
|||||||
30
inc/events/settings_mode_event.h
Normal file
30
inc/events/settings_mode_event.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef BLINKY_SETTINGS_MODE_EVENT_H_
|
||||||
|
#define BLINKY_SETTINGS_MODE_EVENT_H_
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct settings_mode_event {
|
||||||
|
struct app_event_header header;
|
||||||
|
bool active;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DECLARE(settings_mode_event);
|
||||||
|
|
||||||
|
static inline void submit_settings_mode_event(bool active)
|
||||||
|
{
|
||||||
|
struct settings_mode_event *event = new_settings_mode_event();
|
||||||
|
|
||||||
|
event->active = active;
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* BLINKY_SETTINGS_MODE_EVENT_H_ */
|
||||||
40
inc/events/settings_view_event.h
Normal file
40
inc/events/settings_view_event.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef BLINKY_SETTINGS_VIEW_EVENT_H_
|
||||||
|
#define BLINKY_SETTINGS_VIEW_EVENT_H_
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
|
||||||
|
#include "settings_ui.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct settings_view_event {
|
||||||
|
struct app_event_header header;
|
||||||
|
struct settings_ui_state state;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DECLARE(settings_view_event);
|
||||||
|
|
||||||
|
static inline void submit_settings_view_event(
|
||||||
|
const struct settings_ui_state *state)
|
||||||
|
{
|
||||||
|
struct settings_view_event *event;
|
||||||
|
|
||||||
|
if (state == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event = new_settings_view_event();
|
||||||
|
memcpy(&event->state, state, sizeof(event->state));
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* BLINKY_SETTINGS_VIEW_EVENT_H_ */
|
||||||
56
inc/settings_ui.h
Normal file
56
inc/settings_ui.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef BLINKY_SETTINGS_UI_H_
|
||||||
|
#define BLINKY_SETTINGS_UI_H_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "theme_color.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SETTINGS_UI_ROOT_ITEM_COUNT 2U
|
||||||
|
#define SETTINGS_UI_BLE_SLOT_COUNT 3U
|
||||||
|
#define SETTINGS_UI_BLE_ITEM_COUNT 4U
|
||||||
|
#define SETTINGS_UI_THEME_OPTION_COUNT 6U
|
||||||
|
#define SETTINGS_UI_TITLE_MAX 20U
|
||||||
|
#define SETTINGS_UI_VALUE_MAX 24U
|
||||||
|
#define SETTINGS_UI_THEME_NAME_MAX 16U
|
||||||
|
|
||||||
|
enum settings_ui_page {
|
||||||
|
SETTINGS_UI_PAGE_ROOT = 0,
|
||||||
|
SETTINGS_UI_PAGE_BLE,
|
||||||
|
SETTINGS_UI_PAGE_THEME,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct settings_ui_list_item {
|
||||||
|
char title[SETTINGS_UI_TITLE_MAX];
|
||||||
|
char value[SETTINGS_UI_VALUE_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct settings_ui_theme_option {
|
||||||
|
char name[SETTINGS_UI_THEME_NAME_MAX];
|
||||||
|
struct theme_rgb color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct settings_ui_state {
|
||||||
|
bool active;
|
||||||
|
enum settings_ui_page page;
|
||||||
|
uint8_t root_selected;
|
||||||
|
uint8_t ble_selected;
|
||||||
|
uint8_t theme_selected;
|
||||||
|
uint8_t active_ble_slot;
|
||||||
|
struct theme_rgb accent;
|
||||||
|
struct settings_ui_list_item root_items[SETTINGS_UI_ROOT_ITEM_COUNT];
|
||||||
|
struct settings_ui_list_item ble_items[SETTINGS_UI_BLE_ITEM_COUNT];
|
||||||
|
struct settings_ui_theme_option
|
||||||
|
theme_options[SETTINGS_UI_THEME_OPTION_COUNT];
|
||||||
|
uint8_t theme_option_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* BLINKY_SETTINGS_UI_H_ */
|
||||||
7
prj.conf
7
prj.conf
@@ -3,6 +3,7 @@ CONFIG_CAF_BUTTONS=y
|
|||||||
CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h"
|
CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h"
|
||||||
CONFIG_CAF_CLICK_DETECTOR=y
|
CONFIG_CAF_CLICK_DETECTOR=y
|
||||||
CONFIG_CAF_CLICK_DETECTOR_DEF_PATH="click_detector_def.h"
|
CONFIG_CAF_CLICK_DETECTOR_DEF_PATH="click_detector_def.h"
|
||||||
|
CONFIG_CAF_CLICK_DETECTOR_LONG_CLICK_MSEC=1500
|
||||||
CONFIG_GPIO=y
|
CONFIG_GPIO=y
|
||||||
CONFIG_I2C=y
|
CONFIG_I2C=y
|
||||||
CONFIG_LED=y
|
CONFIG_LED=y
|
||||||
@@ -110,10 +111,6 @@ CONFIG_CAF_BLE_ADV_FAST_ADV=y
|
|||||||
CONFIG_CAF_BLE_ADV_FILTER_ACCEPT_LIST=y
|
CONFIG_CAF_BLE_ADV_FILTER_ACCEPT_LIST=y
|
||||||
CONFIG_CAF_BLE_ADV_MODULE_SUSPEND_EVENTS=y
|
CONFIG_CAF_BLE_ADV_MODULE_SUSPEND_EVENTS=y
|
||||||
CONFIG_CAF_BLE_BOND=y
|
CONFIG_CAF_BLE_BOND=y
|
||||||
CONFIG_CAF_BLE_BOND_PEER_ERASE_CLICK=y
|
|
||||||
CONFIG_CAF_BLE_BOND_PEER_ERASE_CLICK_KEY_ID=0x180
|
|
||||||
CONFIG_CAF_BLE_BOND_PEER_ERASE_CLICK_LONG=y
|
|
||||||
CONFIG_CAF_BLE_BOND_PEER_ERASE_CLICK_TIMEOUT=-1
|
|
||||||
CONFIG_CAF_MODULE_SUSPEND_EVENTS=y
|
CONFIG_CAF_MODULE_SUSPEND_EVENTS=y
|
||||||
CONFIG_BT_ADV_PROV_FLAGS=y
|
CONFIG_BT_ADV_PROV_FLAGS=y
|
||||||
CONFIG_BT_ADV_PROV_GAP_APPEARANCE=y
|
CONFIG_BT_ADV_PROV_GAP_APPEARANCE=y
|
||||||
@@ -131,7 +128,7 @@ CONFIG_LV_COLOR_16_SWAP=y
|
|||||||
CONFIG_LV_Z_BITS_PER_PIXEL=16
|
CONFIG_LV_Z_BITS_PER_PIXEL=16
|
||||||
CONFIG_LV_Z_VDB_SIZE=25
|
CONFIG_LV_Z_VDB_SIZE=25
|
||||||
CONFIG_LV_Z_DOUBLE_VDB=y
|
CONFIG_LV_Z_DOUBLE_VDB=y
|
||||||
CONFIG_LV_Z_MEM_POOL_SIZE=16384
|
CONFIG_LV_Z_MEM_POOL_SIZE=32768
|
||||||
CONFIG_LV_USE_LABEL=y
|
CONFIG_LV_USE_LABEL=y
|
||||||
CONFIG_LV_FONT_MONTSERRAT_14=y
|
CONFIG_LV_FONT_MONTSERRAT_14=y
|
||||||
CONFIG_LV_FONT_MONTSERRAT_32=y
|
CONFIG_LV_FONT_MONTSERRAT_32=y
|
||||||
|
|||||||
@@ -18,9 +18,13 @@
|
|||||||
#include "hid_led_event.h"
|
#include "hid_led_event.h"
|
||||||
#include "module_lifecycle.h"
|
#include "module_lifecycle.h"
|
||||||
#include "mode_switch_event.h"
|
#include "mode_switch_event.h"
|
||||||
|
#include "settings_mode_event.h"
|
||||||
|
#include "settings_ui.h"
|
||||||
|
#include "settings_view_event.h"
|
||||||
#include "theme_rgb_update_event.h"
|
#include "theme_rgb_update_event.h"
|
||||||
#include "theme_color.h"
|
#include "theme_color.h"
|
||||||
#include "ui/ui_main.h"
|
#include "ui/ui_main.h"
|
||||||
|
#include "ui/ui_settings.h"
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||||
|
|
||||||
@@ -34,6 +38,9 @@ struct display_module_ctx {
|
|||||||
const struct device *backlight_dev;
|
const struct device *backlight_dev;
|
||||||
uint32_t backlight_idx;
|
uint32_t backlight_idx;
|
||||||
struct ui_main_model ui_model;
|
struct ui_main_model ui_model;
|
||||||
|
struct settings_ui_state settings_ui;
|
||||||
|
bool settings_active;
|
||||||
|
bool settings_ui_valid;
|
||||||
bool lvgl_initialized;
|
bool lvgl_initialized;
|
||||||
char date_text[DATETIME_EVENT_DATE_TEXT_LEN];
|
char date_text[DATETIME_EVENT_DATE_TEXT_LEN];
|
||||||
char time_text[DATETIME_EVENT_TIME_TEXT_LEN];
|
char time_text[DATETIME_EVENT_TIME_TEXT_LEN];
|
||||||
@@ -127,6 +134,7 @@ static int do_start(void)
|
|||||||
|
|
||||||
lvgl_lock();
|
lvgl_lock();
|
||||||
ui_main_init(&ctx.ui_model, ctx.date_text, ctx.time_text);
|
ui_main_init(&ctx.ui_model, ctx.date_text, ctx.time_text);
|
||||||
|
ui_settings_init(NULL);
|
||||||
lvgl_unlock();
|
lvgl_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,8 +175,16 @@ static void refresh_ui(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctx.settings_active && !ctx.settings_ui_valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lvgl_lock();
|
lvgl_lock();
|
||||||
|
if (ctx.settings_active) {
|
||||||
|
ui_settings_refresh(&ctx.settings_ui, true);
|
||||||
|
} else {
|
||||||
ui_main_refresh_all(&ctx.ui_model, ctx.date_text, ctx.time_text);
|
ui_main_refresh_all(&ctx.ui_model, ctx.date_text, ctx.time_text);
|
||||||
|
}
|
||||||
lvgl_unlock();
|
lvgl_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +223,47 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
|||||||
ctx.ui_model.theme_color = (lv_color_t)LV_COLOR_MAKE(event->theme.r,
|
ctx.ui_model.theme_color = (lv_color_t)LV_COLOR_MAKE(event->theme.r,
|
||||||
event->theme.g,
|
event->theme.g,
|
||||||
event->theme.b);
|
event->theme.b);
|
||||||
|
ctx.settings_ui.accent = event->theme;
|
||||||
|
refresh_ui();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_settings_mode_event(aeh)) {
|
||||||
|
const struct settings_mode_event *event =
|
||||||
|
cast_settings_mode_event(aeh);
|
||||||
|
|
||||||
|
ctx.settings_active = event->active;
|
||||||
|
ctx.settings_ui_valid = false;
|
||||||
|
if (!ctx.lvgl_initialized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lvgl_lock();
|
||||||
|
if (!ctx.settings_active) {
|
||||||
|
ui_settings_set_visible(false);
|
||||||
|
ui_main_set_visible(true);
|
||||||
|
ui_main_refresh_all(&ctx.ui_model, ctx.date_text,
|
||||||
|
ctx.time_text);
|
||||||
|
}
|
||||||
|
lvgl_unlock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_settings_view_event(aeh)) {
|
||||||
|
const struct settings_view_event *event =
|
||||||
|
cast_settings_view_event(aeh);
|
||||||
|
|
||||||
|
ctx.settings_ui = event->state;
|
||||||
|
ctx.settings_ui_valid = true;
|
||||||
|
if (ctx.settings_active && ctx.lvgl_initialized) {
|
||||||
|
lvgl_lock();
|
||||||
|
ui_settings_refresh(&ctx.settings_ui, false);
|
||||||
|
ui_main_set_visible(false);
|
||||||
|
ui_settings_set_visible(true);
|
||||||
|
lvgl_unlock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
refresh_ui();
|
refresh_ui();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -257,6 +314,8 @@ APP_EVENT_SUBSCRIBE(MODULE, datetime_event);
|
|||||||
APP_EVENT_SUBSCRIBE(MODULE, hid_led_event);
|
APP_EVENT_SUBSCRIBE(MODULE, hid_led_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, mode_switch_event);
|
APP_EVENT_SUBSCRIBE(MODULE, mode_switch_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, settings_mode_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, settings_view_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, theme_rgb_update_event);
|
APP_EVENT_SUBSCRIBE(MODULE, theme_rgb_update_event);
|
||||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
||||||
|
|||||||
29
src/events/settings_mode_event.c
Normal file
29
src/events/settings_mode_event.c
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#include "settings_mode_event.h"
|
||||||
|
|
||||||
|
static void log_settings_mode_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct settings_mode_event *event =
|
||||||
|
cast_settings_mode_event(aeh);
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(aeh, "active:%u", event->active);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void profile_settings_mode_event(struct log_event_buf *buf,
|
||||||
|
const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct settings_mode_event *event =
|
||||||
|
cast_settings_mode_event(aeh);
|
||||||
|
|
||||||
|
nrf_profiler_log_encode_uint8(buf, event->active ? 1U : 0U);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_INFO_DEFINE(settings_mode_event,
|
||||||
|
ENCODE(NRF_PROFILER_ARG_U8),
|
||||||
|
ENCODE("active"),
|
||||||
|
profile_settings_mode_event);
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(settings_mode_event,
|
||||||
|
log_settings_mode_event,
|
||||||
|
&settings_mode_event_info,
|
||||||
|
APP_EVENT_FLAGS_CREATE(
|
||||||
|
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
59
src/events/settings_view_event.c
Normal file
59
src/events/settings_view_event.c
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#include "settings_view_event.h"
|
||||||
|
|
||||||
|
static const char *page_name(enum settings_ui_page page)
|
||||||
|
{
|
||||||
|
switch (page) {
|
||||||
|
case SETTINGS_UI_PAGE_ROOT:
|
||||||
|
return "root";
|
||||||
|
case SETTINGS_UI_PAGE_BLE:
|
||||||
|
return "ble";
|
||||||
|
case SETTINGS_UI_PAGE_THEME:
|
||||||
|
return "theme";
|
||||||
|
default:
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_settings_view_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct settings_view_event *event =
|
||||||
|
cast_settings_view_event(aeh);
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(
|
||||||
|
aeh,
|
||||||
|
"active:%u page:%s root_sel:%u ble_sel:%u theme_sel:%u slot:%u",
|
||||||
|
event->state.active, page_name(event->state.page),
|
||||||
|
event->state.root_selected, event->state.ble_selected,
|
||||||
|
event->state.theme_selected, event->state.active_ble_slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void profile_settings_view_event(struct log_event_buf *buf,
|
||||||
|
const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct settings_view_event *event =
|
||||||
|
cast_settings_view_event(aeh);
|
||||||
|
|
||||||
|
nrf_profiler_log_encode_uint8(buf, event->state.active ? 1U : 0U);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, event->state.page);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, event->state.root_selected);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, event->state.ble_selected);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, event->state.theme_selected);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, event->state.active_ble_slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_INFO_DEFINE(settings_view_event,
|
||||||
|
ENCODE(NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8),
|
||||||
|
ENCODE("active", "page", "root_selected", "ble_selected",
|
||||||
|
"theme_selected", "active_ble_slot"),
|
||||||
|
profile_settings_view_event);
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(settings_view_event,
|
||||||
|
log_settings_view_event,
|
||||||
|
&settings_view_event_info,
|
||||||
|
APP_EVENT_FLAGS_CREATE(
|
||||||
|
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "keyboard_core.h"
|
#include "keyboard_core.h"
|
||||||
#include "keyboard_hid_report_event.h"
|
#include "keyboard_hid_report_event.h"
|
||||||
#include "module_lifecycle.h"
|
#include "module_lifecycle.h"
|
||||||
|
#include "settings_mode_event.h"
|
||||||
#include "set_protocol_event.h"
|
#include "set_protocol_event.h"
|
||||||
#include "transport_policy_event.h"
|
#include "transport_policy_event.h"
|
||||||
|
|
||||||
@@ -94,6 +95,7 @@ struct keyboard_core_module_ctx {
|
|||||||
enum keyboard_protocol_mode transport_protocol_modes[HID_TRANSPORT_COUNT];
|
enum keyboard_protocol_mode transport_protocol_modes[HID_TRANSPORT_COUNT];
|
||||||
enum hid_transport_policy current_transport;
|
enum hid_transport_policy current_transport;
|
||||||
bool mode_valid;
|
bool mode_valid;
|
||||||
|
bool settings_active;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int do_init(void);
|
static int do_init(void);
|
||||||
@@ -130,6 +132,7 @@ static struct keyboard_core_module_ctx ctx = {
|
|||||||
#define transport_protocol_modes ctx.transport_protocol_modes
|
#define transport_protocol_modes ctx.transport_protocol_modes
|
||||||
#define current_transport ctx.current_transport
|
#define current_transport ctx.current_transport
|
||||||
#define mode_valid ctx.mode_valid
|
#define mode_valid ctx.mode_valid
|
||||||
|
#define settings_active ctx.settings_active
|
||||||
#define running module_lifecycle_is_running(&ctx.lc)
|
#define running module_lifecycle_is_running(&ctx.lc)
|
||||||
|
|
||||||
static bool policy_to_transport(enum hid_transport_policy policy,
|
static bool policy_to_transport(enum hid_transport_policy policy,
|
||||||
@@ -364,6 +367,7 @@ static void submit_consumer_fifo_frame(uint16_t usage_id)
|
|||||||
enum mode_switch_mode mode;
|
enum mode_switch_mode mode;
|
||||||
|
|
||||||
if (!running || !mode_valid ||
|
if (!running || !mode_valid ||
|
||||||
|
settings_active ||
|
||||||
!transport_policy_to_mode(current_transport, &mode) ||
|
!transport_policy_to_mode(current_transport, &mode) ||
|
||||||
(protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) {
|
(protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) {
|
||||||
return;
|
return;
|
||||||
@@ -414,6 +418,10 @@ static void emit_keys_report(bool force)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings_active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) {
|
if (protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) {
|
||||||
build_boot_report(report_buf);
|
build_boot_report(report_buf);
|
||||||
report_size = KEYBOARD_BOOT_REPORT_SIZE;
|
report_size = KEYBOARD_BOOT_REPORT_SIZE;
|
||||||
@@ -445,6 +453,7 @@ static void emit_consumer_report(bool force)
|
|||||||
enum mode_switch_mode mode;
|
enum mode_switch_mode mode;
|
||||||
|
|
||||||
if (!mode_valid || !transport_policy_to_mode(current_transport, &mode) ||
|
if (!mode_valid || !transport_policy_to_mode(current_transport, &mode) ||
|
||||||
|
settings_active ||
|
||||||
(protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) {
|
(protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -518,6 +527,7 @@ static int do_init(void)
|
|||||||
reports_cache_invalidate();
|
reports_cache_invalidate();
|
||||||
function_usage_mask_clear();
|
function_usage_mask_clear();
|
||||||
mode_valid = false;
|
mode_valid = false;
|
||||||
|
settings_active = false;
|
||||||
transport_protocol_modes[HID_TRANSPORT_USB] =
|
transport_protocol_modes[HID_TRANSPORT_USB] =
|
||||||
KEYBOARD_PROTOCOL_MODE_REPORT;
|
KEYBOARD_PROTOCOL_MODE_REPORT;
|
||||||
transport_protocol_modes[HID_TRANSPORT_BLE] =
|
transport_protocol_modes[HID_TRANSPORT_BLE] =
|
||||||
@@ -562,6 +572,10 @@ static bool handle_button_event(const struct button_event *event)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings_active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
entry = keymap_get(event->key_id);
|
entry = keymap_get(event->key_id);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
LOG_WRN("Unmapped key id 0x%04x", event->key_id);
|
LOG_WRN("Unmapped key id 0x%04x", event->key_id);
|
||||||
@@ -639,6 +653,10 @@ static bool handle_encoder_event(const struct encoder_event *event)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings_active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (event->detents > 0) {
|
if (event->detents > 0) {
|
||||||
submit_consumer_pulse_frames(KEYBOARD_CONSUMER_CTRL_VOLUME_UP,
|
submit_consumer_pulse_frames(KEYBOARD_CONSUMER_CTRL_VOLUME_UP,
|
||||||
(uint8_t)event->detents);
|
(uint8_t)event->detents);
|
||||||
@@ -703,6 +721,27 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
|||||||
cast_transport_policy_event(aeh));
|
cast_transport_policy_event(aeh));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_settings_mode_event(aeh)) {
|
||||||
|
const struct settings_mode_event *event =
|
||||||
|
cast_settings_mode_event(aeh);
|
||||||
|
|
||||||
|
if (settings_active == event->active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings_active = event->active;
|
||||||
|
if (settings_active) {
|
||||||
|
if (mode_valid) {
|
||||||
|
emit_release_reports(current_transport);
|
||||||
|
}
|
||||||
|
emit_function_state_event();
|
||||||
|
keyboard_state_clear();
|
||||||
|
reports_cache_invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (is_module_state_event(aeh)) {
|
if (is_module_state_event(aeh)) {
|
||||||
const struct module_state_event *event = cast_module_state_event(aeh);
|
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||||
|
|
||||||
@@ -738,6 +777,7 @@ APP_EVENT_SUBSCRIBE(MODULE, button_event);
|
|||||||
APP_EVENT_SUBSCRIBE(MODULE, encoder_event);
|
APP_EVENT_SUBSCRIBE(MODULE, encoder_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, function_bitmap_update_event);
|
APP_EVENT_SUBSCRIBE(MODULE, function_bitmap_update_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, set_protocol_event);
|
APP_EVENT_SUBSCRIBE(MODULE, set_protocol_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, settings_mode_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, transport_policy_event);
|
APP_EVENT_SUBSCRIBE(MODULE, transport_policy_event);
|
||||||
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||||
|
|||||||
502
src/settings_module.c
Normal file
502
src/settings_module.c
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
|
||||||
|
#define MODULE settings_module
|
||||||
|
#include <caf/events/click_event.h>
|
||||||
|
#include <caf/events/module_state_event.h>
|
||||||
|
#include <caf/events/power_event.h>
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <zephyr/sys/printk.h>
|
||||||
|
|
||||||
|
#include "encoder_event.h"
|
||||||
|
#include "module_lifecycle.h"
|
||||||
|
#include "settings_mode_event.h"
|
||||||
|
#include "settings_ui.h"
|
||||||
|
#include "settings_view_event.h"
|
||||||
|
#include "theme_color.h"
|
||||||
|
#include "theme_rgb_update_event.h"
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
#define SETTINGS_MUTE_KEY_ID 0x180U
|
||||||
|
|
||||||
|
enum root_menu_item {
|
||||||
|
ROOT_MENU_ITEM_BLUETOOTH = 0,
|
||||||
|
ROOT_MENU_ITEM_THEME,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ble_menu_item {
|
||||||
|
BLE_MENU_ITEM_SLOT_1 = 0,
|
||||||
|
BLE_MENU_ITEM_SLOT_2,
|
||||||
|
BLE_MENU_ITEM_SLOT_3,
|
||||||
|
BLE_MENU_ITEM_ERASE_BOND,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct named_theme {
|
||||||
|
const char *name;
|
||||||
|
struct theme_rgb color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ble_slot_state {
|
||||||
|
char label[SETTINGS_UI_VALUE_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct settings_module_ctx {
|
||||||
|
struct module_lifecycle_ctx lc;
|
||||||
|
bool active;
|
||||||
|
enum settings_ui_page page;
|
||||||
|
uint8_t root_selected;
|
||||||
|
uint8_t ble_selected;
|
||||||
|
uint8_t theme_selected;
|
||||||
|
uint8_t active_ble_slot;
|
||||||
|
struct ble_slot_state ble_slots[SETTINGS_UI_BLE_SLOT_COUNT];
|
||||||
|
struct theme_rgb current_theme;
|
||||||
|
uint8_t current_theme_index;
|
||||||
|
bool theme_matches_palette;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int do_init(void);
|
||||||
|
static int do_start(void);
|
||||||
|
static int do_stop(void);
|
||||||
|
|
||||||
|
static const struct module_lifecycle_cfg lifecycle_cfg = {
|
||||||
|
.mode = ML_MODE_POWER,
|
||||||
|
.stopped_state = MODULE_STATE_STANDBY,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct module_lifecycle_ops lifecycle_ops = {
|
||||||
|
.do_init = do_init,
|
||||||
|
.do_start = do_start,
|
||||||
|
.do_stop = do_stop,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct named_theme theme_palette[SETTINGS_UI_THEME_OPTION_COUNT] = {
|
||||||
|
{
|
||||||
|
.name = "Red",
|
||||||
|
.color = { .r = 0xFF, .g = 0x00, .b = 0x00 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "Amber",
|
||||||
|
.color = { .r = 0xFF, .g = 0x95, .b = 0x00 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "Default",
|
||||||
|
.color = {
|
||||||
|
.r = BLINKY_THEME_DEFAULT_R,
|
||||||
|
.g = BLINKY_THEME_DEFAULT_G,
|
||||||
|
.b = BLINKY_THEME_DEFAULT_B,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "Green",
|
||||||
|
.color = { .r = 0x34, .g = 0xC7, .b = 0x59 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "Purple",
|
||||||
|
.color = { .r = 0xBF, .g = 0x5A, .b = 0xF2 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "White",
|
||||||
|
.color = { .r = 0xF2, .g = 0xF2, .b = 0xF7 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct settings_module_ctx ctx = {
|
||||||
|
.lc = {
|
||||||
|
.state = LC_UNINIT,
|
||||||
|
.cfg = &lifecycle_cfg,
|
||||||
|
.ops = &lifecycle_ops,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool theme_equal(const struct theme_rgb *lhs, const struct theme_rgb *rhs)
|
||||||
|
{
|
||||||
|
return (lhs->r == rhs->r) && (lhs->g == rhs->g) && (lhs->b == rhs->b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t wrap_index(uint8_t current, uint8_t count, int8_t delta)
|
||||||
|
{
|
||||||
|
int32_t next = current;
|
||||||
|
|
||||||
|
if (count == 0U) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
next += delta;
|
||||||
|
while (next < 0) {
|
||||||
|
next += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint8_t)(next % count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_text(char *dst, size_t dst_size, const char *src)
|
||||||
|
{
|
||||||
|
if ((dst == NULL) || (dst_size == 0U) || (src == NULL)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(dst, src, dst_size);
|
||||||
|
dst[dst_size - 1U] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *current_theme_name_get(void)
|
||||||
|
{
|
||||||
|
if (!ctx.theme_matches_palette) {
|
||||||
|
return "Custom";
|
||||||
|
}
|
||||||
|
|
||||||
|
return theme_palette[ctx.current_theme_index].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_theme_match_state(void)
|
||||||
|
{
|
||||||
|
ctx.theme_matches_palette = false;
|
||||||
|
ctx.current_theme_index = 0U;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < SETTINGS_UI_THEME_OPTION_COUNT; i++) {
|
||||||
|
if (!theme_equal(&ctx.current_theme, &theme_palette[i].color)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.current_theme_index = i;
|
||||||
|
ctx.theme_matches_palette = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void publish_view_state(void)
|
||||||
|
{
|
||||||
|
struct settings_ui_state state = {
|
||||||
|
.active = ctx.active,
|
||||||
|
.page = ctx.page,
|
||||||
|
.root_selected = ctx.root_selected,
|
||||||
|
.ble_selected = ctx.ble_selected,
|
||||||
|
.theme_selected = ctx.theme_selected,
|
||||||
|
.active_ble_slot = ctx.active_ble_slot,
|
||||||
|
.accent = ctx.current_theme,
|
||||||
|
.theme_option_count = SETTINGS_UI_THEME_OPTION_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
set_text(state.root_items[ROOT_MENU_ITEM_BLUETOOTH].title,
|
||||||
|
sizeof(state.root_items[ROOT_MENU_ITEM_BLUETOOTH].title),
|
||||||
|
"Bluetooth");
|
||||||
|
set_text(state.root_items[ROOT_MENU_ITEM_BLUETOOTH].value,
|
||||||
|
sizeof(state.root_items[ROOT_MENU_ITEM_BLUETOOTH].value),
|
||||||
|
(ctx.active_ble_slot == 0U) ? "Slot 1" :
|
||||||
|
(ctx.active_ble_slot == 1U) ? "Slot 2" : "Slot 3");
|
||||||
|
|
||||||
|
set_text(state.root_items[ROOT_MENU_ITEM_THEME].title,
|
||||||
|
sizeof(state.root_items[ROOT_MENU_ITEM_THEME].title), "Theme");
|
||||||
|
set_text(state.root_items[ROOT_MENU_ITEM_THEME].value,
|
||||||
|
sizeof(state.root_items[ROOT_MENU_ITEM_THEME].value),
|
||||||
|
current_theme_name_get());
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < SETTINGS_UI_BLE_SLOT_COUNT; i++) {
|
||||||
|
char slot_name[SETTINGS_UI_TITLE_MAX];
|
||||||
|
|
||||||
|
snprintk(slot_name, sizeof(slot_name), "Slot %u", i + 1U);
|
||||||
|
set_text(state.ble_items[i].title, sizeof(state.ble_items[i].title),
|
||||||
|
slot_name);
|
||||||
|
set_text(state.ble_items[i].value, sizeof(state.ble_items[i].value),
|
||||||
|
ctx.ble_slots[i].label);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_text(state.ble_items[BLE_MENU_ITEM_ERASE_BOND].title,
|
||||||
|
sizeof(state.ble_items[BLE_MENU_ITEM_ERASE_BOND].title),
|
||||||
|
"Erase Bond");
|
||||||
|
set_text(state.ble_items[BLE_MENU_ITEM_ERASE_BOND].value,
|
||||||
|
sizeof(state.ble_items[BLE_MENU_ITEM_ERASE_BOND].value),
|
||||||
|
(ctx.active_ble_slot == 0U) ? "Slot 1" :
|
||||||
|
(ctx.active_ble_slot == 1U) ? "Slot 2" : "Slot 3");
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < SETTINGS_UI_THEME_OPTION_COUNT; i++) {
|
||||||
|
set_text(state.theme_options[i].name,
|
||||||
|
sizeof(state.theme_options[i].name),
|
||||||
|
theme_palette[i].name);
|
||||||
|
state.theme_options[i].color = theme_palette[i].color;
|
||||||
|
}
|
||||||
|
|
||||||
|
submit_settings_view_event(&state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void settings_exit(void)
|
||||||
|
{
|
||||||
|
if (!ctx.active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.active = false;
|
||||||
|
ctx.page = SETTINGS_UI_PAGE_ROOT;
|
||||||
|
submit_settings_mode_event(false);
|
||||||
|
publish_view_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void settings_enter(void)
|
||||||
|
{
|
||||||
|
if (ctx.active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.active = true;
|
||||||
|
ctx.page = SETTINGS_UI_PAGE_ROOT;
|
||||||
|
ctx.root_selected = ROOT_MENU_ITEM_BLUETOOTH;
|
||||||
|
ctx.ble_selected = ctx.active_ble_slot;
|
||||||
|
ctx.theme_selected = ctx.current_theme_index;
|
||||||
|
submit_settings_mode_event(true);
|
||||||
|
publish_view_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void apply_theme_selection(uint8_t index)
|
||||||
|
{
|
||||||
|
if (index >= SETTINGS_UI_THEME_OPTION_COUNT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.theme_selected = index;
|
||||||
|
ctx.current_theme_index = index;
|
||||||
|
ctx.current_theme = theme_palette[index].color;
|
||||||
|
ctx.theme_matches_palette = true;
|
||||||
|
submit_theme_rgb_update_event(ctx.current_theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_confirm(void)
|
||||||
|
{
|
||||||
|
switch (ctx.page) {
|
||||||
|
case SETTINGS_UI_PAGE_ROOT:
|
||||||
|
ctx.page = (ctx.root_selected == ROOT_MENU_ITEM_BLUETOOTH) ?
|
||||||
|
SETTINGS_UI_PAGE_BLE :
|
||||||
|
SETTINGS_UI_PAGE_THEME;
|
||||||
|
ctx.ble_selected = ctx.active_ble_slot;
|
||||||
|
ctx.theme_selected = ctx.current_theme_index;
|
||||||
|
publish_view_state();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SETTINGS_UI_PAGE_BLE:
|
||||||
|
if (ctx.ble_selected < SETTINGS_UI_BLE_SLOT_COUNT) {
|
||||||
|
ctx.active_ble_slot = ctx.ble_selected;
|
||||||
|
} else {
|
||||||
|
set_text(ctx.ble_slots[ctx.active_ble_slot].label,
|
||||||
|
sizeof(ctx.ble_slots[ctx.active_ble_slot].label),
|
||||||
|
"Empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.page = SETTINGS_UI_PAGE_ROOT;
|
||||||
|
ctx.root_selected = ROOT_MENU_ITEM_BLUETOOTH;
|
||||||
|
publish_view_state();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SETTINGS_UI_PAGE_THEME:
|
||||||
|
apply_theme_selection(ctx.theme_selected);
|
||||||
|
ctx.page = SETTINGS_UI_PAGE_ROOT;
|
||||||
|
ctx.root_selected = ROOT_MENU_ITEM_THEME;
|
||||||
|
publish_view_state();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_back(void)
|
||||||
|
{
|
||||||
|
switch (ctx.page) {
|
||||||
|
case SETTINGS_UI_PAGE_ROOT:
|
||||||
|
settings_exit();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SETTINGS_UI_PAGE_BLE:
|
||||||
|
ctx.page = SETTINGS_UI_PAGE_ROOT;
|
||||||
|
ctx.root_selected = ROOT_MENU_ITEM_BLUETOOTH;
|
||||||
|
publish_view_state();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SETTINGS_UI_PAGE_THEME:
|
||||||
|
ctx.page = SETTINGS_UI_PAGE_ROOT;
|
||||||
|
ctx.root_selected = ROOT_MENU_ITEM_THEME;
|
||||||
|
publish_view_state();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_navigation(int8_t detents)
|
||||||
|
{
|
||||||
|
if (detents == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ctx.page) {
|
||||||
|
case SETTINGS_UI_PAGE_ROOT:
|
||||||
|
ctx.root_selected = wrap_index(ctx.root_selected,
|
||||||
|
SETTINGS_UI_ROOT_ITEM_COUNT,
|
||||||
|
detents);
|
||||||
|
publish_view_state();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SETTINGS_UI_PAGE_BLE:
|
||||||
|
ctx.ble_selected = wrap_index(ctx.ble_selected,
|
||||||
|
SETTINGS_UI_BLE_ITEM_COUNT,
|
||||||
|
detents);
|
||||||
|
publish_view_state();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SETTINGS_UI_PAGE_THEME:
|
||||||
|
ctx.theme_selected = wrap_index(ctx.theme_selected,
|
||||||
|
SETTINGS_UI_THEME_OPTION_COUNT,
|
||||||
|
detents);
|
||||||
|
publish_view_state();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_init(void)
|
||||||
|
{
|
||||||
|
ctx.active = false;
|
||||||
|
ctx.page = SETTINGS_UI_PAGE_ROOT;
|
||||||
|
ctx.root_selected = ROOT_MENU_ITEM_BLUETOOTH;
|
||||||
|
ctx.ble_selected = BLE_MENU_ITEM_SLOT_1;
|
||||||
|
ctx.theme_selected = 0U;
|
||||||
|
ctx.active_ble_slot = 0U;
|
||||||
|
ctx.current_theme = (struct theme_rgb) {
|
||||||
|
.r = BLINKY_THEME_DEFAULT_R,
|
||||||
|
.g = BLINKY_THEME_DEFAULT_G,
|
||||||
|
.b = BLINKY_THEME_DEFAULT_B,
|
||||||
|
};
|
||||||
|
update_theme_match_state();
|
||||||
|
set_text(ctx.ble_slots[0].label, sizeof(ctx.ble_slots[0].label), "Host A");
|
||||||
|
set_text(ctx.ble_slots[1].label, sizeof(ctx.ble_slots[1].label), "Host B");
|
||||||
|
set_text(ctx.ble_slots[2].label, sizeof(ctx.ble_slots[2].label), "Empty");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_start(void)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_stop(void)
|
||||||
|
{
|
||||||
|
settings_exit();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_click_event(const struct click_event *event)
|
||||||
|
{
|
||||||
|
if (!module_lifecycle_is_running(&ctx.lc) ||
|
||||||
|
(event->key_id != SETTINGS_MUTE_KEY_ID)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.active) {
|
||||||
|
if (event->click == CLICK_LONG) {
|
||||||
|
settings_enter();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event->click) {
|
||||||
|
case CLICK_SHORT:
|
||||||
|
handle_confirm();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CLICK_LONG:
|
||||||
|
handle_back();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_encoder_event(const struct encoder_event *event)
|
||||||
|
{
|
||||||
|
if (!module_lifecycle_is_running(&ctx.lc) || !ctx.active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_navigation(event->detents);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_theme_rgb_update_event(
|
||||||
|
const struct theme_rgb_update_event *event)
|
||||||
|
{
|
||||||
|
ctx.current_theme = event->theme;
|
||||||
|
update_theme_match_state();
|
||||||
|
|
||||||
|
if (ctx.page == SETTINGS_UI_PAGE_THEME) {
|
||||||
|
ctx.theme_selected = ctx.current_theme_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.active) {
|
||||||
|
publish_view_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool app_event_handler(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
if (is_click_event(aeh)) {
|
||||||
|
return handle_click_event(cast_click_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_encoder_event(aeh)) {
|
||||||
|
return handle_encoder_event(cast_encoder_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_theme_rgb_update_event(aeh)) {
|
||||||
|
return handle_theme_rgb_update_event(
|
||||||
|
cast_theme_rgb_update_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_module_state_event(aeh)) {
|
||||||
|
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||||
|
|
||||||
|
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
||||||
|
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_power_down_event(aeh)) {
|
||||||
|
if (module_lifecycle_is_initialized(&ctx.lc)) {
|
||||||
|
(void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_wake_up_event(aeh)) {
|
||||||
|
if (module_lifecycle_is_initialized(&ctx.lc)) {
|
||||||
|
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, click_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, encoder_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, theme_rgb_update_event);
|
||||||
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
||||||
@@ -19,6 +19,7 @@ enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct ui_main_ctx {
|
struct ui_main_ctx {
|
||||||
|
lv_obj_t *content;
|
||||||
lv_obj_t *status_badges[UI_STATUS_COUNT];
|
lv_obj_t *status_badges[UI_STATUS_COUNT];
|
||||||
lv_obj_t *status_labels[UI_STATUS_COUNT];
|
lv_obj_t *status_labels[UI_STATUS_COUNT];
|
||||||
lv_obj_t *battery_icon;
|
lv_obj_t *battery_icon;
|
||||||
@@ -116,6 +117,10 @@ static void ui_main_create_status_chip(lv_obj_t *parent, enum ui_status_id id)
|
|||||||
|
|
||||||
void ui_main_refresh_status_bar(const struct ui_main_model *model)
|
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++) {
|
for (uint32_t i = 0; i < UI_STATUS_COUNT; i++) {
|
||||||
lv_obj_t *badge = g_ui.status_badges[i];
|
lv_obj_t *badge = g_ui.status_badges[i];
|
||||||
lv_obj_t *label = g_ui.status_labels[i];
|
lv_obj_t *label = g_ui.status_labels[i];
|
||||||
@@ -145,7 +150,8 @@ void ui_main_refresh_battery(const struct ui_main_model *model)
|
|||||||
lv_color_t battery_color;
|
lv_color_t battery_color;
|
||||||
lv_color_t state_color = lv_color_white();
|
lv_color_t state_color = lv_color_white();
|
||||||
|
|
||||||
if ((g_ui.battery_icon == NULL) || (g_ui.battery_label == NULL) ||
|
if (!ui_initialized ||
|
||||||
|
(g_ui.battery_icon == NULL) || (g_ui.battery_label == NULL) ||
|
||||||
(g_ui.battery_state_label == NULL)) {
|
(g_ui.battery_state_label == NULL)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -171,7 +177,8 @@ void ui_main_refresh_battery(const struct ui_main_model *model)
|
|||||||
|
|
||||||
void ui_main_refresh_datetime(const char *date_text, const char *time_text)
|
void ui_main_refresh_datetime(const char *date_text, const char *time_text)
|
||||||
{
|
{
|
||||||
if ((g_ui.date_label == NULL) || (g_ui.time_label == NULL)) {
|
if (!ui_initialized ||
|
||||||
|
(g_ui.date_label == NULL) || (g_ui.time_label == NULL)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +207,7 @@ void ui_main_init(const struct ui_main_model *model,
|
|||||||
lv_obj_t *bottom_row;
|
lv_obj_t *bottom_row;
|
||||||
|
|
||||||
if (ui_initialized) {
|
if (ui_initialized) {
|
||||||
|
ui_main_refresh_all(model, date_text, time_text);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +221,7 @@ void ui_main_init(const struct ui_main_model *model,
|
|||||||
lv_obj_set_scrollbar_mode(screen, LV_SCROLLBAR_MODE_OFF);
|
lv_obj_set_scrollbar_mode(screen, LV_SCROLLBAR_MODE_OFF);
|
||||||
|
|
||||||
content = lv_obj_create(screen);
|
content = lv_obj_create(screen);
|
||||||
|
g_ui.content = content;
|
||||||
lv_obj_remove_style_all(content);
|
lv_obj_remove_style_all(content);
|
||||||
lv_obj_set_size(content, LV_PCT(100), LV_PCT(100));
|
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_bg_opa(content, LV_OPA_TRANSP, 0);
|
||||||
@@ -287,3 +296,16 @@ void ui_main_init(const struct ui_main_model *model,
|
|||||||
ui_main_refresh_all(model, date_text, time_text);
|
ui_main_refresh_all(model, date_text, time_text);
|
||||||
ui_initialized = true;
|
ui_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ui_main_set_visible(bool visible)
|
||||||
|
{
|
||||||
|
if (!ui_initialized || (g_ui.content == NULL)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
lv_obj_clear_flag(g_ui.content, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
} else {
|
||||||
|
lv_obj_add_flag(g_ui.content, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ void ui_main_refresh_all(const struct ui_main_model *model,
|
|||||||
void ui_main_refresh_status_bar(const struct ui_main_model *model);
|
void ui_main_refresh_status_bar(const struct ui_main_model *model);
|
||||||
void ui_main_refresh_battery(const struct ui_main_model *model);
|
void ui_main_refresh_battery(const struct ui_main_model *model);
|
||||||
void ui_main_refresh_datetime(const char *date_text, const char *time_text);
|
void ui_main_refresh_datetime(const char *date_text, const char *time_text);
|
||||||
|
void ui_main_set_visible(bool visible);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
316
src/ui/ui_settings.c
Normal file
316
src/ui/ui_settings.c
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
#include "ui_settings.h"
|
||||||
|
|
||||||
|
#define UI_SETTINGS_LIST_H 104
|
||||||
|
#define UI_SETTINGS_ROOT_MAX SETTINGS_UI_ROOT_ITEM_COUNT
|
||||||
|
#define UI_SETTINGS_BLE_MAX SETTINGS_UI_BLE_ITEM_COUNT
|
||||||
|
#define UI_SETTINGS_THEME_MAX SETTINGS_UI_THEME_OPTION_COUNT
|
||||||
|
|
||||||
|
struct ui_settings_row {
|
||||||
|
lv_obj_t *row;
|
||||||
|
lv_obj_t *icon;
|
||||||
|
lv_obj_t *swatch;
|
||||||
|
lv_obj_t *title;
|
||||||
|
lv_obj_t *value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ui_settings_ctx {
|
||||||
|
lv_obj_t *content;
|
||||||
|
lv_obj_t *title;
|
||||||
|
lv_obj_t *hint;
|
||||||
|
lv_obj_t *root_scroll;
|
||||||
|
lv_obj_t *ble_scroll;
|
||||||
|
lv_obj_t *theme_scroll;
|
||||||
|
struct ui_settings_row root_rows[UI_SETTINGS_ROOT_MAX];
|
||||||
|
struct ui_settings_row ble_rows[UI_SETTINGS_BLE_MAX];
|
||||||
|
struct ui_settings_row theme_rows[UI_SETTINGS_THEME_MAX];
|
||||||
|
lv_obj_t *value;
|
||||||
|
lv_group_t *focus_group;
|
||||||
|
bool initialized;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct ui_settings_ctx g_ui;
|
||||||
|
|
||||||
|
static void scroll_set_visible(lv_obj_t *scroll, bool visible)
|
||||||
|
{
|
||||||
|
if (scroll == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
lv_obj_clear_flag(scroll, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
} else {
|
||||||
|
lv_obj_add_flag(scroll, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void create_scroll(lv_obj_t *parent, lv_obj_t **scroll)
|
||||||
|
{
|
||||||
|
*scroll = lv_obj_create(parent);
|
||||||
|
lv_obj_remove_style_all(*scroll);
|
||||||
|
lv_obj_set_width(*scroll, LV_PCT(100));
|
||||||
|
lv_obj_set_height(*scroll, UI_SETTINGS_LIST_H);
|
||||||
|
lv_obj_set_style_bg_opa(*scroll, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_layout(*scroll, LV_LAYOUT_FLEX);
|
||||||
|
lv_obj_set_flex_flow(*scroll, LV_FLEX_FLOW_COLUMN);
|
||||||
|
lv_obj_set_flex_align(*scroll, LV_FLEX_ALIGN_START,
|
||||||
|
LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
|
||||||
|
lv_obj_set_style_pad_row(*scroll, 6, 0);
|
||||||
|
lv_obj_set_scroll_dir(*scroll, LV_DIR_VER);
|
||||||
|
lv_obj_set_scrollbar_mode(*scroll, LV_SCROLLBAR_MODE_OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void create_row(lv_obj_t *parent, struct ui_settings_row *row)
|
||||||
|
{
|
||||||
|
row->row = lv_obj_create(parent);
|
||||||
|
lv_obj_remove_style_all(row->row);
|
||||||
|
lv_obj_set_width(row->row, LV_PCT(100));
|
||||||
|
lv_obj_set_height(row->row, 30);
|
||||||
|
lv_obj_set_style_bg_opa(row->row, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_radius(row->row, 9, 0);
|
||||||
|
lv_obj_set_style_border_width(row->row, 2, 0);
|
||||||
|
lv_obj_set_style_border_color(row->row, lv_color_hex(0x0B1017), 0);
|
||||||
|
lv_obj_set_style_pad_hor(row->row, 6, 0);
|
||||||
|
lv_obj_set_layout(row->row, LV_LAYOUT_FLEX);
|
||||||
|
lv_obj_set_flex_flow(row->row, LV_FLEX_FLOW_ROW);
|
||||||
|
lv_obj_set_flex_align(row->row, LV_FLEX_ALIGN_START,
|
||||||
|
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
lv_obj_set_style_pad_column(row->row, 6, 0);
|
||||||
|
lv_obj_add_flag(row->row, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
|
||||||
|
lv_group_add_obj(g_ui.focus_group, row->row);
|
||||||
|
|
||||||
|
row->icon = lv_label_create(row->row);
|
||||||
|
lv_obj_set_width(row->icon, 20);
|
||||||
|
lv_obj_set_style_text_font(row->icon, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(row->icon, lv_color_hex(0x8A95A5), 0);
|
||||||
|
|
||||||
|
row->swatch = lv_obj_create(row->row);
|
||||||
|
lv_obj_remove_style_all(row->swatch);
|
||||||
|
lv_obj_set_size(row->swatch, 16, 16);
|
||||||
|
lv_obj_set_style_bg_opa(row->swatch, LV_OPA_COVER, 0);
|
||||||
|
lv_obj_set_style_bg_color(row->swatch, lv_color_hex(0x0B1017), 0);
|
||||||
|
|
||||||
|
row->title = lv_label_create(row->row);
|
||||||
|
lv_obj_set_width(row->title, 118);
|
||||||
|
lv_obj_set_style_text_font(row->title, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(row->title, lv_color_hex(0xD8DEE9), 0);
|
||||||
|
lv_label_set_long_mode(row->title, LV_LABEL_LONG_CLIP);
|
||||||
|
|
||||||
|
row->value = lv_label_create(row->row);
|
||||||
|
lv_obj_set_flex_grow(row->value, 1);
|
||||||
|
lv_obj_set_style_text_font(row->value, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(row->value, lv_color_hex(0x97A3B5), 0);
|
||||||
|
lv_obj_set_style_text_align(row->value, LV_TEXT_ALIGN_RIGHT, 0);
|
||||||
|
lv_label_set_long_mode(row->value, LV_LABEL_LONG_CLIP);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void row_set(struct ui_settings_row *row, const char *icon,
|
||||||
|
const char *title, const char *value, bool selected,
|
||||||
|
lv_color_t accent, const struct theme_rgb *swatch)
|
||||||
|
{
|
||||||
|
lv_label_set_text(row->icon, icon ? icon : "");
|
||||||
|
lv_label_set_text(row->title, title ? title : "");
|
||||||
|
lv_label_set_text(row->value, value ? value : "");
|
||||||
|
|
||||||
|
lv_obj_set_style_border_color(row->row,
|
||||||
|
selected ? accent : lv_color_hex(0x0B1017),
|
||||||
|
0);
|
||||||
|
lv_obj_set_style_text_color(row->icon, lv_color_hex(0x8A95A5), 0);
|
||||||
|
lv_obj_set_style_text_color(row->title, lv_color_hex(0xD8DEE9), 0);
|
||||||
|
lv_obj_set_style_text_color(row->value, lv_color_hex(0x97A3B5), 0);
|
||||||
|
|
||||||
|
if (swatch != NULL) {
|
||||||
|
lv_obj_set_style_bg_color(row->swatch,
|
||||||
|
lv_color_make(swatch->r, swatch->g,
|
||||||
|
swatch->b),
|
||||||
|
0);
|
||||||
|
} else {
|
||||||
|
lv_obj_set_style_bg_color(row->swatch, lv_color_hex(0x0B1017), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void focus_row(lv_obj_t *scroll, struct ui_settings_row *row,
|
||||||
|
bool animate)
|
||||||
|
{
|
||||||
|
lv_obj_update_layout(scroll);
|
||||||
|
lv_group_focus_obj(row->row);
|
||||||
|
lv_obj_scroll_to_view(row->row, animate ? LV_ANIM_ON : LV_ANIM_OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void refresh_root(const struct settings_ui_state *state,
|
||||||
|
lv_color_t accent, bool animate)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < UI_SETTINGS_ROOT_MAX; i++) {
|
||||||
|
bool selected = state->root_selected == i;
|
||||||
|
const char *icon = (i == 0U) ? LV_SYMBOL_BLUETOOTH : LV_SYMBOL_TINT;
|
||||||
|
|
||||||
|
row_set(&g_ui.root_rows[i], icon,
|
||||||
|
state->root_items[i].title, state->root_items[i].value,
|
||||||
|
selected, accent, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
focus_row(g_ui.root_scroll, &g_ui.root_rows[state->root_selected], animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void refresh_ble(const struct settings_ui_state *state,
|
||||||
|
lv_color_t accent, bool animate)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < UI_SETTINGS_BLE_MAX; i++) {
|
||||||
|
bool selected = state->ble_selected == i;
|
||||||
|
bool slot = i < SETTINGS_UI_BLE_SLOT_COUNT;
|
||||||
|
const char *icon = slot ? ((state->active_ble_slot == i) ?
|
||||||
|
LV_SYMBOL_OK : LV_SYMBOL_BLUETOOTH) :
|
||||||
|
LV_SYMBOL_TRASH;
|
||||||
|
|
||||||
|
row_set(&g_ui.ble_rows[i], icon,
|
||||||
|
state->ble_items[i].title, state->ble_items[i].value,
|
||||||
|
selected, accent, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
focus_row(g_ui.ble_scroll, &g_ui.ble_rows[state->ble_selected], animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void refresh_theme(const struct settings_ui_state *state,
|
||||||
|
lv_color_t accent, bool animate)
|
||||||
|
{
|
||||||
|
uint8_t count = state->theme_option_count;
|
||||||
|
uint8_t selected = state->theme_selected;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < UI_SETTINGS_THEME_MAX; i++) {
|
||||||
|
bool selected_row = i == selected;
|
||||||
|
|
||||||
|
if (i < count) {
|
||||||
|
row_set(&g_ui.theme_rows[i], LV_SYMBOL_TINT,
|
||||||
|
state->theme_options[i].name, "", selected_row, accent,
|
||||||
|
&state->theme_options[i].color);
|
||||||
|
} else {
|
||||||
|
row_set(&g_ui.theme_rows[i], "", "", "", false, accent, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected < count) {
|
||||||
|
focus_row(g_ui.theme_scroll, &g_ui.theme_rows[selected], animate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ui_settings_init(const struct settings_ui_state *state)
|
||||||
|
{
|
||||||
|
lv_obj_t *screen = lv_screen_active();
|
||||||
|
|
||||||
|
if (g_ui.initialized) {
|
||||||
|
ui_settings_refresh(state, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&g_ui, 0, sizeof(g_ui));
|
||||||
|
g_ui.focus_group = lv_group_create();
|
||||||
|
|
||||||
|
g_ui.content = lv_obj_create(screen);
|
||||||
|
lv_obj_remove_style_all(g_ui.content);
|
||||||
|
lv_obj_set_size(g_ui.content, LV_PCT(100), LV_PCT(100));
|
||||||
|
lv_obj_set_style_bg_color(g_ui.content, lv_color_hex(0x0B1017), 0);
|
||||||
|
lv_obj_set_style_bg_opa(g_ui.content, LV_OPA_COVER, 0);
|
||||||
|
lv_obj_set_style_pad_left(g_ui.content, 14, 0);
|
||||||
|
lv_obj_set_style_pad_right(g_ui.content, 14, 0);
|
||||||
|
lv_obj_set_style_pad_top(g_ui.content, 8, 0);
|
||||||
|
lv_obj_set_style_pad_bottom(g_ui.content, 8, 0);
|
||||||
|
lv_obj_set_layout(g_ui.content, LV_LAYOUT_FLEX);
|
||||||
|
lv_obj_set_flex_flow(g_ui.content, LV_FLEX_FLOW_COLUMN);
|
||||||
|
lv_obj_set_flex_align(g_ui.content, LV_FLEX_ALIGN_START,
|
||||||
|
LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
|
||||||
|
lv_obj_set_style_pad_row(g_ui.content, 6, 0);
|
||||||
|
|
||||||
|
g_ui.title = lv_label_create(g_ui.content);
|
||||||
|
lv_obj_set_style_text_font(g_ui.title, &lv_font_montserrat_32, 0);
|
||||||
|
lv_obj_set_style_text_color(g_ui.title, lv_color_white(), 0);
|
||||||
|
|
||||||
|
g_ui.hint = lv_label_create(g_ui.content);
|
||||||
|
lv_obj_set_style_text_font(g_ui.hint, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(g_ui.hint, lv_color_hex(0x97A3B5), 0);
|
||||||
|
lv_label_set_text(g_ui.hint, "Rotate select Tap OK Hold Back");
|
||||||
|
|
||||||
|
create_scroll(g_ui.content, &g_ui.root_scroll);
|
||||||
|
for (uint8_t i = 0; i < UI_SETTINGS_ROOT_MAX; i++) {
|
||||||
|
create_row(g_ui.root_scroll, &g_ui.root_rows[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
create_scroll(g_ui.content, &g_ui.ble_scroll);
|
||||||
|
for (uint8_t i = 0; i < UI_SETTINGS_BLE_MAX; i++) {
|
||||||
|
create_row(g_ui.ble_scroll, &g_ui.ble_rows[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
create_scroll(g_ui.content, &g_ui.theme_scroll);
|
||||||
|
for (uint8_t i = 0; i < UI_SETTINGS_THEME_MAX; i++) {
|
||||||
|
create_row(g_ui.theme_scroll, &g_ui.theme_rows[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ui.value = lv_label_create(g_ui.content);
|
||||||
|
lv_obj_set_width(g_ui.value, LV_PCT(100));
|
||||||
|
lv_obj_set_style_text_font(g_ui.value, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(g_ui.value, lv_color_hex(0x97A3B5), 0);
|
||||||
|
|
||||||
|
g_ui.initialized = true;
|
||||||
|
ui_settings_set_visible(false);
|
||||||
|
|
||||||
|
if (state != NULL) {
|
||||||
|
ui_settings_refresh(state, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ui_settings_refresh(const struct settings_ui_state *state, bool animate)
|
||||||
|
{
|
||||||
|
lv_color_t accent;
|
||||||
|
|
||||||
|
if ((state == NULL) || !g_ui.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
accent = lv_color_make(state->accent.r, state->accent.g, state->accent.b);
|
||||||
|
|
||||||
|
scroll_set_visible(g_ui.root_scroll, false);
|
||||||
|
scroll_set_visible(g_ui.ble_scroll, false);
|
||||||
|
scroll_set_visible(g_ui.theme_scroll, false);
|
||||||
|
|
||||||
|
switch (state->page) {
|
||||||
|
case SETTINGS_UI_PAGE_ROOT:
|
||||||
|
scroll_set_visible(g_ui.root_scroll, true);
|
||||||
|
lv_label_set_text(g_ui.title, LV_SYMBOL_SETTINGS " Settings");
|
||||||
|
lv_label_set_text(g_ui.value, "Short: open Hold: exit");
|
||||||
|
refresh_root(state, accent, animate);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SETTINGS_UI_PAGE_BLE:
|
||||||
|
scroll_set_visible(g_ui.ble_scroll, true);
|
||||||
|
lv_label_set_text(g_ui.title, LV_SYMBOL_BLUETOOTH " Bluetooth");
|
||||||
|
lv_label_set_text(g_ui.value, "Short: select and return");
|
||||||
|
refresh_ble(state, accent, animate);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SETTINGS_UI_PAGE_THEME:
|
||||||
|
scroll_set_visible(g_ui.theme_scroll, true);
|
||||||
|
lv_label_set_text(g_ui.title, LV_SYMBOL_TINT " Theme");
|
||||||
|
lv_label_set_text(g_ui.value, "Short: apply and return");
|
||||||
|
refresh_theme(state, accent, animate);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ui_settings_set_visible(bool visible)
|
||||||
|
{
|
||||||
|
if (!g_ui.initialized || (g_ui.content == NULL)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
lv_obj_clear_flag(g_ui.content, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
} else {
|
||||||
|
lv_obj_add_flag(g_ui.content, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/ui/ui_settings.h
Normal file
18
src/ui/ui_settings.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef BLINKY_UI_SETTINGS_H_
|
||||||
|
#define BLINKY_UI_SETTINGS_H_
|
||||||
|
|
||||||
|
#include "settings_ui.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ui_settings_init(const struct settings_ui_state *state);
|
||||||
|
void ui_settings_refresh(const struct settings_ui_state *state, bool animate);
|
||||||
|
void ui_settings_set_visible(bool visible);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* BLINKY_UI_SETTINGS_H_ */
|
||||||
Reference in New Issue
Block a user