Add dark color scheme, backlight timeout, and L/R swap feature
- Invert UI colors: black background with white text, yellow highlights - Add 30-second backlight timeout with auto-wake on button press - Add Swap L/R menu option with checkbox to swap audio channels - Update volume page styling (yellow bar, full-screen container) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
/* device name */
|
/* device name */
|
||||||
#define TARGET_DEVICE_NAME "ESP_SPEAKER"
|
#define TARGET_DEVICE_NAME "ESP_SPEAKER"
|
||||||
#define LOCAL_DEVICE_NAME "ESP_A2DP_SRC"
|
#define LOCAL_DEVICE_NAME "SOUNDSHOT"
|
||||||
|
|
||||||
/* AVRCP used transaction label */
|
/* AVRCP used transaction label */
|
||||||
#define APP_RC_CT_TL_GET_CAPS (0)
|
#define APP_RC_CT_TL_GET_CAPS (0)
|
||||||
@@ -798,6 +798,8 @@ void generate_exp(uint8_t *buf, int len, float balance)
|
|||||||
//float rate_hz = MIN_RATE_HZ * powf(MAX_RATE_HZ / MIN_RATE_HZ, abs_balance);
|
//float rate_hz = MIN_RATE_HZ * powf(MAX_RATE_HZ / MIN_RATE_HZ, abs_balance);
|
||||||
float samples_per_click = SAMPLE_RATE / rate_hz;
|
float samples_per_click = SAMPLE_RATE / rate_hz;
|
||||||
|
|
||||||
|
bool swap_lr = system_getSwapLR();
|
||||||
|
|
||||||
for (int i = 0; i < samples_needed; i++) {
|
for (int i = 0; i < samples_needed; i++) {
|
||||||
int16_t left = 0;
|
int16_t left = 0;
|
||||||
int16_t right = 0;
|
int16_t right = 0;
|
||||||
@@ -814,8 +816,13 @@ void generate_exp(uint8_t *buf, int len, float balance)
|
|||||||
|
|
||||||
click_timer -= 1.0f;
|
click_timer -= 1.0f;
|
||||||
|
|
||||||
samples[i * 2 + 0] = left;
|
if (swap_lr) {
|
||||||
samples[i * 2 + 1] = right;
|
samples[i * 2 + 0] = right;
|
||||||
|
samples[i * 2 + 1] = left;
|
||||||
|
} else {
|
||||||
|
samples[i * 2 + 0] = left;
|
||||||
|
samples[i * 2 + 1] = right;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
187
main/gui.c
187
main/gui.c
@@ -65,6 +65,8 @@ static void createBubble(lv_obj_t * scr);
|
|||||||
static void build_scrollable_menu(void);
|
static void build_scrollable_menu(void);
|
||||||
static void currentFocusIndex(menu_context_t *ctx);
|
static void currentFocusIndex(menu_context_t *ctx);
|
||||||
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text);
|
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text);
|
||||||
|
static lv_obj_t * addMenuItemWithCheckbox(lv_obj_t *page, const char *text, bool checked, lv_obj_t **checkbox_out);
|
||||||
|
static void update_checkbox_focus_style(lv_obj_t *cb, bool focused);
|
||||||
static lv_obj_t* create_menu_container(void);
|
static lv_obj_t* create_menu_container(void);
|
||||||
static void show_bt_device_list(void);
|
static void show_bt_device_list(void);
|
||||||
static void show_volume_control(void);
|
static void show_volume_control(void);
|
||||||
@@ -103,6 +105,19 @@ static lv_obj_t *_currentPage = NULL;
|
|||||||
|
|
||||||
static GuiMode_t _mode = GUI_BUBBLE;
|
static GuiMode_t _mode = GUI_BUBBLE;
|
||||||
|
|
||||||
|
// Backlight timeout
|
||||||
|
#define BACKLIGHT_TIMEOUT_MS 30000
|
||||||
|
static TickType_t _lastButtonPress = 0;
|
||||||
|
static bool _backlightOn = true;
|
||||||
|
|
||||||
|
static void backlight_reset_timeout(void) {
|
||||||
|
_lastButtonPress = xTaskGetTickCount();
|
||||||
|
if (!_backlightOn) {
|
||||||
|
gpio_set_level(PIN_NUM_BK_LIGHT, 1);
|
||||||
|
_backlightOn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Menu navigation stack
|
// Menu navigation stack
|
||||||
#define MAX_MENU_STACK_SIZE 8
|
#define MAX_MENU_STACK_SIZE 8
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -137,6 +152,9 @@ static int _current_volume = 50; // Default volume (0-100)
|
|||||||
static lv_obj_t* _calibration_page = NULL;
|
static lv_obj_t* _calibration_page = NULL;
|
||||||
static lv_timer_t* _calibration_timer = NULL;
|
static lv_timer_t* _calibration_timer = NULL;
|
||||||
|
|
||||||
|
// Swap L/R checkbox
|
||||||
|
static lv_obj_t* _swapLR_checkbox = NULL;
|
||||||
|
|
||||||
// Hide *all* headers/back buttons that LVGL may show for this menu
|
// Hide *all* headers/back buttons that LVGL may show for this menu
|
||||||
static void menu_hide_headers(lv_obj_t *menu) {
|
static void menu_hide_headers(lv_obj_t *menu) {
|
||||||
if (!menu) return;
|
if (!menu) return;
|
||||||
@@ -200,6 +218,7 @@ static lv_obj_t* create_main_page(void) {
|
|||||||
|
|
||||||
addMenuItem(main_page, "Calibration");
|
addMenuItem(main_page, "Calibration");
|
||||||
addMenuItem(main_page, "Volume");
|
addMenuItem(main_page, "Volume");
|
||||||
|
addMenuItemWithCheckbox(main_page, "Swap L/R", system_getSwapLR(), &_swapLR_checkbox);
|
||||||
//addMenuItem(main_page, "About");
|
//addMenuItem(main_page, "About");
|
||||||
addMenuItem(main_page, "Exit");
|
addMenuItem(main_page, "Exit");
|
||||||
|
|
||||||
@@ -238,6 +257,7 @@ static void cleanup_menu(void) {
|
|||||||
_volume_page = NULL;
|
_volume_page = NULL;
|
||||||
_volume_bar = NULL;
|
_volume_bar = NULL;
|
||||||
_volume_label = NULL;
|
_volume_label = NULL;
|
||||||
|
_swapLR_checkbox = NULL;
|
||||||
if (_calibration_timer) {
|
if (_calibration_timer) {
|
||||||
lv_timer_del(_calibration_timer);
|
lv_timer_del(_calibration_timer);
|
||||||
_calibration_timer = NULL;
|
_calibration_timer = NULL;
|
||||||
@@ -257,7 +277,9 @@ static void create_lvgl_demo(void)
|
|||||||
lvgl_port_lock(0);
|
lvgl_port_lock(0);
|
||||||
// Create a screen with black background
|
// Create a screen with black background
|
||||||
lv_obj_t *scr = lv_scr_act();
|
lv_obj_t *scr = lv_scr_act();
|
||||||
|
lv_obj_set_style_bg_color(scr, lv_color_black(), 0);
|
||||||
|
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
||||||
|
|
||||||
createBubble(scr);
|
createBubble(scr);
|
||||||
// Menu will be created lazily when needed
|
// Menu will be created lazily when needed
|
||||||
|
|
||||||
@@ -462,6 +484,14 @@ static void btn_click_cb(lv_event_t * e) {
|
|||||||
menu_stack_push(_currentPage);
|
menu_stack_push(_currentPage);
|
||||||
show_volume_control();
|
show_volume_control();
|
||||||
UNLOCK();
|
UNLOCK();
|
||||||
|
} else if (strcmp(txt, "Swap L/R") == 0) {
|
||||||
|
// Toggle the swap L/R state
|
||||||
|
system_toggleSwapLR();
|
||||||
|
// Update the checkbox visual (now a label showing "X" or empty)
|
||||||
|
if (_swapLR_checkbox) {
|
||||||
|
lv_label_set_text(_swapLR_checkbox, system_getSwapLR() ? "X" : "");
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Swap L/R toggled to: %s", system_getSwapLR() ? "ON" : "OFF");
|
||||||
} else if (strcmp(txt, "Back") == 0) {
|
} else if (strcmp(txt, "Back") == 0) {
|
||||||
LOCK();
|
LOCK();
|
||||||
menu_go_back();
|
menu_go_back();
|
||||||
@@ -498,18 +528,18 @@ static void refresh_highlight(void) {
|
|||||||
|
|
||||||
if (lv_obj_has_flag(child, LV_OBJ_FLAG_USER_1))
|
if (lv_obj_has_flag(child, LV_OBJ_FLAG_USER_1))
|
||||||
{
|
{
|
||||||
lv_obj_set_style_bg_color(child, lv_color_hex(0xFF8800), 0);
|
lv_obj_set_style_bg_color(child, lv_color_hex(0xFFFF00), 0);
|
||||||
lv_obj_set_style_text_color(lv_obj_get_child(child,0),
|
lv_obj_set_style_text_color(lv_obj_get_child(child,0),
|
||||||
lv_color_white(), 0);
|
lv_color_black(), 0);
|
||||||
|
|
||||||
selected = child;
|
selected = child;
|
||||||
ESP_LOGI(TAG, "Selected");
|
ESP_LOGI(TAG, "Selected");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lv_obj_set_style_bg_color(child, lv_color_white(), 0);
|
lv_obj_set_style_bg_color(child, lv_color_black(), 0);
|
||||||
lv_obj_set_style_text_color(lv_obj_get_child(child,0),
|
lv_obj_set_style_text_color(lv_obj_get_child(child,0),
|
||||||
lv_color_black(), 0);
|
lv_color_white(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
@@ -560,6 +590,12 @@ static void menuInc(int inc)
|
|||||||
// Update scroll on old focused item (stop scrolling)
|
// Update scroll on old focused item (stop scrolling)
|
||||||
update_label_scroll(_menuContext.obj, false);
|
update_label_scroll(_menuContext.obj, false);
|
||||||
|
|
||||||
|
// Update checkbox style on old focused item (if it has one)
|
||||||
|
lv_obj_t *old_cb = lv_obj_get_user_data(_menuContext.obj);
|
||||||
|
if (old_cb) {
|
||||||
|
update_checkbox_focus_style(old_cb, false);
|
||||||
|
}
|
||||||
|
|
||||||
lv_obj_add_state(next, LV_STATE_FOCUSED);
|
lv_obj_add_state(next, LV_STATE_FOCUSED);
|
||||||
lv_obj_clear_state(_menuContext.obj, LV_STATE_FOCUSED);
|
lv_obj_clear_state(_menuContext.obj, LV_STATE_FOCUSED);
|
||||||
lv_obj_scroll_to_view(next, LV_ANIM_ON);
|
lv_obj_scroll_to_view(next, LV_ANIM_ON);
|
||||||
@@ -567,6 +603,12 @@ static void menuInc(int inc)
|
|||||||
// Update scroll on new focused item (start scrolling)
|
// Update scroll on new focused item (start scrolling)
|
||||||
update_label_scroll(next, true);
|
update_label_scroll(next, true);
|
||||||
|
|
||||||
|
// Update checkbox style on new focused item (if it has one)
|
||||||
|
lv_obj_t *new_cb = lv_obj_get_user_data(next);
|
||||||
|
if (new_cb) {
|
||||||
|
update_checkbox_focus_style(new_cb, true);
|
||||||
|
}
|
||||||
|
|
||||||
_menuContext.obj = next;
|
_menuContext.obj = next;
|
||||||
_menuContext.selected = search_index;
|
_menuContext.selected = search_index;
|
||||||
}
|
}
|
||||||
@@ -626,6 +668,83 @@ static void label_scroll_focus_cb(lv_event_t * e)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static lv_obj_t * addMenuItemWithCheckbox(lv_obj_t *page, const char *text, bool checked, lv_obj_t **checkbox_out)
|
||||||
|
{
|
||||||
|
if (!page || !text) {
|
||||||
|
ESP_LOGE(TAG, "Null parameters in addMenuItemWithCheckbox");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure styles are initialized
|
||||||
|
ensure_menu_styles();
|
||||||
|
|
||||||
|
lv_obj_t * btn = lv_btn_create(page);
|
||||||
|
if (!btn) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create button in addMenuItemWithCheckbox");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_obj_set_size(btn, LV_PCT(100), ROW_H);
|
||||||
|
|
||||||
|
lv_obj_add_style(btn, &_styleUnfocusedBtn, LV_PART_MAIN | LV_STATE_DEFAULT);
|
||||||
|
lv_obj_add_style(btn, &_styleFocusedBtn, LV_PART_MAIN | LV_STATE_FOCUSED);
|
||||||
|
|
||||||
|
lv_obj_add_state(btn, LV_STATE_DEFAULT);
|
||||||
|
|
||||||
|
lv_obj_set_style_radius(btn, 0, LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_border_width(btn, 0, LV_PART_MAIN);
|
||||||
|
lv_obj_clear_flag(btn, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
|
||||||
|
// Label on the left (same as regular menu item)
|
||||||
|
lv_obj_t * lbl = lv_label_create(btn);
|
||||||
|
if (!lbl) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create label in addMenuItemWithCheckbox");
|
||||||
|
lv_obj_del(btn);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_label_set_text(lbl, text);
|
||||||
|
lv_label_set_long_mode(lbl, LV_LABEL_LONG_CLIP);
|
||||||
|
lv_obj_set_width(lbl, LV_PCT(80));
|
||||||
|
lv_obj_set_style_radius(lbl, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||||||
|
lv_obj_set_style_border_width(lbl, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||||||
|
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 0, 0);
|
||||||
|
|
||||||
|
// Label on the right to act as checkbox (shows "X" when checked, empty when not)
|
||||||
|
lv_obj_t * cb = lv_label_create(btn);
|
||||||
|
lv_label_set_text(cb, checked ? "X" : "");
|
||||||
|
lv_obj_set_style_text_color(cb, lv_color_white(), 0); // Unfocused: white
|
||||||
|
lv_obj_align(cb, LV_ALIGN_RIGHT_MID, -4, 0);
|
||||||
|
|
||||||
|
if (checkbox_out) {
|
||||||
|
*checkbox_out = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add focus event handler to control scrolling
|
||||||
|
lv_obj_add_event_cb(btn, label_scroll_focus_cb, LV_EVENT_ALL, NULL);
|
||||||
|
|
||||||
|
// click callback
|
||||||
|
lv_obj_add_event_cb(btn, btn_click_cb, LV_EVENT_CLICKED, NULL);
|
||||||
|
|
||||||
|
// Store checkbox in user data for focus styling updates
|
||||||
|
lv_obj_set_user_data(btn, cb);
|
||||||
|
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update checkbox label color based on parent focus state
|
||||||
|
static void update_checkbox_focus_style(lv_obj_t *cb, bool focused) {
|
||||||
|
if (!cb) return;
|
||||||
|
|
||||||
|
if (focused) {
|
||||||
|
// Focused: black text (on yellow background)
|
||||||
|
lv_obj_set_style_text_color(cb, lv_color_black(), 0);
|
||||||
|
} else {
|
||||||
|
// Unfocused: white text (on black background)
|
||||||
|
lv_obj_set_style_text_color(cb, lv_color_white(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text)
|
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text)
|
||||||
{
|
{
|
||||||
if (!page || !text) {
|
if (!page || !text) {
|
||||||
@@ -719,12 +838,12 @@ static void ensure_menu_styles(void) {
|
|||||||
lv_style_init(&_styleFocusedBtn);
|
lv_style_init(&_styleFocusedBtn);
|
||||||
lv_style_init(&_styleUnfocusedBtn);
|
lv_style_init(&_styleUnfocusedBtn);
|
||||||
|
|
||||||
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0xff,0xff,0xff)); // white bg
|
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0x00,0x00,0x00)); // black bg
|
||||||
lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_black()); // black text
|
lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_white()); // white text
|
||||||
//lv_style_set_text_font(&_styleUnfocusedBtn, &lv_font_unscii_16); // larger font
|
//lv_style_set_text_font(&_styleUnfocusedBtn, &lv_font_unscii_16); // larger font
|
||||||
|
|
||||||
lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0x33,0x99,0xFF)); // blue bg
|
lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0xFF,0xFF,0x00)); // bright yellow bg
|
||||||
lv_style_set_text_color(&_styleFocusedBtn, lv_color_white()); // white text
|
lv_style_set_text_color(&_styleFocusedBtn, lv_color_black()); // black text
|
||||||
//lv_style_set_text_font(&_styleFocusedBtn, &lv_font_unscii_16); // larger font
|
//lv_style_set_text_font(&_styleFocusedBtn, &lv_font_unscii_16); // larger font
|
||||||
|
|
||||||
styles_initialized = true;
|
styles_initialized = true;
|
||||||
@@ -1066,9 +1185,9 @@ static lv_obj_t* create_bt_device_page(bool start_discovery) {
|
|||||||
lv_label_set_text(_bt_status_item, "Devices:");
|
lv_label_set_text(_bt_status_item, "Devices:");
|
||||||
lv_obj_set_width(_bt_status_item, lv_pct(100));
|
lv_obj_set_width(_bt_status_item, lv_pct(100));
|
||||||
lv_obj_set_height(_bt_status_item, 16); // Thinner header
|
lv_obj_set_height(_bt_status_item, 16); // Thinner header
|
||||||
lv_obj_set_style_bg_color(_bt_status_item, lv_color_make(0xE0, 0xE0, 0xE0), 0);
|
lv_obj_set_style_bg_color(_bt_status_item, lv_color_make(0x00, 0x00, 0x00), 0);
|
||||||
lv_obj_set_style_bg_opa(_bt_status_item, LV_OPA_COVER, 0);
|
lv_obj_set_style_bg_opa(_bt_status_item, LV_OPA_COVER, 0);
|
||||||
lv_obj_set_style_text_color(_bt_status_item, lv_color_black(), 0);
|
lv_obj_set_style_text_color(_bt_status_item, lv_color_white(), 0);
|
||||||
lv_obj_set_style_pad_left(_bt_status_item, 5, 0);
|
lv_obj_set_style_pad_left(_bt_status_item, 5, 0);
|
||||||
lv_obj_set_style_pad_top(_bt_status_item, 2, 0);
|
lv_obj_set_style_pad_top(_bt_status_item, 2, 0);
|
||||||
lv_obj_set_style_text_align(_bt_status_item, LV_TEXT_ALIGN_LEFT, 0);
|
lv_obj_set_style_text_align(_bt_status_item, LV_TEXT_ALIGN_LEFT, 0);
|
||||||
@@ -1086,7 +1205,7 @@ static lv_obj_t* create_bt_device_page(bool start_discovery) {
|
|||||||
lv_obj_set_style_outline_width(_bt_device_container, 0, LV_PART_MAIN);
|
lv_obj_set_style_outline_width(_bt_device_container, 0, LV_PART_MAIN);
|
||||||
lv_obj_set_style_pad_all(_bt_device_container, 0, LV_PART_MAIN);
|
lv_obj_set_style_pad_all(_bt_device_container, 0, LV_PART_MAIN);
|
||||||
lv_obj_set_style_pad_row(_bt_device_container, 0, LV_PART_MAIN);
|
lv_obj_set_style_pad_row(_bt_device_container, 0, LV_PART_MAIN);
|
||||||
lv_obj_set_style_bg_color(_bt_device_container, lv_color_white(), LV_PART_MAIN);
|
lv_obj_set_style_bg_color(_bt_device_container, lv_color_black(), LV_PART_MAIN);
|
||||||
lv_obj_set_style_bg_opa(_bt_device_container, LV_OPA_COVER, LV_PART_MAIN);
|
lv_obj_set_style_bg_opa(_bt_device_container, LV_OPA_COVER, LV_PART_MAIN);
|
||||||
lv_obj_set_flex_flow(_bt_device_container, LV_FLEX_FLOW_COLUMN);
|
lv_obj_set_flex_flow(_bt_device_container, LV_FLEX_FLOW_COLUMN);
|
||||||
lv_obj_set_scrollbar_mode(_bt_device_container, LV_SCROLLBAR_MODE_AUTO);
|
lv_obj_set_scrollbar_mode(_bt_device_container, LV_SCROLLBAR_MODE_AUTO);
|
||||||
@@ -1235,9 +1354,11 @@ static lv_obj_t* create_volume_page(void) {
|
|||||||
|
|
||||||
// Create a container to center everything vertically
|
// Create a container to center everything vertically
|
||||||
lv_obj_t* container = lv_obj_create(_volume_page);
|
lv_obj_t* container = lv_obj_create(_volume_page);
|
||||||
lv_obj_set_size(container, lv_pct(100), LV_SIZE_CONTENT);
|
lv_obj_set_size(container, lv_pct(100), lv_pct(100));
|
||||||
lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0);
|
lv_obj_set_style_bg_color(container, lv_color_black(), 0);
|
||||||
|
lv_obj_set_style_bg_opa(container, LV_OPA_COVER, 0);
|
||||||
lv_obj_set_style_border_width(container, 0, 0);
|
lv_obj_set_style_border_width(container, 0, 0);
|
||||||
|
lv_obj_set_style_radius(container, 0, 0);
|
||||||
lv_obj_set_style_pad_all(container, 0, 0);
|
lv_obj_set_style_pad_all(container, 0, 0);
|
||||||
lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN);
|
lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN);
|
||||||
lv_obj_set_flex_align(container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
lv_obj_set_flex_align(container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
@@ -1247,7 +1368,7 @@ static lv_obj_t* create_volume_page(void) {
|
|||||||
lv_obj_t* title = lv_label_create(container);
|
lv_obj_t* title = lv_label_create(container);
|
||||||
lv_label_set_text(title, "Volume Control");
|
lv_label_set_text(title, "Volume Control");
|
||||||
lv_obj_set_style_text_align(title, LV_TEXT_ALIGN_CENTER, 0);
|
lv_obj_set_style_text_align(title, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
lv_obj_set_style_text_color(title, lv_color_black(), 0);
|
lv_obj_set_style_text_color(title, lv_color_white(), 0);
|
||||||
lv_obj_set_style_pad_bottom(title, 8, 0);
|
lv_obj_set_style_pad_bottom(title, 8, 0);
|
||||||
|
|
||||||
// Create volume bar (progress bar)
|
// Create volume bar (progress bar)
|
||||||
@@ -1255,6 +1376,8 @@ static lv_obj_t* create_volume_page(void) {
|
|||||||
lv_obj_set_size(_volume_bar, 120, 20);
|
lv_obj_set_size(_volume_bar, 120, 20);
|
||||||
lv_bar_set_range(_volume_bar, 0, 100);
|
lv_bar_set_range(_volume_bar, 0, 100);
|
||||||
lv_bar_set_value(_volume_bar, _current_volume, LV_ANIM_OFF);
|
lv_bar_set_value(_volume_bar, _current_volume, LV_ANIM_OFF);
|
||||||
|
lv_obj_set_style_bg_color(_volume_bar, lv_color_make(0x40, 0x40, 0x40), LV_PART_MAIN); // Dark gray background
|
||||||
|
lv_obj_set_style_bg_color(_volume_bar, lv_color_make(0xFF, 0xFF, 0x00), LV_PART_INDICATOR); // Yellow indicator
|
||||||
lv_obj_set_style_pad_top(_volume_bar, 5, 0);
|
lv_obj_set_style_pad_top(_volume_bar, 5, 0);
|
||||||
lv_obj_set_style_pad_bottom(_volume_bar, 5, 0);
|
lv_obj_set_style_pad_bottom(_volume_bar, 5, 0);
|
||||||
|
|
||||||
@@ -1264,6 +1387,7 @@ static lv_obj_t* create_volume_page(void) {
|
|||||||
snprintf(volume_text, sizeof(volume_text), "%d%%", _current_volume);
|
snprintf(volume_text, sizeof(volume_text), "%d%%", _current_volume);
|
||||||
lv_label_set_text(_volume_label, volume_text);
|
lv_label_set_text(_volume_label, volume_text);
|
||||||
lv_obj_set_style_text_align(_volume_label, LV_TEXT_ALIGN_CENTER, 0);
|
lv_obj_set_style_text_align(_volume_label, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
|
lv_obj_set_style_text_color(_volume_label, lv_color_white(), 0);
|
||||||
lv_obj_set_style_pad_top(_volume_label, 5, 0);
|
lv_obj_set_style_pad_top(_volume_label, 5, 0);
|
||||||
|
|
||||||
return _volume_page;
|
return _volume_page;
|
||||||
@@ -1438,7 +1562,7 @@ static void calibration_click_cb(lv_event_t * e) {
|
|||||||
// Show "Calibrated" message centered
|
// Show "Calibrated" message centered
|
||||||
lv_obj_t* label = lv_label_create(container);
|
lv_obj_t* label = lv_label_create(container);
|
||||||
lv_label_set_text(label, "Calibrated");
|
lv_label_set_text(label, "Calibrated");
|
||||||
lv_obj_set_style_text_color(label, lv_color_black(), 0);
|
lv_obj_set_style_text_color(label, lv_color_white(), 0);
|
||||||
lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0);
|
lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0);
|
||||||
lv_obj_center(label);
|
lv_obj_center(label);
|
||||||
}
|
}
|
||||||
@@ -1467,7 +1591,7 @@ static void calibration_click_cb(lv_event_t * e) {
|
|||||||
// Show "Cleared" message centered
|
// Show "Cleared" message centered
|
||||||
lv_obj_t* label = lv_label_create(container);
|
lv_obj_t* label = lv_label_create(container);
|
||||||
lv_label_set_text(label, "Cleared");
|
lv_label_set_text(label, "Cleared");
|
||||||
lv_obj_set_style_text_color(label, lv_color_black(), 0);
|
lv_obj_set_style_text_color(label, lv_color_white(), 0);
|
||||||
lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0);
|
lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0);
|
||||||
lv_obj_center(label);
|
lv_obj_center(label);
|
||||||
}
|
}
|
||||||
@@ -1556,11 +1680,11 @@ static void build_scrollable_menu(void) {
|
|||||||
lv_style_init(&_styleFocusedBtn);
|
lv_style_init(&_styleFocusedBtn);
|
||||||
lv_style_init(&_styleUnfocusedBtn);
|
lv_style_init(&_styleUnfocusedBtn);
|
||||||
|
|
||||||
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0xff,0xff,0xff)); // gray bg
|
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0x00,0x00,0x00)); // black bg
|
||||||
lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_hex(0xFF8800));
|
lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_white());
|
||||||
|
|
||||||
lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0x33,0x99,0xFF)); // blue bg
|
lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0xFF,0xFF,0x00)); // bright yellow bg
|
||||||
lv_style_set_text_color(&_styleUnfocusedBtn,lv_color_black());
|
lv_style_set_text_color(&_styleFocusedBtn, lv_color_black());
|
||||||
|
|
||||||
|
|
||||||
// 2) Inside it, create the lv_menu and hide its sidebar/header
|
// 2) Inside it, create the lv_menu and hide its sidebar/header
|
||||||
@@ -1653,13 +1777,28 @@ static void gui_task(void *pvParameters)
|
|||||||
lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||||||
UNLOCK();
|
UNLOCK();
|
||||||
|
|
||||||
|
// Initialize backlight timeout
|
||||||
|
_lastButtonPress = xTaskGetTickCount();
|
||||||
|
_backlightOn = true;
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Start GUI Task...");
|
ESP_LOGI(TAG, "Start GUI Task...");
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
|
// Check backlight timeout
|
||||||
|
if (_backlightOn) {
|
||||||
|
TickType_t elapsed = xTaskGetTickCount() - _lastButtonPress;
|
||||||
|
if (elapsed > pdMS_TO_TICKS(BACKLIGHT_TIMEOUT_MS)) {
|
||||||
|
gpio_set_level(PIN_NUM_BK_LIGHT, 0);
|
||||||
|
_backlightOn = false;
|
||||||
|
ESP_LOGI(TAG, "Backlight off due to timeout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE)
|
if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE)
|
||||||
{
|
{
|
||||||
|
// Reset backlight timeout on any button press
|
||||||
|
backlight_reset_timeout();
|
||||||
|
|
||||||
switch (ev) {
|
switch (ev) {
|
||||||
#if 0
|
#if 0
|
||||||
case (KEY_UP << KEY_LONG_PRESS):
|
case (KEY_UP << KEY_LONG_PRESS):
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ void system_init(void)
|
|||||||
_systemState.primaryAxis = PRIMARY_AXIS;
|
_systemState.primaryAxis = PRIMARY_AXIS;
|
||||||
_systemState.pairedDeviceCount = 0;
|
_systemState.pairedDeviceCount = 0;
|
||||||
_systemState.isCharging = false;
|
_systemState.isCharging = false;
|
||||||
|
_systemState.swapLR = false;
|
||||||
|
|
||||||
_systemEvent = xEventGroupCreate();
|
_systemEvent = xEventGroupCreate();
|
||||||
|
|
||||||
@@ -74,6 +75,31 @@ bool system_getChargeStatus(void)
|
|||||||
return charging;
|
return charging;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void system_setSwapLR(bool swap)
|
||||||
|
{
|
||||||
|
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||||
|
_systemState.swapLR = swap;
|
||||||
|
xSemaphoreGive(_eventManager.mutex);
|
||||||
|
ESP_LOGI("system", "Swap L/R: %s", swap ? "ON" : "OFF");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool system_getSwapLR(void)
|
||||||
|
{
|
||||||
|
bool swap;
|
||||||
|
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||||
|
swap = _systemState.swapLR;
|
||||||
|
xSemaphoreGive(_eventManager.mutex);
|
||||||
|
return swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void system_toggleSwapLR(void)
|
||||||
|
{
|
||||||
|
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||||
|
_systemState.swapLR = !_systemState.swapLR;
|
||||||
|
ESP_LOGI("system", "Swap L/R toggled: %s", _systemState.swapLR ? "ON" : "OFF");
|
||||||
|
xSemaphoreGive(_eventManager.mutex);
|
||||||
|
}
|
||||||
|
|
||||||
void system_setZeroAngle(void)
|
void system_setZeroAngle(void)
|
||||||
{
|
{
|
||||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ typedef struct SystemState_s
|
|||||||
// Charge status
|
// Charge status
|
||||||
bool isCharging;
|
bool isCharging;
|
||||||
|
|
||||||
|
// Swap L/R audio channels
|
||||||
|
bool swapLR;
|
||||||
|
|
||||||
} SystemState_t;
|
} SystemState_t;
|
||||||
|
|
||||||
|
|
||||||
@@ -90,6 +93,10 @@ float system_getAngle(void);
|
|||||||
void system_setChargeStatus(bool charging);
|
void system_setChargeStatus(bool charging);
|
||||||
bool system_getChargeStatus(void);
|
bool system_getChargeStatus(void);
|
||||||
|
|
||||||
|
void system_setSwapLR(bool swap);
|
||||||
|
bool system_getSwapLR(void);
|
||||||
|
void system_toggleSwapLR(void);
|
||||||
|
|
||||||
void system_setZeroAngle(void);
|
void system_setZeroAngle(void);
|
||||||
void system_clearZeroAngle(void);
|
void system_clearZeroAngle(void);
|
||||||
float system_getZeroAngle(void);
|
float system_getZeroAngle(void);
|
||||||
|
|||||||
Reference in New Issue
Block a user