Fixes
This commit is contained in:
parent
a4cefd7c09
commit
df29f89977
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -42,7 +42,7 @@ dependencies:
|
|||||||
version: 1.1.4
|
version: 1.1.4
|
||||||
espressif/esp_hosted:
|
espressif/esp_hosted:
|
||||||
component_hash:
|
component_hash:
|
||||||
b6422e810fe97acd87ac184f7da1653e69e885fd209e13c5b1ae20c5787d914d
|
49424510d8cf3659aa4bcf787e7b4abbf11848a25e7e5f133cf7f3324860d066
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: idf
|
- name: idf
|
||||||
require: private
|
require: private
|
||||||
@ -50,7 +50,7 @@ dependencies:
|
|||||||
source:
|
source:
|
||||||
registry_url: https://components.espressif.com
|
registry_url: https://components.espressif.com
|
||||||
type: service
|
type: service
|
||||||
version: 2.11.1
|
version: 2.11.3
|
||||||
espressif/esp_lcd_touch:
|
espressif/esp_lcd_touch:
|
||||||
component_hash:
|
component_hash:
|
||||||
3f85a7d95af876f1a6ecca8eb90a81614890d0f03a038390804e5a77e2caf862
|
3f85a7d95af876f1a6ecca8eb90a81614890d0f03a038390804e5a77e2caf862
|
||||||
@ -377,7 +377,7 @@ dependencies:
|
|||||||
17e68bfd21f0edf4c3ee838e2273da840bf3930e5dbc3bfa6c1190c3aed41f9f
|
17e68bfd21f0edf4c3ee838e2273da840bf3930e5dbc3bfa6c1190c3aed41f9f
|
||||||
dependencies: []
|
dependencies: []
|
||||||
source:
|
source:
|
||||||
registry_url: https://components.espressif.com/
|
registry_url: https://components.espressif.com
|
||||||
type: service
|
type: service
|
||||||
version: 9.4.0
|
version: 9.4.0
|
||||||
waveshare/esp_lcd_jd9365_10_1:
|
waveshare/esp_lcd_jd9365_10_1:
|
||||||
@ -408,8 +408,7 @@ direct_dependencies:
|
|||||||
- espressif/esp_tinyusb
|
- espressif/esp_tinyusb
|
||||||
- espressif/esp_wifi_remote
|
- espressif/esp_wifi_remote
|
||||||
- idf
|
- idf
|
||||||
- lvgl/lvgl
|
|
||||||
- waveshare/esp_lcd_jd9365_10_1
|
- waveshare/esp_lcd_jd9365_10_1
|
||||||
manifest_hash: 77a45d81439c2b8bff313cb08784154731b4451f54eecd31bcd4c2fc83b0f096
|
manifest_hash: 4224a9627ef40f4e86cf66ea3d5037d4b4dbfca46c368078b42e6988300c7f2f
|
||||||
target: esp32p4
|
target: esp32p4
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
|
|||||||
19
main/Gui.cpp
19
main/Gui.cpp
@ -1,5 +1,6 @@
|
|||||||
#include "Gui.hpp"
|
#include "Gui.hpp"
|
||||||
#include "esp_lv_adapter.h"
|
#include "esp_lv_adapter.h"
|
||||||
|
#include "esp_log.h"
|
||||||
#include "Gui/WifiSetting.hpp"
|
#include "Gui/WifiSetting.hpp"
|
||||||
#include "Gui/EthSetting.hpp"
|
#include "Gui/EthSetting.hpp"
|
||||||
#include "WidgetManager.hpp"
|
#include "WidgetManager.hpp"
|
||||||
@ -14,15 +15,27 @@ static void screen_long_press_handler(lv_event_t * e)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void widget_manager_timer_cb(lv_timer_t* timer)
|
||||||
|
{
|
||||||
|
(void)timer;
|
||||||
|
// Debug: Log every 100th call to verify timer is running
|
||||||
|
static uint32_t callCount = 0;
|
||||||
|
if (++callCount % 100 == 0) {
|
||||||
|
ESP_LOGI("Gui", "Timer tick %lu", callCount);
|
||||||
|
}
|
||||||
|
WidgetManager::instance().loop();
|
||||||
|
}
|
||||||
|
|
||||||
void Gui::create()
|
void Gui::create()
|
||||||
{
|
{
|
||||||
// Initialize WidgetManager (loads config from SD card)
|
// Initialize WidgetManager (loads config from SD card)
|
||||||
WidgetManager::instance().init();
|
WidgetManager::instance().init();
|
||||||
|
|
||||||
if (esp_lv_adapter_lock(-1) == ESP_OK) {
|
if (esp_lv_adapter_lock(-1) == ESP_OK) {
|
||||||
// Add long press handler for settings
|
// TEMP: Disabled long press handler for testing
|
||||||
lv_obj_add_event_cb(lv_scr_act(), screen_long_press_handler,
|
// lv_obj_add_event_cb(lv_scr_act(), screen_long_press_handler,
|
||||||
LV_EVENT_LONG_PRESSED, NULL);
|
// LV_EVENT_LONG_PRESSED, NULL);
|
||||||
|
lv_timer_create(widget_manager_timer_cb, 10, nullptr);
|
||||||
|
|
||||||
esp_lv_adapter_unlock();
|
esp_lv_adapter_unlock();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ void EthSetting::show() {
|
|||||||
if (esp_lv_adapter_lock(-1) == ESP_OK) {
|
if (esp_lv_adapter_lock(-1) == ESP_OK) {
|
||||||
createUI();
|
createUI();
|
||||||
visible_ = true;
|
visible_ = true;
|
||||||
|
esp_lv_adapter_unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ void EthSetting::createUI() {
|
|||||||
lv_obj_set_size(closeBtn, 40, 40);
|
lv_obj_set_size(closeBtn, 40, 40);
|
||||||
lv_obj_align(closeBtn, LV_ALIGN_LEFT_MID, 0, 0);
|
lv_obj_align(closeBtn, LV_ALIGN_LEFT_MID, 0, 0);
|
||||||
lv_obj_set_style_bg_color(closeBtn, lv_color_hex(0x804040), 0);
|
lv_obj_set_style_bg_color(closeBtn, lv_color_hex(0x804040), 0);
|
||||||
lv_obj_add_event_cb(closeBtn, onCloseClick, LV_EVENT_CLICKED, nullptr);
|
//lv_obj_add_event_cb(closeBtn, onCloseClick, LV_EVENT_CLICKED, nullptr);
|
||||||
|
|
||||||
lv_obj_t* closeLbl = lv_label_create(closeBtn);
|
lv_obj_t* closeLbl = lv_label_create(closeBtn);
|
||||||
lv_label_set_text(closeLbl, "X");
|
lv_label_set_text(closeLbl, "X");
|
||||||
|
|||||||
@ -86,7 +86,7 @@ void WifiSetting::createUI() {
|
|||||||
lv_obj_set_size(closeBtn, 40, 40);
|
lv_obj_set_size(closeBtn, 40, 40);
|
||||||
lv_obj_align(closeBtn, LV_ALIGN_LEFT_MID, 0, 0);
|
lv_obj_align(closeBtn, LV_ALIGN_LEFT_MID, 0, 0);
|
||||||
lv_obj_set_style_bg_color(closeBtn, lv_color_hex(0x804040), 0);
|
lv_obj_set_style_bg_color(closeBtn, lv_color_hex(0x804040), 0);
|
||||||
lv_obj_add_event_cb(closeBtn, onCloseClick, LV_EVENT_CLICKED, nullptr);
|
//lv_obj_add_event_cb(closeBtn, onCloseClick, LV_EVENT_CLICKED, nullptr);
|
||||||
|
|
||||||
lv_obj_t* closeLbl = lv_label_create(closeBtn);
|
lv_obj_t* closeLbl = lv_label_create(closeBtn);
|
||||||
lv_label_set_text(closeLbl, "X");
|
lv_label_set_text(closeLbl, "X");
|
||||||
@ -111,7 +111,7 @@ void WifiSetting::createUI() {
|
|||||||
disconnectBtn_ = lv_btn_create(statusSection);
|
disconnectBtn_ = lv_btn_create(statusSection);
|
||||||
lv_obj_set_size(disconnectBtn_, 100, 40);
|
lv_obj_set_size(disconnectBtn_, 100, 40);
|
||||||
lv_obj_align(disconnectBtn_, LV_ALIGN_RIGHT_MID, 0, 0);
|
lv_obj_align(disconnectBtn_, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||||
lv_obj_add_event_cb(disconnectBtn_, onDisconnectClick, LV_EVENT_CLICKED, nullptr);
|
//lv_obj_add_event_cb(disconnectBtn_, onDisconnectClick, LV_EVENT_CLICKED, nullptr);
|
||||||
lv_obj_add_flag(disconnectBtn_, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_add_flag(disconnectBtn_, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
|
||||||
lv_obj_t* disconnectLbl = lv_label_create(disconnectBtn_);
|
lv_obj_t* disconnectLbl = lv_label_create(disconnectBtn_);
|
||||||
@ -140,7 +140,7 @@ void WifiSetting::createUI() {
|
|||||||
lv_obj_t* scanBtn = lv_btn_create(availableHeader);
|
lv_obj_t* scanBtn = lv_btn_create(availableHeader);
|
||||||
lv_obj_set_size(scanBtn, 100, 35);
|
lv_obj_set_size(scanBtn, 100, 35);
|
||||||
lv_obj_align(scanBtn, LV_ALIGN_RIGHT_MID, 0, 0);
|
lv_obj_align(scanBtn, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||||
lv_obj_add_event_cb(scanBtn, onScanClick, LV_EVENT_CLICKED, nullptr);
|
//lv_obj_add_event_cb(scanBtn, onScanClick, LV_EVENT_CLICKED, nullptr);
|
||||||
|
|
||||||
lv_obj_t* scanLbl = lv_label_create(scanBtn);
|
lv_obj_t* scanLbl = lv_label_create(scanBtn);
|
||||||
lv_label_set_text(scanLbl, "Scannen");
|
lv_label_set_text(scanLbl, "Scannen");
|
||||||
@ -198,7 +198,7 @@ void WifiSetting::refreshNetworkList() {
|
|||||||
(const char*)ap.ssid, barSymbols[bars - 1], ap.rssi);
|
(const char*)ap.ssid, barSymbols[bars - 1], ap.rssi);
|
||||||
|
|
||||||
lv_obj_t* btn = lv_list_add_btn(settings.networkList_, LV_SYMBOL_WIFI, signalStr);
|
lv_obj_t* btn = lv_list_add_btn(settings.networkList_, LV_SYMBOL_WIFI, signalStr);
|
||||||
lv_obj_add_event_cb(btn, onNetworkSelect, LV_EVENT_CLICKED, nullptr);
|
//lv_obj_add_event_cb(btn, onNetworkSelect, LV_EVENT_CLICKED, nullptr);
|
||||||
|
|
||||||
size_t ssidLen = strlen((const char*)ap.ssid);
|
size_t ssidLen = strlen((const char*)ap.ssid);
|
||||||
char* ssidCopy = (char*)lv_malloc(ssidLen + 1);
|
char* ssidCopy = (char*)lv_malloc(ssidLen + 1);
|
||||||
@ -237,7 +237,7 @@ void WifiSetting::refreshSavedNetworks() {
|
|||||||
lv_obj_set_size(deleteBtn, 80, 30);
|
lv_obj_set_size(deleteBtn, 80, 30);
|
||||||
lv_obj_align(deleteBtn, LV_ALIGN_RIGHT_MID, 0, 0);
|
lv_obj_align(deleteBtn, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||||
lv_obj_set_style_bg_color(deleteBtn, lv_color_hex(0x804040), 0);
|
lv_obj_set_style_bg_color(deleteBtn, lv_color_hex(0x804040), 0);
|
||||||
lv_obj_add_event_cb(deleteBtn, onDeleteSavedClick, LV_EVENT_CLICKED, nullptr);
|
//lv_obj_add_event_cb(deleteBtn, onDeleteSavedClick, LV_EVENT_CLICKED, nullptr);
|
||||||
|
|
||||||
char* ssidCopy = (char*)lv_malloc(ssid.size() + 1);
|
char* ssidCopy = (char*)lv_malloc(ssid.size() + 1);
|
||||||
strcpy(ssidCopy, ssid.c_str());
|
strcpy(ssidCopy, ssid.c_str());
|
||||||
@ -312,7 +312,7 @@ void WifiSetting::createPasswordDialogUI() {
|
|||||||
lv_obj_set_size(cancelBtn, 150, 50);
|
lv_obj_set_size(cancelBtn, 150, 50);
|
||||||
lv_obj_align(cancelBtn, LV_ALIGN_TOP_LEFT, 20, 150);
|
lv_obj_align(cancelBtn, LV_ALIGN_TOP_LEFT, 20, 150);
|
||||||
lv_obj_set_style_bg_color(cancelBtn, lv_color_hex(0x606060), 0);
|
lv_obj_set_style_bg_color(cancelBtn, lv_color_hex(0x606060), 0);
|
||||||
lv_obj_add_event_cb(cancelBtn, onCancelClick, LV_EVENT_CLICKED, nullptr);
|
//lv_obj_add_event_cb(cancelBtn, onCancelClick, LV_EVENT_CLICKED, nullptr);
|
||||||
|
|
||||||
lv_obj_t* cancelLbl = lv_label_create(cancelBtn);
|
lv_obj_t* cancelLbl = lv_label_create(cancelBtn);
|
||||||
lv_label_set_text(cancelLbl, "Abbrechen");
|
lv_label_set_text(cancelLbl, "Abbrechen");
|
||||||
@ -322,7 +322,7 @@ void WifiSetting::createPasswordDialogUI() {
|
|||||||
lv_obj_set_size(connectBtn, 150, 50);
|
lv_obj_set_size(connectBtn, 150, 50);
|
||||||
lv_obj_align(connectBtn, LV_ALIGN_TOP_RIGHT, -20, 150);
|
lv_obj_align(connectBtn, LV_ALIGN_TOP_RIGHT, -20, 150);
|
||||||
lv_obj_set_style_bg_color(connectBtn, lv_color_hex(0x408040), 0);
|
lv_obj_set_style_bg_color(connectBtn, lv_color_hex(0x408040), 0);
|
||||||
lv_obj_add_event_cb(connectBtn, onConnectClick, LV_EVENT_CLICKED, nullptr);
|
//lv_obj_add_event_cb(connectBtn, onConnectClick, LV_EVENT_CLICKED, nullptr);
|
||||||
|
|
||||||
lv_obj_t* connectLbl = lv_label_create(connectBtn);
|
lv_obj_t* connectLbl = lv_label_create(connectBtn);
|
||||||
lv_label_set_text(connectLbl, "Verbinden");
|
lv_label_set_text(connectLbl, "Verbinden");
|
||||||
@ -332,7 +332,7 @@ void WifiSetting::createPasswordDialogUI() {
|
|||||||
lv_obj_set_size(keyboard_, LV_PCT(100), 300);
|
lv_obj_set_size(keyboard_, LV_PCT(100), 300);
|
||||||
lv_obj_align(keyboard_, LV_ALIGN_BOTTOM_MID, 0, 0);
|
lv_obj_align(keyboard_, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||||
lv_keyboard_set_textarea(keyboard_, passwordInput_);
|
lv_keyboard_set_textarea(keyboard_, passwordInput_);
|
||||||
lv_obj_add_event_cb(keyboard_, onKeyboardReady, LV_EVENT_READY, nullptr);
|
//lv_obj_add_event_cb(keyboard_, onKeyboardReady, LV_EVENT_READY, nullptr);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Password dialog created");
|
ESP_LOGI(TAG, "Password dialog created");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,6 +62,7 @@ esp_lcd_touch_handle_t Touch::getTouchHandle() const {
|
|||||||
|
|
||||||
void Touch::lv_indev_read_cb(lv_indev_t *indev, lv_indev_data_t *data)
|
void Touch::lv_indev_read_cb(lv_indev_t *indev, lv_indev_data_t *data)
|
||||||
{
|
{
|
||||||
|
|
||||||
Touch* self = static_cast<Touch*>(lv_indev_get_user_data(indev));
|
Touch* self = static_cast<Touch*>(lv_indev_get_user_data(indev));
|
||||||
if (!self) {
|
if (!self) {
|
||||||
data->state = LV_INDEV_STATE_RELEASED;
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
@ -76,7 +77,8 @@ void Touch::lv_indev_read_cb(lv_indev_t *indev, lv_indev_data_t *data)
|
|||||||
data->point.x = x[0];
|
data->point.x = x[0];
|
||||||
data->point.y = y[0];
|
data->point.y = y[0];
|
||||||
data->state = LV_INDEV_STATE_PRESSED;
|
data->state = LV_INDEV_STATE_PRESSED;
|
||||||
WidgetManager::instance().onUserActivity();
|
// TEMP: Disabled to test if this causes the freeze
|
||||||
|
// WidgetManager::instance().onUserActivity();
|
||||||
} else {
|
} else {
|
||||||
data->state = LV_INDEV_STATE_RELEASED;
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,10 @@ WidgetManager& WidgetManager::instance() {
|
|||||||
|
|
||||||
WidgetManager::WidgetManager() {
|
WidgetManager::WidgetManager() {
|
||||||
// widgets_ is default-initialized to nullptr
|
// widgets_ is default-initialized to nullptr
|
||||||
|
uiQueue_ = xQueueCreate(UI_EVENT_QUEUE_LEN, sizeof(UiEvent));
|
||||||
|
if (!uiQueue_) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create UI event queue");
|
||||||
|
}
|
||||||
createDefaultConfig();
|
createDefaultConfig();
|
||||||
activeScreenId_ = config_.startScreenId;
|
activeScreenId_ = config_.startScreenId;
|
||||||
lastActivityUs_ = esp_timer_get_time();
|
lastActivityUs_ = esp_timer_get_time();
|
||||||
@ -182,6 +186,8 @@ const ScreenConfig* WidgetManager::activeScreen() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WidgetManager::applyScreen(uint8_t screenId) {
|
void WidgetManager::applyScreen(uint8_t screenId) {
|
||||||
|
ESP_LOGI(TAG, "applyScreen(%d) - start", screenId);
|
||||||
|
|
||||||
ScreenConfig* screen = config_.findScreen(screenId);
|
ScreenConfig* screen = config_.findScreen(screenId);
|
||||||
if (!screen) {
|
if (!screen) {
|
||||||
ESP_LOGW(TAG, "Screen %d not found", screenId);
|
ESP_LOGW(TAG, "Screen %d not found", screenId);
|
||||||
@ -189,17 +195,40 @@ void WidgetManager::applyScreen(uint8_t screenId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (modalContainer_) {
|
if (modalContainer_) {
|
||||||
|
ESP_LOGI(TAG, "Closing modal first");
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// First destroy C++ widgets (which deletes LVGL objects)
|
ESP_LOGI(TAG, "Acquiring LVGL lock...");
|
||||||
destroyAllWidgets();
|
if (esp_lv_adapter_lock(-1) != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to acquire LVGL lock!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "LVGL lock acquired");
|
||||||
|
|
||||||
if (esp_lv_adapter_lock(-1) == ESP_OK) {
|
// Reset all input devices BEFORE destroying widgets to clear any
|
||||||
|
// pending input state and prevent use-after-free on widget objects
|
||||||
|
ESP_LOGI(TAG, "Resetting input devices...");
|
||||||
|
lv_indev_t* indev = lv_indev_get_next(nullptr);
|
||||||
|
while (indev) {
|
||||||
|
lv_indev_reset(indev, nullptr);
|
||||||
|
indev = lv_indev_get_next(indev);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Input devices reset");
|
||||||
|
|
||||||
|
// Now destroy C++ widgets (which deletes LVGL objects) under LVGL lock
|
||||||
|
ESP_LOGI(TAG, "Destroying widgets...");
|
||||||
|
destroyAllWidgets();
|
||||||
|
ESP_LOGI(TAG, "Widgets destroyed");
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Creating new widgets for screen '%s' (%d widgets)...",
|
||||||
|
screen->name, screen->widgetCount);
|
||||||
lv_obj_t* root = lv_scr_act();
|
lv_obj_t* root = lv_scr_act();
|
||||||
createAllWidgets(*screen, root);
|
createAllWidgets(*screen, root);
|
||||||
|
ESP_LOGI(TAG, "Widgets created");
|
||||||
|
|
||||||
esp_lv_adapter_unlock();
|
esp_lv_adapter_unlock();
|
||||||
}
|
ESP_LOGI(TAG, "applyScreen(%d) - complete", screenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WidgetManager::showModalScreen(const ScreenConfig& screen) {
|
void WidgetManager::showModalScreen(const ScreenConfig& screen) {
|
||||||
@ -207,11 +236,18 @@ void WidgetManager::showModalScreen(const ScreenConfig& screen) {
|
|||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (esp_lv_adapter_lock(-1) != ESP_OK) return;
|
||||||
|
|
||||||
|
// Reset all input devices BEFORE destroying widgets
|
||||||
|
lv_indev_t* indev = lv_indev_get_next(nullptr);
|
||||||
|
while (indev) {
|
||||||
|
lv_indev_reset(indev, nullptr);
|
||||||
|
indev = lv_indev_get_next(indev);
|
||||||
|
}
|
||||||
|
|
||||||
// Destroy any existing widgets before creating modal widgets
|
// Destroy any existing widgets before creating modal widgets
|
||||||
destroyAllWidgets();
|
destroyAllWidgets();
|
||||||
|
|
||||||
if (esp_lv_adapter_lock(-1) != ESP_OK) return;
|
|
||||||
|
|
||||||
lv_disp_t* disp = lv_disp_get_default();
|
lv_disp_t* disp = lv_disp_get_default();
|
||||||
int32_t dispWidth = disp ? lv_disp_get_hor_res(disp) : 1280;
|
int32_t dispWidth = disp ? lv_disp_get_hor_res(disp) : 1280;
|
||||||
int32_t dispHeight = disp ? lv_disp_get_ver_res(disp) : 800;
|
int32_t dispHeight = disp ? lv_disp_get_ver_res(disp) : 800;
|
||||||
@ -299,28 +335,40 @@ void WidgetManager::showModalScreen(const ScreenConfig& screen) {
|
|||||||
void WidgetManager::closeModal() {
|
void WidgetManager::closeModal() {
|
||||||
if (!modalContainer_) return;
|
if (!modalContainer_) return;
|
||||||
|
|
||||||
|
if (esp_lv_adapter_lock(-1) != ESP_OK) return;
|
||||||
|
|
||||||
|
// Reset all input devices BEFORE destroying widgets
|
||||||
|
lv_indev_t* indev = lv_indev_get_next(nullptr);
|
||||||
|
while (indev) {
|
||||||
|
lv_indev_reset(indev, nullptr);
|
||||||
|
indev = lv_indev_get_next(indev);
|
||||||
|
}
|
||||||
|
|
||||||
// First destroy C++ widgets (which deletes their LVGL objects)
|
// First destroy C++ widgets (which deletes their LVGL objects)
|
||||||
destroyAllWidgets();
|
destroyAllWidgets();
|
||||||
|
|
||||||
if (esp_lv_adapter_lock(-1) == ESP_OK) {
|
|
||||||
if (modalDimmer_) {
|
if (modalDimmer_) {
|
||||||
lv_obj_delete(modalDimmer_);
|
lv_obj_delete(modalDimmer_);
|
||||||
}
|
}
|
||||||
lv_obj_delete(modalContainer_);
|
lv_obj_delete(modalContainer_);
|
||||||
esp_lv_adapter_unlock();
|
esp_lv_adapter_unlock();
|
||||||
}
|
|
||||||
modalContainer_ = nullptr;
|
modalContainer_ = nullptr;
|
||||||
modalDimmer_ = nullptr;
|
modalDimmer_ = nullptr;
|
||||||
modalScreenId_ = SCREEN_ID_NONE;
|
modalScreenId_ = SCREEN_ID_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WidgetManager::showScreen(uint8_t screenId) {
|
void WidgetManager::showScreen(uint8_t screenId) {
|
||||||
|
ESP_LOGI(TAG, "showScreen(%d) called", screenId);
|
||||||
|
|
||||||
ScreenConfig* screen = config_.findScreen(screenId);
|
ScreenConfig* screen = config_.findScreen(screenId);
|
||||||
if (!screen) {
|
if (!screen) {
|
||||||
ESP_LOGW(TAG, "Screen %d not found", screenId);
|
ESP_LOGW(TAG, "Screen %d not found", screenId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Found screen '%s', mode=%d", screen->name,
|
||||||
|
static_cast<int>(screen->mode));
|
||||||
|
|
||||||
if (screen->mode == ScreenMode::MODAL) {
|
if (screen->mode == ScreenMode::MODAL) {
|
||||||
showModalScreen(*screen);
|
showModalScreen(*screen);
|
||||||
return;
|
return;
|
||||||
@ -335,18 +383,25 @@ void WidgetManager::showScreen(uint8_t screenId) {
|
|||||||
void WidgetManager::handleButtonAction(const WidgetConfig& cfg, lv_obj_t* target) {
|
void WidgetManager::handleButtonAction(const WidgetConfig& cfg, lv_obj_t* target) {
|
||||||
if (cfg.type != WidgetType::BUTTON) return;
|
if (cfg.type != WidgetType::BUTTON) return;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "handleButtonAction: button=%d action=%d targetScreen=%d",
|
||||||
|
cfg.id, static_cast<int>(cfg.action), cfg.targetScreen);
|
||||||
|
|
||||||
onUserActivity();
|
onUserActivity();
|
||||||
|
|
||||||
switch (cfg.action) {
|
switch (cfg.action) {
|
||||||
case ButtonAction::JUMP:
|
case ButtonAction::JUMP:
|
||||||
navPending_ = true;
|
ESP_LOGI(TAG, "JUMP action: scheduling navigation to screen %d", cfg.targetScreen);
|
||||||
navAction_ = ButtonAction::JUMP;
|
navAction_ = ButtonAction::JUMP;
|
||||||
navTargetScreen_ = cfg.targetScreen;
|
navTargetScreen_ = cfg.targetScreen;
|
||||||
|
navPending_ = true;
|
||||||
|
navRequestUs_ = esp_timer_get_time();
|
||||||
break;
|
break;
|
||||||
case ButtonAction::BACK:
|
case ButtonAction::BACK:
|
||||||
navPending_ = true;
|
ESP_LOGI(TAG, "BACK action: scheduling navigation back");
|
||||||
navAction_ = ButtonAction::BACK;
|
navAction_ = ButtonAction::BACK;
|
||||||
navTargetScreen_ = SCREEN_ID_NONE;
|
navTargetScreen_ = SCREEN_ID_NONE;
|
||||||
|
navPending_ = true;
|
||||||
|
navRequestUs_ = esp_timer_get_time();
|
||||||
break;
|
break;
|
||||||
case ButtonAction::KNX:
|
case ButtonAction::KNX:
|
||||||
default: {
|
default: {
|
||||||
@ -393,14 +448,22 @@ void WidgetManager::enterStandby() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WidgetManager::loop() {
|
void WidgetManager::loop() {
|
||||||
|
bool didUiNav = false;
|
||||||
if (navPending_) {
|
if (navPending_) {
|
||||||
|
int64_t now = esp_timer_get_time();
|
||||||
|
// Increased delay to ensure touch events are fully processed
|
||||||
|
if (now - navRequestUs_ >= NAV_DELAY_US) {
|
||||||
navPending_ = false;
|
navPending_ = false;
|
||||||
|
ESP_LOGI(TAG, "Executing navigation: action=%d target=%d",
|
||||||
|
static_cast<int>(navAction_), navTargetScreen_);
|
||||||
if (navAction_ == ButtonAction::JUMP) {
|
if (navAction_ == ButtonAction::JUMP) {
|
||||||
showScreen(navTargetScreen_);
|
showScreen(navTargetScreen_);
|
||||||
} else if (navAction_ == ButtonAction::BACK) {
|
} else if (navAction_ == ButtonAction::BACK) {
|
||||||
goBack();
|
goBack();
|
||||||
}
|
}
|
||||||
return;
|
didUiNav = true;
|
||||||
|
ESP_LOGI(TAG, "Navigation complete");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (standbyWakePending_) {
|
if (standbyWakePending_) {
|
||||||
@ -409,9 +472,13 @@ void WidgetManager::loop() {
|
|||||||
activeScreenId_ = standbyWakeTarget_;
|
activeScreenId_ = standbyWakeTarget_;
|
||||||
applyScreen(activeScreenId_);
|
applyScreen(activeScreenId_);
|
||||||
}
|
}
|
||||||
return;
|
didUiNav = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processUiQueue();
|
||||||
|
|
||||||
|
if (didUiNav) return;
|
||||||
|
|
||||||
if (!config_.standbyEnabled || config_.standbyMinutes == 0) return;
|
if (!config_.standbyEnabled || config_.standbyMinutes == 0) return;
|
||||||
if (standbyActive_) return;
|
if (standbyActive_) return;
|
||||||
if (config_.standbyScreenId == SCREEN_ID_NONE) return;
|
if (config_.standbyScreenId == SCREEN_ID_NONE) return;
|
||||||
@ -464,39 +531,88 @@ void WidgetManager::createAllWidgets(const ScreenConfig& screen, lv_obj_t* paren
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WidgetManager::onKnxValue(uint16_t groupAddr, float value) {
|
void WidgetManager::onKnxValue(uint16_t groupAddr, float value) {
|
||||||
if (esp_lv_adapter_lock(100) != ESP_OK) return;
|
UiEvent event = {};
|
||||||
|
event.type = UiEventType::KNX_VALUE;
|
||||||
|
event.groupAddr = groupAddr;
|
||||||
|
event.value = value;
|
||||||
|
enqueueUiEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetManager::onKnxSwitch(uint16_t groupAddr, bool value) {
|
||||||
|
UiEvent event = {};
|
||||||
|
event.type = UiEventType::KNX_SWITCH;
|
||||||
|
event.groupAddr = groupAddr;
|
||||||
|
event.state = value;
|
||||||
|
enqueueUiEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetManager::onKnxText(uint16_t groupAddr, const char* text) {
|
||||||
|
UiEvent event = {};
|
||||||
|
event.type = UiEventType::KNX_TEXT;
|
||||||
|
event.groupAddr = groupAddr;
|
||||||
|
if (text) {
|
||||||
|
strncpy(event.text, text, sizeof(event.text) - 1);
|
||||||
|
} else {
|
||||||
|
event.text[0] = '\0';
|
||||||
|
}
|
||||||
|
enqueueUiEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WidgetManager::enqueueUiEvent(const UiEvent& event) {
|
||||||
|
if (!uiQueue_) return false;
|
||||||
|
return xQueueSend(uiQueue_, &event, 0) == pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetManager::processUiQueue() {
|
||||||
|
if (!uiQueue_) return;
|
||||||
|
if (uxQueueMessagesWaiting(uiQueue_) == 0) return;
|
||||||
|
|
||||||
|
if (esp_lv_adapter_lock(-1) != ESP_OK) return;
|
||||||
|
|
||||||
|
UiEvent event = {};
|
||||||
|
static constexpr size_t kMaxEventsPerLoop = 8;
|
||||||
|
size_t processed = 0;
|
||||||
|
while (processed < kMaxEventsPerLoop &&
|
||||||
|
xQueueReceive(uiQueue_, &event, 0) == pdTRUE) {
|
||||||
|
switch (event.type) {
|
||||||
|
case UiEventType::KNX_VALUE:
|
||||||
|
applyKnxValue(event.groupAddr, event.value);
|
||||||
|
break;
|
||||||
|
case UiEventType::KNX_SWITCH:
|
||||||
|
applyKnxSwitch(event.groupAddr, event.state);
|
||||||
|
break;
|
||||||
|
case UiEventType::KNX_TEXT:
|
||||||
|
applyKnxText(event.groupAddr, event.text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
processed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_lv_adapter_unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetManager::applyKnxValue(uint16_t groupAddr, float value) {
|
||||||
for (auto& widget : widgets_) {
|
for (auto& widget : widgets_) {
|
||||||
if (widget && widget->getKnxAddress() == groupAddr) {
|
if (widget && widget->getKnxAddress() == groupAddr) {
|
||||||
widget->onKnxValue(value);
|
widget->onKnxValue(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_lv_adapter_unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WidgetManager::onKnxSwitch(uint16_t groupAddr, bool value) {
|
void WidgetManager::applyKnxSwitch(uint16_t groupAddr, bool value) {
|
||||||
if (esp_lv_adapter_lock(100) != ESP_OK) return;
|
|
||||||
|
|
||||||
for (auto& widget : widgets_) {
|
for (auto& widget : widgets_) {
|
||||||
if (widget && widget->getKnxAddress() == groupAddr) {
|
if (widget && widget->getKnxAddress() == groupAddr) {
|
||||||
widget->onKnxSwitch(value);
|
widget->onKnxSwitch(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_lv_adapter_unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WidgetManager::onKnxText(uint16_t groupAddr, const char* text) {
|
void WidgetManager::applyKnxText(uint16_t groupAddr, const char* text) {
|
||||||
if (esp_lv_adapter_lock(100) != ESP_OK) return;
|
|
||||||
|
|
||||||
for (auto& widget : widgets_) {
|
for (auto& widget : widgets_) {
|
||||||
if (widget && widget->getKnxAddress() == groupAddr) {
|
if (widget && widget->getKnxAddress() == groupAddr) {
|
||||||
widget->onKnxText(text);
|
widget->onKnxText(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_lv_adapter_unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to parse hex color string
|
// Helper function to parse hex color string
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
#include "WidgetConfig.hpp"
|
#include "WidgetConfig.hpp"
|
||||||
#include "widgets/Widget.hpp"
|
#include "widgets/Widget.hpp"
|
||||||
#include "lvgl.h"
|
#include "lvgl.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@ -35,7 +37,7 @@ public:
|
|||||||
// User activity (resets standby timer)
|
// User activity (resets standby timer)
|
||||||
void onUserActivity();
|
void onUserActivity();
|
||||||
|
|
||||||
// KNX value update (called from KnxWorker)
|
// Thread-safe KNX updates (queued to UI thread)
|
||||||
void onKnxValue(uint16_t groupAddr, float value);
|
void onKnxValue(uint16_t groupAddr, float value);
|
||||||
void onKnxSwitch(uint16_t groupAddr, bool value);
|
void onKnxSwitch(uint16_t groupAddr, bool value);
|
||||||
void onKnxText(uint16_t groupAddr, const char* text);
|
void onKnxText(uint16_t groupAddr, const char* text);
|
||||||
@ -54,10 +56,32 @@ private:
|
|||||||
WidgetManager(const WidgetManager&) = delete;
|
WidgetManager(const WidgetManager&) = delete;
|
||||||
WidgetManager& operator=(const WidgetManager&) = delete;
|
WidgetManager& operator=(const WidgetManager&) = delete;
|
||||||
|
|
||||||
|
static constexpr size_t UI_EVENT_QUEUE_LEN = 16;
|
||||||
|
static constexpr size_t UI_EVENT_TEXT_LEN = MAX_TEXT_LEN;
|
||||||
|
|
||||||
|
enum class UiEventType : uint8_t {
|
||||||
|
KNX_VALUE = 0,
|
||||||
|
KNX_SWITCH = 1,
|
||||||
|
KNX_TEXT = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UiEvent {
|
||||||
|
UiEventType type;
|
||||||
|
uint16_t groupAddr;
|
||||||
|
float value;
|
||||||
|
bool state;
|
||||||
|
char text[UI_EVENT_TEXT_LEN];
|
||||||
|
};
|
||||||
|
|
||||||
void loadFromSdCard();
|
void loadFromSdCard();
|
||||||
void saveToSdCard();
|
void saveToSdCard();
|
||||||
void destroyAllWidgets();
|
void destroyAllWidgets();
|
||||||
void createAllWidgets(const ScreenConfig& screen, lv_obj_t* parent);
|
void createAllWidgets(const ScreenConfig& screen, lv_obj_t* parent);
|
||||||
|
bool enqueueUiEvent(const UiEvent& event);
|
||||||
|
void processUiQueue();
|
||||||
|
void applyKnxValue(uint16_t groupAddr, float value);
|
||||||
|
void applyKnxSwitch(uint16_t groupAddr, bool value);
|
||||||
|
void applyKnxText(uint16_t groupAddr, const char* text);
|
||||||
|
|
||||||
void createDefaultConfig();
|
void createDefaultConfig();
|
||||||
void applyScreen(uint8_t screenId);
|
void applyScreen(uint8_t screenId);
|
||||||
@ -69,6 +93,7 @@ private:
|
|||||||
const ScreenConfig* activeScreen() const;
|
const ScreenConfig* activeScreen() const;
|
||||||
|
|
||||||
static constexpr const char* CONFIG_FILE = "/sdcard/lvgl.json";
|
static constexpr const char* CONFIG_FILE = "/sdcard/lvgl.json";
|
||||||
|
static constexpr int64_t NAV_DELAY_US = 200 * 1000; // 200ms delay for touch release
|
||||||
|
|
||||||
GuiConfig config_;
|
GuiConfig config_;
|
||||||
uint8_t activeScreenId_ = 0;
|
uint8_t activeScreenId_ = 0;
|
||||||
@ -81,6 +106,7 @@ private:
|
|||||||
bool navPending_ = false;
|
bool navPending_ = false;
|
||||||
ButtonAction navAction_ = ButtonAction::KNX;
|
ButtonAction navAction_ = ButtonAction::KNX;
|
||||||
uint8_t navTargetScreen_ = 0xFF;
|
uint8_t navTargetScreen_ = 0xFF;
|
||||||
|
int64_t navRequestUs_ = 0;
|
||||||
int64_t lastActivityUs_ = 0;
|
int64_t lastActivityUs_ = 0;
|
||||||
|
|
||||||
// Runtime widget instances (indexed by widget ID)
|
// Runtime widget instances (indexed by widget ID)
|
||||||
@ -88,4 +114,5 @@ private:
|
|||||||
lv_obj_t* screen_ = nullptr;
|
lv_obj_t* screen_ = nullptr;
|
||||||
lv_obj_t* modalContainer_ = nullptr;
|
lv_obj_t* modalContainer_ = nullptr;
|
||||||
lv_obj_t* modalDimmer_ = nullptr;
|
lv_obj_t* modalDimmer_ = nullptr;
|
||||||
|
QueueHandle_t uiQueue_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,8 +15,7 @@ dependencies:
|
|||||||
# # All dependencies of `main` are public by default.
|
# # All dependencies of `main` are public by default.
|
||||||
# public: true
|
# public: true
|
||||||
waveshare/esp_lcd_jd9365_10_1: '*'
|
waveshare/esp_lcd_jd9365_10_1: '*'
|
||||||
lvgl/lvgl: ^9.4.0
|
espressif/esp_lvgl_port: ^2.7.0
|
||||||
espressif/esp_lvgl_port: ^2.3.0
|
|
||||||
espressif/esp_lcd_touch_gt911: '*'
|
espressif/esp_lcd_touch_gt911: '*'
|
||||||
espressif/esp_lvgl_adapter: '*'
|
espressif/esp_lvgl_adapter: '*'
|
||||||
espressif/esp_wifi_remote: '*'
|
espressif/esp_wifi_remote: '*'
|
||||||
|
|||||||
@ -17,6 +17,14 @@
|
|||||||
|
|
||||||
#define TAG "App"
|
#define TAG "App"
|
||||||
|
|
||||||
|
static void knx_task(void* arg) {
|
||||||
|
KnxWorker* worker = static_cast<KnxWorker*>(arg);
|
||||||
|
worker->init();
|
||||||
|
while (true) {
|
||||||
|
worker->loop();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is a simple wrapper for the application logic
|
// This is a simple wrapper for the application logic
|
||||||
class Application {
|
class Application {
|
||||||
@ -63,7 +71,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Creating UI");
|
ESP_LOGI(TAG, "Creating UI");
|
||||||
knxWorker.init();
|
BaseType_t knx_ok = xTaskCreatePinnedToCore(
|
||||||
|
knx_task, "knx", 4096, &Gui::knxWorker, 5, nullptr, 1);
|
||||||
|
if (knx_ok != pdPASS) {
|
||||||
|
ESP_LOGE(TAG, "Failed to start KNX task");
|
||||||
|
}
|
||||||
gui.create();
|
gui.create();
|
||||||
|
|
||||||
// Start WebServer for widget configuration
|
// Start WebServer for widget configuration
|
||||||
@ -72,8 +84,6 @@ public:
|
|||||||
ESP_LOGI(TAG, "Application running");
|
ESP_LOGI(TAG, "Application running");
|
||||||
while (true) {
|
while (true) {
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
knxWorker.loop();
|
|
||||||
WidgetManager::instance().loop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +92,6 @@ private:
|
|||||||
Touch touch;
|
Touch touch;
|
||||||
Gui gui;
|
Gui gui;
|
||||||
Nvs nvs;
|
Nvs nvs;
|
||||||
KnxWorker knxWorker;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern "C" void app_main(void)
|
extern "C" void app_main(void)
|
||||||
|
|||||||
@ -1,5 +1,13 @@
|
|||||||
#include "ButtonWidget.hpp"
|
#include "ButtonWidget.hpp"
|
||||||
#include "../WidgetManager.hpp"
|
#include "../WidgetManager.hpp"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
static const char* TAG = "ButtonWidget";
|
||||||
|
|
||||||
|
// Free function instead of static member
|
||||||
|
static void button_event_cb(lv_event_t* e) {
|
||||||
|
ESP_LOGI(TAG, "button_event_cb called");
|
||||||
|
}
|
||||||
|
|
||||||
ButtonWidget::ButtonWidget(const WidgetConfig& config)
|
ButtonWidget::ButtonWidget(const WidgetConfig& config)
|
||||||
: Widget(config)
|
: Widget(config)
|
||||||
@ -7,32 +15,33 @@ ButtonWidget::ButtonWidget(const WidgetConfig& config)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ButtonWidget::~ButtonWidget() {
|
||||||
|
// Remove event callback BEFORE the base class destructor deletes the object
|
||||||
|
if (obj_) {
|
||||||
|
lv_obj_remove_event_cb(obj_, button_event_cb);
|
||||||
|
}
|
||||||
|
label_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void ButtonWidget::clickCallback(lv_event_t* e) {
|
void ButtonWidget::clickCallback(lv_event_t* e) {
|
||||||
ButtonWidget* widget = static_cast<ButtonWidget*>(lv_event_get_user_data(e));
|
// Not used currently
|
||||||
if (!widget) return;
|
(void)e;
|
||||||
lv_obj_t* target = static_cast<lv_obj_t*>(lv_event_get_target(e));
|
|
||||||
WidgetManager::instance().handleButtonAction(widget->getConfig(), target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lv_obj_t* ButtonWidget::create(lv_obj_t* parent) {
|
lv_obj_t* ButtonWidget::create(lv_obj_t* parent) {
|
||||||
obj_ = lv_btn_create(parent);
|
obj_ = lv_btn_create(parent);
|
||||||
|
lv_obj_set_pos(obj_, config_.x, config_.y);
|
||||||
|
lv_obj_set_size(obj_, config_.width > 0 ? config_.width : 100,
|
||||||
|
config_.height > 0 ? config_.height : 50);
|
||||||
|
|
||||||
if (config_.isToggle) {
|
// Test: free function callback
|
||||||
lv_obj_add_flag(obj_, LV_OBJ_FLAG_CHECKABLE);
|
lv_obj_add_event_cb(obj_, button_event_cb, LV_EVENT_CLICKED, NULL);
|
||||||
}
|
|
||||||
|
|
||||||
lv_obj_add_event_cb(obj_, clickCallback, LV_EVENT_CLICKED, this);
|
|
||||||
|
|
||||||
// Create label inside button
|
|
||||||
label_ = lv_label_create(obj_);
|
label_ = lv_label_create(obj_);
|
||||||
lv_label_set_text(label_, config_.text);
|
lv_label_set_text(label_, config_.text);
|
||||||
lv_obj_center(label_);
|
lv_obj_center(label_);
|
||||||
|
|
||||||
lv_obj_set_pos(obj_, config_.x, config_.y);
|
ESP_LOGI(TAG, "Created button '%s' at %d,%d", config_.text, config_.x, config_.y);
|
||||||
if (config_.width > 0 && config_.height > 0) {
|
|
||||||
lv_obj_set_size(obj_, config_.width, config_.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj_;
|
return obj_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
class ButtonWidget : public Widget {
|
class ButtonWidget : public Widget {
|
||||||
public:
|
public:
|
||||||
explicit ButtonWidget(const WidgetConfig& config);
|
explicit ButtonWidget(const WidgetConfig& config);
|
||||||
|
~ButtonWidget() override;
|
||||||
|
|
||||||
lv_obj_t* create(lv_obj_t* parent) override;
|
lv_obj_t* create(lv_obj_t* parent) override;
|
||||||
void applyStyle() override;
|
void applyStyle() override;
|
||||||
|
|||||||
@ -908,6 +908,18 @@
|
|||||||
if (!screen.bgColor) screen.bgColor = '#1A1A2E';
|
if (!screen.bgColor) screen.bgColor = '#1A1A2E';
|
||||||
if (!Array.isArray(screen.widgets)) screen.widgets = [];
|
if (!Array.isArray(screen.widgets)) screen.widgets = [];
|
||||||
|
|
||||||
|
// Modal defaults
|
||||||
|
if (!screen.modal) {
|
||||||
|
screen.modal = { x: 0, y: 0, w: 0, h: 0, radius: 12, dim: true };
|
||||||
|
} else {
|
||||||
|
if (screen.modal.x === undefined) screen.modal.x = 0;
|
||||||
|
if (screen.modal.y === undefined) screen.modal.y = 0;
|
||||||
|
if (screen.modal.w === undefined) screen.modal.w = 0;
|
||||||
|
if (screen.modal.h === undefined) screen.modal.h = 0;
|
||||||
|
if (screen.modal.radius === undefined) screen.modal.radius = 12;
|
||||||
|
if (screen.modal.dim === undefined) screen.modal.dim = true;
|
||||||
|
}
|
||||||
|
|
||||||
screen.widgets.forEach(normalizeWidget);
|
screen.widgets.forEach(normalizeWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user