Compare commits
2 Commits
6a03df1b39
...
48968e7880
| Author | SHA1 | Date | |
|---|---|---|---|
| 48968e7880 | |||
| fbdc5426be |
@@ -35,7 +35,13 @@ target_sources(app PRIVATE
|
||||
src/led_strip_module.c
|
||||
src/mode_policy_module.c
|
||||
src/time_sync_module.c
|
||||
src/settings_module.c
|
||||
src/ui/ui_main.c
|
||||
src/ui/ui_settings.c
|
||||
src/ui/ui_settings_controller.c
|
||||
src/ui/ui_settings_root.c
|
||||
src/ui/ui_settings_ble.c
|
||||
src/ui/ui_settings_theme.c
|
||||
src/protocol_module.c
|
||||
src/usb_cdc_module.c
|
||||
src/usb_device_module.c
|
||||
@@ -58,6 +64,8 @@ target_sources(app PRIVATE
|
||||
src/events/proto_transport_state_event.c
|
||||
src/events/proto_tx_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/time_sync_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_ */
|
||||
35
inc/events/settings_view_event.h
Normal file
35
inc/events/settings_view_event.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef BLINKY_SETTINGS_VIEW_EVENT_H_
|
||||
#define BLINKY_SETTINGS_VIEW_EVENT_H_
|
||||
|
||||
#include <app_event_manager.h>
|
||||
#include <app_event_manager_profiler_tracer.h>
|
||||
|
||||
#include "ui/ui_settings_page.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct settings_view_event {
|
||||
struct app_event_header header;
|
||||
struct ui_settings_page *page;
|
||||
bool animate;
|
||||
};
|
||||
|
||||
APP_EVENT_TYPE_DECLARE(settings_view_event);
|
||||
|
||||
static inline void submit_settings_view_event(struct ui_settings_page *page,
|
||||
bool animate)
|
||||
{
|
||||
struct settings_view_event *event = new_settings_view_event();
|
||||
|
||||
event->page = page;
|
||||
event->animate = animate;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLINKY_SETTINGS_VIEW_EVENT_H_ */
|
||||
66
inc/ui/ui_page.h
Normal file
66
inc/ui/ui_page.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#ifndef BLINKY_UI_PAGE_H_
|
||||
#define BLINKY_UI_PAGE_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ui_page;
|
||||
|
||||
struct ui_page_ops {
|
||||
void (*init)(struct ui_page *page);
|
||||
void (*deinit)(struct ui_page *page);
|
||||
void (*refresh)(struct ui_page *page);
|
||||
};
|
||||
|
||||
struct ui_page {
|
||||
const struct ui_page_ops *ops;
|
||||
struct ui_page *parent;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
static inline void ui_page_init(struct ui_page *page)
|
||||
{
|
||||
if ((page == NULL) || (page->ops == NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (page->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (page->ops->init != NULL) {
|
||||
page->ops->init(page);
|
||||
}
|
||||
page->initialized = true;
|
||||
}
|
||||
|
||||
static inline void ui_page_deinit(struct ui_page *page)
|
||||
{
|
||||
if ((page == NULL) || !page->initialized || (page->ops == NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (page->ops->deinit != NULL) {
|
||||
page->ops->deinit(page);
|
||||
}
|
||||
page->initialized = false;
|
||||
}
|
||||
|
||||
static inline void ui_page_refresh(struct ui_page *page)
|
||||
{
|
||||
if ((page == NULL) || !page->initialized ||
|
||||
(page->ops == NULL) || (page->ops->refresh == NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
page->ops->refresh(page);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLINKY_UI_PAGE_H_ */
|
||||
38
inc/ui/ui_settings_controller.h
Normal file
38
inc/ui/ui_settings_controller.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef BLINKY_UI_SETTINGS_CONTROLLER_H_
|
||||
#define BLINKY_UI_SETTINGS_CONTROLLER_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "theme_color.h"
|
||||
#include "ui_page.h"
|
||||
#include "ui_settings_page.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void ui_settings_controller_open(void);
|
||||
void ui_settings_controller_close(void);
|
||||
bool ui_settings_controller_back(void);
|
||||
void ui_settings_controller_select(void);
|
||||
void ui_settings_controller_move(int8_t delta);
|
||||
void ui_settings_controller_refresh(bool animate);
|
||||
bool ui_settings_controller_is_active(void);
|
||||
|
||||
void ui_settings_controller_switch_to(struct ui_settings_page *page,
|
||||
struct ui_page *parent);
|
||||
|
||||
const char *ui_settings_ble_current_label(void);
|
||||
const char *ui_settings_ble_slot_label(uint8_t slot);
|
||||
void ui_settings_ble_select_slot(uint8_t slot);
|
||||
void ui_settings_ble_erase_current(void);
|
||||
void ui_settings_theme_set_current(struct theme_rgb theme);
|
||||
const char *ui_settings_theme_current_name(void);
|
||||
uint8_t ui_settings_theme_current_index(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLINKY_UI_SETTINGS_CONTROLLER_H_ */
|
||||
56
inc/ui/ui_settings_page.h
Normal file
56
inc/ui/ui_settings_page.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef BLINKY_UI_SETTINGS_PAGE_H_
|
||||
#define BLINKY_UI_SETTINGS_PAGE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include "ui_page.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ui_settings_page;
|
||||
struct ui_settings_item;
|
||||
|
||||
typedef void (*ui_settings_item_draw_fn)(const struct ui_settings_item *item,
|
||||
lv_obj_t *container);
|
||||
|
||||
struct ui_settings_item {
|
||||
const char *icon;
|
||||
const char *title;
|
||||
const char *value;
|
||||
ui_settings_item_draw_fn draw;
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
struct ui_settings_page_ops {
|
||||
struct ui_page_ops base;
|
||||
|
||||
uint8_t (*get_count)(struct ui_settings_page *page);
|
||||
void (*get_item)(struct ui_settings_page *page, uint8_t index,
|
||||
struct ui_settings_item *item);
|
||||
void (*on_enter)(struct ui_settings_page *page);
|
||||
void (*on_select)(struct ui_settings_page *page, uint8_t index);
|
||||
void (*on_back)(struct ui_settings_page *page);
|
||||
};
|
||||
|
||||
struct ui_settings_page {
|
||||
struct ui_page base;
|
||||
const struct ui_settings_page_ops *ops;
|
||||
const char *title;
|
||||
const char *hint;
|
||||
uint8_t selected;
|
||||
};
|
||||
|
||||
static inline struct ui_settings_page *ui_page_to_settings(struct ui_page *page)
|
||||
{
|
||||
return (struct ui_settings_page *)page;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLINKY_UI_SETTINGS_PAGE_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_CLICK_DETECTOR=y
|
||||
CONFIG_CAF_CLICK_DETECTOR_DEF_PATH="click_detector_def.h"
|
||||
CONFIG_CAF_CLICK_DETECTOR_LONG_CLICK_MSEC=1500
|
||||
CONFIG_GPIO=y
|
||||
CONFIG_I2C=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_MODULE_SUSPEND_EVENTS=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_BT_ADV_PROV_FLAGS=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_VDB_SIZE=25
|
||||
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_FONT_MONTSERRAT_14=y
|
||||
CONFIG_LV_FONT_MONTSERRAT_32=y
|
||||
|
||||
@@ -18,9 +18,13 @@
|
||||
#include "hid_led_event.h"
|
||||
#include "module_lifecycle.h"
|
||||
#include "mode_switch_event.h"
|
||||
#include "settings_mode_event.h"
|
||||
#include "settings_view_event.h"
|
||||
#include "theme_rgb_update_event.h"
|
||||
#include "theme_color.h"
|
||||
#include "ui/ui_page.h"
|
||||
#include "ui/ui_main.h"
|
||||
#include "ui/ui_settings.h"
|
||||
|
||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
|
||||
@@ -34,6 +38,8 @@ struct display_module_ctx {
|
||||
const struct device *backlight_dev;
|
||||
uint32_t backlight_idx;
|
||||
struct ui_main_model ui_model;
|
||||
struct ui_settings_page *settings_page;
|
||||
bool settings_active;
|
||||
bool lvgl_initialized;
|
||||
char date_text[DATETIME_EVENT_DATE_TEXT_LEN];
|
||||
char time_text[DATETIME_EVENT_TIME_TEXT_LEN];
|
||||
@@ -74,6 +80,11 @@ static struct display_module_ctx ctx = {
|
||||
.time_text = "00:00:00",
|
||||
};
|
||||
|
||||
static struct ui_page *main_page(void)
|
||||
{
|
||||
return ui_main_page_get(&ctx.ui_model, ctx.date_text, ctx.time_text);
|
||||
}
|
||||
|
||||
static int backlight_set(bool on)
|
||||
{
|
||||
if (on) {
|
||||
@@ -126,10 +137,16 @@ static int do_start(void)
|
||||
ctx.lvgl_initialized = true;
|
||||
|
||||
lvgl_lock();
|
||||
ui_main_init(&ctx.ui_model, ctx.date_text, ctx.time_text);
|
||||
ui_page_init(main_page());
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
if (!ctx.settings_active) {
|
||||
ui_page_init(main_page());
|
||||
}
|
||||
lvgl_unlock();
|
||||
|
||||
err = backlight_set(true);
|
||||
if (err) {
|
||||
LOG_ERR("Backlight enable failed (%d)", err);
|
||||
@@ -168,7 +185,11 @@ static void refresh_ui(void)
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
ui_main_refresh_all(&ctx.ui_model, ctx.date_text, ctx.time_text);
|
||||
if (ctx.settings_active && (ctx.settings_page != NULL)) {
|
||||
ui_settings_show(ctx.settings_page, true);
|
||||
} else {
|
||||
ui_page_refresh(main_page());
|
||||
}
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
@@ -211,6 +232,48 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_settings_mode_event(aeh)) {
|
||||
const struct settings_mode_event *event =
|
||||
cast_settings_mode_event(aeh);
|
||||
|
||||
ctx.settings_active = event->active;
|
||||
if (!ctx.settings_active) {
|
||||
ctx.settings_page = NULL;
|
||||
}
|
||||
if (!ctx.lvgl_initialized) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
if (ctx.settings_active) {
|
||||
ui_page_deinit(main_page());
|
||||
ui_settings_init();
|
||||
if (ctx.settings_page != NULL) {
|
||||
ui_settings_show(ctx.settings_page, false);
|
||||
}
|
||||
} else {
|
||||
ui_settings_deinit();
|
||||
ui_page_init(main_page());
|
||||
ui_page_refresh(main_page());
|
||||
}
|
||||
lvgl_unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_settings_view_event(aeh)) {
|
||||
const struct settings_view_event *event =
|
||||
cast_settings_view_event(aeh);
|
||||
|
||||
ctx.settings_page = event->page;
|
||||
if (ctx.settings_active && ctx.lvgl_initialized) {
|
||||
lvgl_lock();
|
||||
ui_settings_show(ctx.settings_page, event->animate);
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_datetime_event(aeh)) {
|
||||
const struct datetime_event *event = cast_datetime_event(aeh);
|
||||
|
||||
@@ -257,6 +320,8 @@ APP_EVENT_SUBSCRIBE(MODULE, datetime_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, hid_led_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, module_state_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_EARLY(MODULE, power_down_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));
|
||||
33
src/events/settings_view_event.c
Normal file
33
src/events/settings_view_event.c
Normal file
@@ -0,0 +1,33 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "settings_view_event.h"
|
||||
|
||||
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, "page:%p animate:%u",
|
||||
event->page, event->animate);
|
||||
}
|
||||
|
||||
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_uint32(buf, (uint32_t)(uintptr_t)event->page);
|
||||
nrf_profiler_log_encode_uint8(buf, event->animate ? 1U : 0U);
|
||||
}
|
||||
|
||||
APP_EVENT_INFO_DEFINE(settings_view_event,
|
||||
ENCODE(NRF_PROFILER_ARG_U32, NRF_PROFILER_ARG_U8),
|
||||
ENCODE("page", "animate"),
|
||||
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_hid_report_event.h"
|
||||
#include "module_lifecycle.h"
|
||||
#include "settings_mode_event.h"
|
||||
#include "set_protocol_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 hid_transport_policy current_transport;
|
||||
bool mode_valid;
|
||||
bool settings_active;
|
||||
};
|
||||
|
||||
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 current_transport ctx.current_transport
|
||||
#define mode_valid ctx.mode_valid
|
||||
#define settings_active ctx.settings_active
|
||||
#define running module_lifecycle_is_running(&ctx.lc)
|
||||
|
||||
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;
|
||||
|
||||
if (!running || !mode_valid ||
|
||||
settings_active ||
|
||||
!transport_policy_to_mode(current_transport, &mode) ||
|
||||
(protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) {
|
||||
return;
|
||||
@@ -414,6 +418,10 @@ static void emit_keys_report(bool force)
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) {
|
||||
build_boot_report(report_buf);
|
||||
report_size = KEYBOARD_BOOT_REPORT_SIZE;
|
||||
@@ -445,6 +453,7 @@ static void emit_consumer_report(bool force)
|
||||
enum mode_switch_mode mode;
|
||||
|
||||
if (!mode_valid || !transport_policy_to_mode(current_transport, &mode) ||
|
||||
settings_active ||
|
||||
(protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) {
|
||||
return;
|
||||
}
|
||||
@@ -518,6 +527,7 @@ static int do_init(void)
|
||||
reports_cache_invalidate();
|
||||
function_usage_mask_clear();
|
||||
mode_valid = false;
|
||||
settings_active = false;
|
||||
transport_protocol_modes[HID_TRANSPORT_USB] =
|
||||
KEYBOARD_PROTOCOL_MODE_REPORT;
|
||||
transport_protocol_modes[HID_TRANSPORT_BLE] =
|
||||
@@ -562,6 +572,10 @@ static bool handle_button_event(const struct button_event *event)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (settings_active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entry = keymap_get(event->key_id);
|
||||
if (!entry) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (settings_active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->detents > 0) {
|
||||
submit_consumer_pulse_frames(KEYBOARD_CONSUMER_CTRL_VOLUME_UP,
|
||||
(uint8_t)event->detents);
|
||||
@@ -703,6 +721,27 @@ static bool app_event_handler(const struct app_event_header *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)) {
|
||||
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, function_bitmap_update_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, module_state_event);
|
||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||
|
||||
210
src/settings_module.c
Normal file
210
src/settings_module.c
Normal file
@@ -0,0 +1,210 @@
|
||||
#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 "encoder_event.h"
|
||||
#include "module_lifecycle.h"
|
||||
#include "settings_mode_event.h"
|
||||
#include "theme_color.h"
|
||||
#include "theme_rgb_update_event.h"
|
||||
#include "ui/ui_settings_controller.h"
|
||||
|
||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
|
||||
#define SETTINGS_MUTE_KEY_ID 0x180U
|
||||
|
||||
struct settings_module_ctx {
|
||||
struct module_lifecycle_ctx lc;
|
||||
bool active;
|
||||
struct theme_rgb current_theme;
|
||||
};
|
||||
|
||||
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 struct settings_module_ctx ctx = {
|
||||
.lc = {
|
||||
.state = LC_UNINIT,
|
||||
.cfg = &lifecycle_cfg,
|
||||
.ops = &lifecycle_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static void settings_exit(void)
|
||||
{
|
||||
if (!ctx.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.active = false;
|
||||
ui_settings_controller_close();
|
||||
submit_settings_mode_event(false);
|
||||
}
|
||||
|
||||
static void settings_enter(void)
|
||||
{
|
||||
if (ctx.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.active = true;
|
||||
submit_settings_mode_event(true);
|
||||
ui_settings_controller_open();
|
||||
}
|
||||
|
||||
static int do_init(void)
|
||||
{
|
||||
ctx.active = false;
|
||||
ctx.current_theme = (struct theme_rgb) {
|
||||
.r = BLINKY_THEME_DEFAULT_R,
|
||||
.g = BLINKY_THEME_DEFAULT_G,
|
||||
.b = BLINKY_THEME_DEFAULT_B,
|
||||
};
|
||||
ui_settings_theme_set_current(ctx.current_theme);
|
||||
|
||||
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:
|
||||
ui_settings_controller_select();
|
||||
break;
|
||||
|
||||
case CLICK_LONG:
|
||||
{
|
||||
bool active = ui_settings_controller_back();
|
||||
|
||||
if (!active) {
|
||||
settings_exit();
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
ui_settings_controller_move(event->detents);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_theme_rgb_update_event(
|
||||
const struct theme_rgb_update_event *event)
|
||||
{
|
||||
ctx.current_theme = event->theme;
|
||||
ui_settings_theme_set_current(event->theme);
|
||||
|
||||
if (ctx.active) {
|
||||
ui_settings_controller_refresh(false);
|
||||
}
|
||||
|
||||
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 {
|
||||
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;
|
||||
@@ -30,6 +31,9 @@ struct ui_main_ctx {
|
||||
|
||||
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,
|
||||
@@ -116,6 +120,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)
|
||||
{
|
||||
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];
|
||||
@@ -145,7 +153,8 @@ void ui_main_refresh_battery(const struct ui_main_model *model)
|
||||
lv_color_t battery_color;
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
@@ -171,7 +180,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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -200,6 +210,7 @@ void ui_main_init(const struct ui_main_model *model,
|
||||
lv_obj_t *bottom_row;
|
||||
|
||||
if (ui_initialized) {
|
||||
ui_main_refresh_all(model, date_text, time_text);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -213,6 +224,7 @@ void ui_main_init(const struct ui_main_model *model,
|
||||
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);
|
||||
@@ -287,3 +299,56 @@ void ui_main_init(const struct ui_main_model *model,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <lvgl.h>
|
||||
|
||||
#include "mode_switch_event.h"
|
||||
#include "ui/ui_page.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -31,6 +32,10 @@ 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_battery(const struct ui_main_model *model);
|
||||
void ui_main_refresh_datetime(const char *date_text, const char *time_text);
|
||||
void ui_main_deinit(void);
|
||||
struct ui_page *ui_main_page_get(const struct ui_main_model *model,
|
||||
const char *date_text,
|
||||
const char *time_text);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
233
src/ui/ui_settings.c
Normal file
233
src/ui/ui_settings.c
Normal file
@@ -0,0 +1,233 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include "ui_settings.h"
|
||||
|
||||
#define UI_SETTINGS_LIST_H 104
|
||||
#define UI_SETTINGS_MAX_ITEMS 8U
|
||||
|
||||
struct ui_settings_row {
|
||||
lv_obj_t *row;
|
||||
lv_obj_t *icon;
|
||||
lv_obj_t *title;
|
||||
lv_obj_t *value;
|
||||
lv_obj_t *custom;
|
||||
};
|
||||
|
||||
struct ui_settings_ctx {
|
||||
lv_obj_t *content;
|
||||
lv_obj_t *title;
|
||||
lv_obj_t *hint;
|
||||
lv_obj_t *scroll;
|
||||
lv_obj_t *value;
|
||||
struct ui_settings_row rows[UI_SETTINGS_MAX_ITEMS];
|
||||
lv_group_t *focus_group;
|
||||
uint8_t row_count;
|
||||
struct ui_settings_page *page;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
static struct ui_settings_ctx g_ui;
|
||||
|
||||
static void row_clear_custom(struct ui_settings_row *row)
|
||||
{
|
||||
lv_obj_clean(row->custom);
|
||||
lv_obj_set_style_bg_opa(row->custom, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_bg_color(row->custom, lv_color_hex(0x0B1017), 0);
|
||||
}
|
||||
|
||||
static void create_row(struct ui_settings_row *row)
|
||||
{
|
||||
row->row = lv_obj_create(g_ui.scroll);
|
||||
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->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);
|
||||
|
||||
row->custom = lv_obj_create(row->row);
|
||||
lv_obj_remove_style_all(row->custom);
|
||||
lv_obj_set_size(row->custom, 16, 16);
|
||||
row_clear_custom(row);
|
||||
}
|
||||
|
||||
static void rebuild_rows(uint8_t count)
|
||||
{
|
||||
if (count > UI_SETTINGS_MAX_ITEMS) {
|
||||
count = UI_SETTINGS_MAX_ITEMS;
|
||||
}
|
||||
|
||||
if (g_ui.row_count == count) {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_obj_clean(g_ui.scroll);
|
||||
memset(g_ui.rows, 0, sizeof(g_ui.rows));
|
||||
g_ui.row_count = count;
|
||||
lv_group_delete(g_ui.focus_group);
|
||||
g_ui.focus_group = lv_group_create();
|
||||
|
||||
for (uint8_t i = 0; i < g_ui.row_count; i++) {
|
||||
create_row(&g_ui.rows[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void row_set(struct ui_settings_row *row,
|
||||
const struct ui_settings_item *item,
|
||||
bool selected,
|
||||
lv_color_t accent)
|
||||
{
|
||||
lv_label_set_text(row->icon, item->icon ? item->icon : "");
|
||||
lv_label_set_text(row->title, item->title ? item->title : "");
|
||||
lv_label_set_text(row->value, item->value ? item->value : "");
|
||||
lv_obj_set_style_border_color(row->row,
|
||||
selected ? accent : lv_color_hex(0x0B1017),
|
||||
0);
|
||||
|
||||
row_clear_custom(row);
|
||||
if (item->draw != NULL) {
|
||||
item->draw(item, row->custom);
|
||||
}
|
||||
}
|
||||
|
||||
static void focus_row(uint8_t index, bool animate)
|
||||
{
|
||||
if (index >= g_ui.row_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_obj_update_layout(g_ui.scroll);
|
||||
lv_group_focus_obj(g_ui.rows[index].row);
|
||||
lv_obj_scroll_to_view(g_ui.rows[index].row,
|
||||
animate ? LV_ANIM_ON : LV_ANIM_OFF);
|
||||
}
|
||||
|
||||
void ui_settings_init(void)
|
||||
{
|
||||
lv_obj_t *screen = lv_screen_active();
|
||||
|
||||
if (g_ui.initialized) {
|
||||
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);
|
||||
|
||||
g_ui.scroll = lv_obj_create(g_ui.content);
|
||||
lv_obj_remove_style_all(g_ui.scroll);
|
||||
lv_obj_set_width(g_ui.scroll, LV_PCT(100));
|
||||
lv_obj_set_height(g_ui.scroll, UI_SETTINGS_LIST_H);
|
||||
lv_obj_set_style_bg_opa(g_ui.scroll, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_layout(g_ui.scroll, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(g_ui.scroll, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(g_ui.scroll, LV_FLEX_ALIGN_START,
|
||||
LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
|
||||
lv_obj_set_style_pad_row(g_ui.scroll, 6, 0);
|
||||
lv_obj_set_scroll_dir(g_ui.scroll, LV_DIR_VER);
|
||||
lv_obj_set_scrollbar_mode(g_ui.scroll, LV_SCROLLBAR_MODE_OFF);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void ui_settings_show(struct ui_settings_page *page, bool animate)
|
||||
{
|
||||
uint8_t count;
|
||||
lv_color_t accent = lv_color_hex(0x4C9EF5);
|
||||
|
||||
if ((page == NULL) || !g_ui.initialized ||
|
||||
(page->ops == NULL) || (page->ops->get_count == NULL) ||
|
||||
(page->ops->get_item == NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
count = page->ops->get_count(page);
|
||||
rebuild_rows(count);
|
||||
g_ui.page = page;
|
||||
|
||||
lv_label_set_text(g_ui.title, page->title ? page->title : "");
|
||||
lv_label_set_text(g_ui.hint, page->hint ? page->hint : "");
|
||||
lv_label_set_text(g_ui.value, "");
|
||||
|
||||
for (uint8_t i = 0; i < g_ui.row_count; i++) {
|
||||
struct ui_settings_item item = { 0 };
|
||||
|
||||
page->ops->get_item(page, i, &item);
|
||||
row_set(&g_ui.rows[i], &item, page->selected == i, accent);
|
||||
}
|
||||
|
||||
focus_row(page->selected, animate);
|
||||
}
|
||||
|
||||
void ui_settings_deinit(void)
|
||||
{
|
||||
if (!g_ui.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_ui.focus_group != NULL) {
|
||||
lv_group_delete(g_ui.focus_group);
|
||||
}
|
||||
|
||||
if (g_ui.content != NULL) {
|
||||
lv_obj_delete(g_ui.content);
|
||||
}
|
||||
|
||||
memset(&g_ui, 0, sizeof(g_ui));
|
||||
}
|
||||
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 "ui/ui_settings_page.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void ui_settings_init(void);
|
||||
void ui_settings_show(struct ui_settings_page *page, bool animate);
|
||||
void ui_settings_deinit(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLINKY_UI_SETTINGS_H_ */
|
||||
60
src/ui/ui_settings_ble.c
Normal file
60
src/ui/ui_settings_ble.c
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include "ui/ui_settings_controller.h"
|
||||
|
||||
static uint8_t ble_get_count(struct ui_settings_page *page)
|
||||
{
|
||||
ARG_UNUSED(page);
|
||||
return 4U;
|
||||
}
|
||||
|
||||
static void ble_get_item(struct ui_settings_page *page, uint8_t index,
|
||||
struct ui_settings_item *item)
|
||||
{
|
||||
ARG_UNUSED(page);
|
||||
|
||||
if (index < 3U) {
|
||||
static const char *const titles[] = {
|
||||
"Slot 1",
|
||||
"Slot 2",
|
||||
"Slot 3",
|
||||
};
|
||||
|
||||
item->icon = LV_SYMBOL_BLUETOOTH;
|
||||
item->title = titles[index];
|
||||
item->value = ui_settings_ble_slot_label(index);
|
||||
return;
|
||||
}
|
||||
|
||||
item->icon = LV_SYMBOL_TRASH;
|
||||
item->title = "Erase Bond";
|
||||
item->value = ui_settings_ble_current_label();
|
||||
}
|
||||
|
||||
static void ble_on_select(struct ui_settings_page *page, uint8_t index)
|
||||
{
|
||||
if (index < 3U) {
|
||||
ui_settings_ble_select_slot(index);
|
||||
} else {
|
||||
ui_settings_ble_erase_current();
|
||||
}
|
||||
|
||||
(void)ui_settings_controller_back();
|
||||
}
|
||||
|
||||
static const struct ui_settings_page_ops ble_ops = {
|
||||
.get_count = ble_get_count,
|
||||
.get_item = ble_get_item,
|
||||
.on_select = ble_on_select,
|
||||
};
|
||||
|
||||
struct ui_settings_page ui_settings_ble_page = {
|
||||
.base = {
|
||||
.ops = &ble_ops.base,
|
||||
},
|
||||
.ops = &ble_ops,
|
||||
.title = LV_SYMBOL_BLUETOOTH " Bluetooth",
|
||||
.hint = "Short: select and return",
|
||||
};
|
||||
205
src/ui/ui_settings_controller.c
Normal file
205
src/ui/ui_settings_controller.c
Normal file
@@ -0,0 +1,205 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include "ui/ui_settings_controller.h"
|
||||
#include "settings_view_event.h"
|
||||
#include "ui_settings_pages.h"
|
||||
|
||||
#define BLE_SLOT_COUNT 3U
|
||||
|
||||
struct controller_ctx {
|
||||
struct ui_settings_page *current;
|
||||
bool active;
|
||||
uint8_t active_ble_slot;
|
||||
char ble_labels[BLE_SLOT_COUNT][16];
|
||||
struct theme_rgb theme;
|
||||
};
|
||||
|
||||
static struct controller_ctx ctx = {
|
||||
.active_ble_slot = 0U,
|
||||
.ble_labels = {
|
||||
"Host A",
|
||||
"Host B",
|
||||
"Empty",
|
||||
},
|
||||
.theme = {
|
||||
.r = BLINKY_THEME_DEFAULT_R,
|
||||
.g = BLINKY_THEME_DEFAULT_G,
|
||||
.b = BLINKY_THEME_DEFAULT_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 publish_view(bool animate)
|
||||
{
|
||||
if (ctx.active && (ctx.current != NULL)) {
|
||||
submit_settings_view_event(ctx.current, animate);
|
||||
}
|
||||
}
|
||||
|
||||
void ui_settings_controller_switch_to(struct ui_settings_page *page,
|
||||
struct ui_page *parent)
|
||||
{
|
||||
if (page == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.current != NULL) {
|
||||
ui_page_deinit(&ctx.current->base);
|
||||
}
|
||||
|
||||
page->base.parent = parent;
|
||||
ctx.current = page;
|
||||
ui_page_init(&page->base);
|
||||
if ((page->ops != NULL) && (page->ops->on_enter != NULL)) {
|
||||
page->ops->on_enter(page);
|
||||
}
|
||||
}
|
||||
|
||||
void ui_settings_controller_open(void)
|
||||
{
|
||||
ctx.active = true;
|
||||
ui_settings_controller_switch_to(&ui_settings_root_page, NULL);
|
||||
publish_view(false);
|
||||
}
|
||||
|
||||
void ui_settings_controller_close(void)
|
||||
{
|
||||
if (ctx.current != NULL) {
|
||||
ui_page_deinit(&ctx.current->base);
|
||||
ctx.current = NULL;
|
||||
}
|
||||
|
||||
ctx.active = false;
|
||||
}
|
||||
|
||||
bool ui_settings_controller_back(void)
|
||||
{
|
||||
struct ui_page *parent;
|
||||
|
||||
if (ctx.current == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((ctx.current->ops != NULL) && (ctx.current->ops->on_back != NULL)) {
|
||||
ctx.current->ops->on_back(ctx.current);
|
||||
publish_view(true);
|
||||
return ctx.active;
|
||||
}
|
||||
|
||||
parent = ctx.current->base.parent;
|
||||
if (parent == NULL) {
|
||||
ui_settings_controller_close();
|
||||
return false;
|
||||
}
|
||||
|
||||
ui_settings_controller_switch_to(ui_page_to_settings(parent),
|
||||
parent->parent);
|
||||
publish_view(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ui_settings_controller_select(void)
|
||||
{
|
||||
if ((ctx.current == NULL) || (ctx.current->ops == NULL) ||
|
||||
(ctx.current->ops->on_select == NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.current->ops->on_select(ctx.current, ctx.current->selected);
|
||||
publish_view(true);
|
||||
}
|
||||
|
||||
void ui_settings_controller_move(int8_t delta)
|
||||
{
|
||||
uint8_t count;
|
||||
|
||||
if ((ctx.current == NULL) || (ctx.current->ops == NULL) ||
|
||||
(ctx.current->ops->get_count == NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
count = ctx.current->ops->get_count(ctx.current);
|
||||
ctx.current->selected = wrap_index(ctx.current->selected, count, delta);
|
||||
publish_view(true);
|
||||
}
|
||||
|
||||
void ui_settings_controller_refresh(bool animate)
|
||||
{
|
||||
if (ctx.current != NULL) {
|
||||
publish_view(animate);
|
||||
}
|
||||
}
|
||||
|
||||
bool ui_settings_controller_is_active(void)
|
||||
{
|
||||
return ctx.active;
|
||||
}
|
||||
|
||||
const char *ui_settings_ble_current_label(void)
|
||||
{
|
||||
return (ctx.active_ble_slot == 0U) ? "Slot 1" :
|
||||
(ctx.active_ble_slot == 1U) ? "Slot 2" : "Slot 3";
|
||||
}
|
||||
|
||||
void ui_settings_ble_select_slot(uint8_t slot)
|
||||
{
|
||||
if (slot < BLE_SLOT_COUNT) {
|
||||
ctx.active_ble_slot = slot;
|
||||
}
|
||||
}
|
||||
|
||||
void ui_settings_ble_erase_current(void)
|
||||
{
|
||||
strncpy(ctx.ble_labels[ctx.active_ble_slot], "Empty",
|
||||
sizeof(ctx.ble_labels[ctx.active_ble_slot]));
|
||||
ctx.ble_labels[ctx.active_ble_slot]
|
||||
[sizeof(ctx.ble_labels[ctx.active_ble_slot]) - 1U] = '\0';
|
||||
}
|
||||
|
||||
const char *ui_settings_ble_slot_label(uint8_t slot)
|
||||
{
|
||||
if (slot >= BLE_SLOT_COUNT) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return ctx.ble_labels[slot];
|
||||
}
|
||||
|
||||
void ui_settings_theme_set_current(struct theme_rgb theme)
|
||||
{
|
||||
ctx.theme = theme;
|
||||
}
|
||||
|
||||
const char *ui_settings_theme_current_name(void)
|
||||
{
|
||||
extern const char *ui_settings_theme_name_for_color(struct theme_rgb theme);
|
||||
|
||||
return ui_settings_theme_name_for_color(ctx.theme);
|
||||
}
|
||||
|
||||
uint8_t ui_settings_theme_current_index(void)
|
||||
{
|
||||
extern uint8_t ui_settings_theme_index_for_color(struct theme_rgb theme);
|
||||
|
||||
return ui_settings_theme_index_for_color(ctx.theme);
|
||||
}
|
||||
10
src/ui/ui_settings_pages.h
Normal file
10
src/ui/ui_settings_pages.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef BLINKY_UI_SETTINGS_PAGES_H_
|
||||
#define BLINKY_UI_SETTINGS_PAGES_H_
|
||||
|
||||
#include "ui/ui_settings_page.h"
|
||||
|
||||
extern struct ui_settings_page ui_settings_root_page;
|
||||
extern struct ui_settings_page ui_settings_ble_page;
|
||||
extern struct ui_settings_page ui_settings_theme_page;
|
||||
|
||||
#endif /* BLINKY_UI_SETTINGS_PAGES_H_ */
|
||||
64
src/ui/ui_settings_root.c
Normal file
64
src/ui/ui_settings_root.c
Normal file
@@ -0,0 +1,64 @@
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include "ui/ui_settings_controller.h"
|
||||
#include "ui_settings_pages.h"
|
||||
|
||||
static uint8_t root_get_count(struct ui_settings_page *page)
|
||||
{
|
||||
ARG_UNUSED(page);
|
||||
return 2U;
|
||||
}
|
||||
|
||||
static void root_get_item(struct ui_settings_page *page, uint8_t index,
|
||||
struct ui_settings_item *item)
|
||||
{
|
||||
ARG_UNUSED(page);
|
||||
|
||||
if (index == 0U) {
|
||||
item->icon = LV_SYMBOL_BLUETOOTH;
|
||||
item->title = "Bluetooth";
|
||||
item->value = ui_settings_ble_current_label();
|
||||
return;
|
||||
}
|
||||
|
||||
item->icon = LV_SYMBOL_TINT;
|
||||
item->title = "Theme";
|
||||
item->value = ui_settings_theme_current_name();
|
||||
}
|
||||
|
||||
static void root_on_enter(struct ui_settings_page *page)
|
||||
{
|
||||
page->selected = 0U;
|
||||
}
|
||||
|
||||
static void root_on_select(struct ui_settings_page *page, uint8_t index)
|
||||
{
|
||||
ui_settings_controller_switch_to(
|
||||
(index == 0U) ? &ui_settings_ble_page : &ui_settings_theme_page,
|
||||
&page->base);
|
||||
}
|
||||
|
||||
static void root_on_back(struct ui_settings_page *page)
|
||||
{
|
||||
ARG_UNUSED(page);
|
||||
ui_settings_controller_close();
|
||||
}
|
||||
|
||||
static const struct ui_settings_page_ops root_ops = {
|
||||
.get_count = root_get_count,
|
||||
.get_item = root_get_item,
|
||||
.on_enter = root_on_enter,
|
||||
.on_select = root_on_select,
|
||||
.on_back = root_on_back,
|
||||
};
|
||||
|
||||
struct ui_settings_page ui_settings_root_page = {
|
||||
.base = {
|
||||
.ops = &root_ops.base,
|
||||
},
|
||||
.ops = &root_ops,
|
||||
.title = LV_SYMBOL_SETTINGS " Settings",
|
||||
.hint = "Rotate select Tap OK Hold exit",
|
||||
};
|
||||
109
src/ui/ui_settings_theme.c
Normal file
109
src/ui/ui_settings_theme.c
Normal file
@@ -0,0 +1,109 @@
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include "theme_rgb_update_event.h"
|
||||
#include "ui/ui_settings_controller.h"
|
||||
|
||||
struct theme_option {
|
||||
const char *name;
|
||||
struct theme_rgb color;
|
||||
};
|
||||
|
||||
static const struct theme_option themes[] = {
|
||||
{ "Red", { .r = 0xFF, .g = 0x00, .b = 0x00 } },
|
||||
{ "Amber", { .r = 0xFF, .g = 0x95, .b = 0x00 } },
|
||||
{ "Default", { .r = BLINKY_THEME_DEFAULT_R,
|
||||
.g = BLINKY_THEME_DEFAULT_G,
|
||||
.b = BLINKY_THEME_DEFAULT_B } },
|
||||
{ "Green", { .r = 0x34, .g = 0xC7, .b = 0x59 } },
|
||||
{ "Purple", { .r = 0xBF, .g = 0x5A, .b = 0xF2 } },
|
||||
{ "White", { .r = 0xF2, .g = 0xF2, .b = 0xF7 } },
|
||||
};
|
||||
|
||||
static bool theme_equal(struct theme_rgb lhs, struct theme_rgb rhs)
|
||||
{
|
||||
return (lhs.r == rhs.r) && (lhs.g == rhs.g) && (lhs.b == rhs.b);
|
||||
}
|
||||
|
||||
uint8_t ui_settings_theme_index_for_color(struct theme_rgb theme)
|
||||
{
|
||||
for (uint8_t i = 0; i < ARRAY_SIZE(themes); i++) {
|
||||
if (theme_equal(theme, themes[i].color)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0U;
|
||||
}
|
||||
|
||||
const char *ui_settings_theme_name_for_color(struct theme_rgb theme)
|
||||
{
|
||||
for (uint8_t i = 0; i < ARRAY_SIZE(themes); i++) {
|
||||
if (theme_equal(theme, themes[i].color)) {
|
||||
return themes[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
return "Custom";
|
||||
}
|
||||
|
||||
static void draw_swatch(const struct ui_settings_item *item, lv_obj_t *container)
|
||||
{
|
||||
const struct theme_rgb *color = item->user_data;
|
||||
|
||||
if (color == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_obj_set_style_bg_opa(container, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_bg_color(container,
|
||||
lv_color_make(color->r, color->g, color->b), 0);
|
||||
}
|
||||
|
||||
static uint8_t theme_get_count(struct ui_settings_page *page)
|
||||
{
|
||||
ARG_UNUSED(page);
|
||||
return ARRAY_SIZE(themes);
|
||||
}
|
||||
|
||||
static void theme_get_item(struct ui_settings_page *page, uint8_t index,
|
||||
struct ui_settings_item *item)
|
||||
{
|
||||
ARG_UNUSED(page);
|
||||
|
||||
item->icon = LV_SYMBOL_TINT;
|
||||
item->title = themes[index].name;
|
||||
item->draw = draw_swatch;
|
||||
item->user_data = (void *)&themes[index].color;
|
||||
}
|
||||
|
||||
static void theme_on_enter(struct ui_settings_page *page)
|
||||
{
|
||||
page->selected = ui_settings_theme_current_index();
|
||||
}
|
||||
|
||||
static void theme_on_select(struct ui_settings_page *page, uint8_t index)
|
||||
{
|
||||
struct theme_rgb theme = themes[index].color;
|
||||
|
||||
ui_settings_theme_set_current(theme);
|
||||
submit_theme_rgb_update_event(theme);
|
||||
(void)ui_settings_controller_back();
|
||||
}
|
||||
|
||||
static const struct ui_settings_page_ops theme_ops = {
|
||||
.get_count = theme_get_count,
|
||||
.get_item = theme_get_item,
|
||||
.on_enter = theme_on_enter,
|
||||
.on_select = theme_on_select,
|
||||
};
|
||||
|
||||
struct ui_settings_page ui_settings_theme_page = {
|
||||
.base = {
|
||||
.ops = &theme_ops.base,
|
||||
},
|
||||
.ops = &theme_ops,
|
||||
.title = LV_SYMBOL_TINT " Theme",
|
||||
.hint = "Short: apply and return",
|
||||
};
|
||||
Reference in New Issue
Block a user