Backup
This commit is contained in:
parent
f34eb810da
commit
a4cefd7c09
BIN
.cache/clangd/index/ButtonWidget.cpp.6932614AE5FC71F9.idx
Normal file
BIN
.cache/clangd/index/ButtonWidget.cpp.6932614AE5FC71F9.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/ButtonWidget.hpp.551D0D3595AEDB81.idx
Normal file
BIN
.cache/clangd/index/ButtonWidget.hpp.551D0D3595AEDB81.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/ConfigHandlers.cpp.9A6D389FB90DEEB0.idx
Normal file
BIN
.cache/clangd/index/ConfigHandlers.cpp.9A6D389FB90DEEB0.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/FileManagerHandlers.cpp.2F53BB33AABE7329.idx
Normal file
BIN
.cache/clangd/index/FileManagerHandlers.cpp.2F53BB33AABE7329.idx
Normal file
Binary file not shown.
Binary file not shown.
BIN
.cache/clangd/index/KnxHandlers.cpp.CB0E869699204D5E.idx
Normal file
BIN
.cache/clangd/index/KnxHandlers.cpp.CB0E869699204D5E.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/LabelWidget.cpp.86E5A1BF3C34B7BC.idx
Normal file
BIN
.cache/clangd/index/LabelWidget.cpp.86E5A1BF3C34B7BC.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/LabelWidget.hpp.C810267C2FCC36BA.idx
Normal file
BIN
.cache/clangd/index/LabelWidget.hpp.C810267C2FCC36BA.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/LedWidget.cpp.3E101A9B7D9821AD.idx
Normal file
BIN
.cache/clangd/index/LedWidget.cpp.3E101A9B7D9821AD.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/LedWidget.hpp.C3DFB6FAB9F8428E.idx
Normal file
BIN
.cache/clangd/index/LedWidget.hpp.C3DFB6FAB9F8428E.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/StaticFileHandlers.cpp.7B27CFEA2E9C1EF7.idx
Normal file
BIN
.cache/clangd/index/StaticFileHandlers.cpp.7B27CFEA2E9C1EF7.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/StatusHandlers.cpp.1021770D2A2A95F2.idx
Normal file
BIN
.cache/clangd/index/StatusHandlers.cpp.1021770D2A2A95F2.idx
Normal file
Binary file not shown.
Binary file not shown.
BIN
.cache/clangd/index/WebServer.cpp.552E49883800B71B.idx
Normal file
BIN
.cache/clangd/index/WebServer.cpp.552E49883800B71B.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/WebServer.hpp.4D1C3D9E0DDB58E5.idx
Normal file
BIN
.cache/clangd/index/WebServer.hpp.4D1C3D9E0DDB58E5.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/Widget.cpp.63DB7B9186B85891.idx
Normal file
BIN
.cache/clangd/index/Widget.cpp.63DB7B9186B85891.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/Widget.hpp.63E5D98A9B23E60F.idx
Normal file
BIN
.cache/clangd/index/Widget.hpp.63E5D98A9B23E60F.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/WidgetConfig.cpp.FD56F9F36C29A5DA.idx
Normal file
BIN
.cache/clangd/index/WidgetConfig.cpp.FD56F9F36C29A5DA.idx
Normal file
Binary file not shown.
Binary file not shown.
BIN
.cache/clangd/index/WidgetFactory.cpp.1026CAEFCA630F22.idx
Normal file
BIN
.cache/clangd/index/WidgetFactory.cpp.1026CAEFCA630F22.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/WidgetFactory.hpp.6C7FACD7AA34E439.idx
Normal file
BIN
.cache/clangd/index/WidgetFactory.hpp.6C7FACD7AA34E439.idx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,4 +1,9 @@
|
||||
idf_component_register(SRCS "KnxWorker.cpp" "Nvs.cpp" "main.cpp" "Display.cpp" "Touch.cpp" "Gui.cpp" "Wifi.cpp" "LvglIdle.c" "Gui/WifiSetting.cpp" "Gui/EthSetting.cpp" "Hardware/Eth.cpp" "WidgetManager.cpp" "SdCard.cpp"
|
||||
idf_component_register(SRCS "KnxWorker.cpp" "Nvs.cpp" "main.cpp" "Display.cpp" "Touch.cpp" "Gui.cpp" "Wifi.cpp" "LvglIdle.c" "Gui/WifiSetting.cpp" "Gui/EthSetting.cpp" "Hardware/Eth.cpp" "WidgetManager.cpp" "WidgetConfig.cpp" "SdCard.cpp"
|
||||
"widgets/Widget.cpp"
|
||||
"widgets/LabelWidget.cpp"
|
||||
"widgets/ButtonWidget.cpp"
|
||||
"widgets/LedWidget.cpp"
|
||||
"widgets/WidgetFactory.cpp"
|
||||
"webserver/WebServer.cpp"
|
||||
"webserver/StaticFileHandlers.cpp"
|
||||
"webserver/ConfigHandlers.cpp"
|
||||
@ -7,5 +12,5 @@ idf_component_register(SRCS "KnxWorker.cpp" "Nvs.cpp" "main.cpp" "Display.cpp" "
|
||||
"webserver/FileManagerHandlers.cpp"
|
||||
PRIV_REQUIRES spi_flash esp_driver_ppa esp_lcd usb
|
||||
REQUIRES esp_mm esp_eth esp_driver_ppa esp_timer lvgl knx ethernet_init esp_wifi_remote esp_netif esp_event nvs_flash esp_http_server fatfs sdmmc json tinyusb
|
||||
INCLUDE_DIRS "webserver"
|
||||
INCLUDE_DIRS "webserver" "widgets"
|
||||
EMBED_TXTFILES "embedded/filemanager.html")
|
||||
|
||||
222
main/WidgetConfig.cpp
Normal file
222
main/WidgetConfig.cpp
Normal file
@ -0,0 +1,222 @@
|
||||
#include "WidgetConfig.hpp"
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
// WidgetConfig implementation
|
||||
void WidgetConfig::serialize(uint8_t* buf) const {
|
||||
memset(buf, 0, SERIALIZED_SIZE);
|
||||
size_t pos = 0;
|
||||
|
||||
buf[pos++] = id;
|
||||
buf[pos++] = static_cast<uint8_t>(type);
|
||||
buf[pos++] = x & 0xFF; buf[pos++] = (x >> 8) & 0xFF;
|
||||
buf[pos++] = y & 0xFF; buf[pos++] = (y >> 8) & 0xFF;
|
||||
buf[pos++] = width & 0xFF; buf[pos++] = (width >> 8) & 0xFF;
|
||||
buf[pos++] = height & 0xFF; buf[pos++] = (height >> 8) & 0xFF;
|
||||
buf[pos++] = visible ? 1 : 0;
|
||||
|
||||
buf[pos++] = static_cast<uint8_t>(textSource);
|
||||
memcpy(&buf[pos], text, MAX_TEXT_LEN); pos += MAX_TEXT_LEN;
|
||||
buf[pos++] = knxAddress & 0xFF; buf[pos++] = (knxAddress >> 8) & 0xFF;
|
||||
buf[pos++] = fontSize;
|
||||
|
||||
buf[pos++] = textColor.r; buf[pos++] = textColor.g; buf[pos++] = textColor.b;
|
||||
buf[pos++] = bgColor.r; buf[pos++] = bgColor.g; buf[pos++] = bgColor.b;
|
||||
buf[pos++] = bgOpacity;
|
||||
buf[pos++] = borderRadius;
|
||||
|
||||
buf[pos++] = shadow.offsetX;
|
||||
buf[pos++] = shadow.offsetY;
|
||||
buf[pos++] = shadow.blur;
|
||||
buf[pos++] = shadow.spread;
|
||||
buf[pos++] = shadow.color.r; buf[pos++] = shadow.color.g; buf[pos++] = shadow.color.b;
|
||||
buf[pos++] = shadow.enabled ? 1 : 0;
|
||||
|
||||
buf[pos++] = isToggle ? 1 : 0;
|
||||
buf[pos++] = knxAddressWrite & 0xFF; buf[pos++] = (knxAddressWrite >> 8) & 0xFF;
|
||||
buf[pos++] = static_cast<uint8_t>(action);
|
||||
buf[pos++] = targetScreen;
|
||||
}
|
||||
|
||||
void WidgetConfig::deserialize(const uint8_t* buf) {
|
||||
size_t pos = 0;
|
||||
|
||||
id = buf[pos++];
|
||||
type = static_cast<WidgetType>(buf[pos++]);
|
||||
x = buf[pos] | (buf[pos+1] << 8); pos += 2;
|
||||
y = buf[pos] | (buf[pos+1] << 8); pos += 2;
|
||||
width = buf[pos] | (buf[pos+1] << 8); pos += 2;
|
||||
height = buf[pos] | (buf[pos+1] << 8); pos += 2;
|
||||
visible = buf[pos++] != 0;
|
||||
|
||||
textSource = static_cast<TextSource>(buf[pos++]);
|
||||
memcpy(text, &buf[pos], MAX_TEXT_LEN); pos += MAX_TEXT_LEN;
|
||||
text[MAX_TEXT_LEN - 1] = '\0';
|
||||
knxAddress = buf[pos] | (buf[pos+1] << 8); pos += 2;
|
||||
fontSize = buf[pos++];
|
||||
|
||||
textColor.r = buf[pos++]; textColor.g = buf[pos++]; textColor.b = buf[pos++];
|
||||
bgColor.r = buf[pos++]; bgColor.g = buf[pos++]; bgColor.b = buf[pos++];
|
||||
bgOpacity = buf[pos++];
|
||||
borderRadius = buf[pos++];
|
||||
|
||||
shadow.offsetX = static_cast<int8_t>(buf[pos++]);
|
||||
shadow.offsetY = static_cast<int8_t>(buf[pos++]);
|
||||
shadow.blur = buf[pos++];
|
||||
shadow.spread = buf[pos++];
|
||||
shadow.color.r = buf[pos++]; shadow.color.g = buf[pos++]; shadow.color.b = buf[pos++];
|
||||
shadow.enabled = buf[pos++] != 0;
|
||||
|
||||
isToggle = buf[pos++] != 0;
|
||||
knxAddressWrite = buf[pos] | (buf[pos+1] << 8);
|
||||
pos += 2;
|
||||
action = static_cast<ButtonAction>(buf[pos++]);
|
||||
targetScreen = buf[pos++];
|
||||
}
|
||||
|
||||
WidgetConfig WidgetConfig::createLabel(uint8_t id, int16_t x, int16_t y, const char* labelText) {
|
||||
WidgetConfig cfg = {};
|
||||
cfg.id = id;
|
||||
cfg.type = WidgetType::LABEL;
|
||||
cfg.x = x;
|
||||
cfg.y = y;
|
||||
cfg.width = 150;
|
||||
cfg.height = 40;
|
||||
cfg.visible = true;
|
||||
cfg.textSource = TextSource::STATIC;
|
||||
strncpy(cfg.text, labelText, MAX_TEXT_LEN - 1);
|
||||
cfg.fontSize = 1; // 18pt
|
||||
cfg.textColor = {255, 255, 255};
|
||||
cfg.bgColor = {0, 0, 0};
|
||||
cfg.bgOpacity = 0;
|
||||
cfg.borderRadius = 0;
|
||||
cfg.shadow.enabled = false;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
WidgetConfig WidgetConfig::createKnxLabel(uint8_t id, int16_t x, int16_t y,
|
||||
TextSource source, uint16_t knxAddr, const char* format) {
|
||||
WidgetConfig cfg = createLabel(id, x, y, format);
|
||||
cfg.textSource = source;
|
||||
cfg.knxAddress = knxAddr;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
WidgetConfig WidgetConfig::createButton(uint8_t id, int16_t x, int16_t y,
|
||||
const char* labelText, uint16_t knxAddrWrite, bool toggle) {
|
||||
WidgetConfig cfg = {};
|
||||
cfg.id = id;
|
||||
cfg.type = WidgetType::BUTTON;
|
||||
cfg.x = x;
|
||||
cfg.y = y;
|
||||
cfg.width = 120;
|
||||
cfg.height = 50;
|
||||
cfg.visible = true;
|
||||
cfg.textSource = TextSource::STATIC;
|
||||
strncpy(cfg.text, labelText, MAX_TEXT_LEN - 1);
|
||||
cfg.fontSize = 1;
|
||||
cfg.textColor = {255, 255, 255};
|
||||
cfg.bgColor = {33, 150, 243}; // Blue
|
||||
cfg.bgOpacity = 255;
|
||||
cfg.borderRadius = 8;
|
||||
cfg.shadow.enabled = true;
|
||||
cfg.shadow.offsetX = 2;
|
||||
cfg.shadow.offsetY = 2;
|
||||
cfg.shadow.blur = 8;
|
||||
cfg.shadow.spread = 0;
|
||||
cfg.shadow.color = {0, 0, 0};
|
||||
cfg.isToggle = toggle;
|
||||
cfg.knxAddressWrite = knxAddrWrite;
|
||||
cfg.action = ButtonAction::KNX;
|
||||
cfg.targetScreen = 0;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// ScreenConfig implementation
|
||||
void ScreenConfig::clear(uint8_t newId, const char* newName) {
|
||||
id = newId;
|
||||
mode = ScreenMode::FULLSCREEN;
|
||||
backgroundColor = {26, 26, 46}; // Dark blue background
|
||||
widgetCount = 0;
|
||||
memset(widgets, 0, sizeof(widgets));
|
||||
memset(name, 0, sizeof(name));
|
||||
if (newName && newName[0] != '\0') {
|
||||
strncpy(name, newName, sizeof(name) - 1);
|
||||
}
|
||||
// Modal defaults
|
||||
modalX = 0; // 0 = centered
|
||||
modalY = 0; // 0 = centered
|
||||
modalWidth = 0; // 0 = auto
|
||||
modalHeight = 0; // 0 = auto
|
||||
modalBorderRadius = 12;
|
||||
modalDimBackground = true;
|
||||
}
|
||||
|
||||
int ScreenConfig::addWidget(const WidgetConfig& widget) {
|
||||
if (widgetCount >= MAX_WIDGETS) return -1;
|
||||
|
||||
// Find next free ID
|
||||
uint8_t newId = 0;
|
||||
for (uint8_t i = 0; i < widgetCount; i++) {
|
||||
if (widgets[i].id >= newId) newId = widgets[i].id + 1;
|
||||
}
|
||||
|
||||
widgets[widgetCount] = widget;
|
||||
widgets[widgetCount].id = newId;
|
||||
widgetCount++;
|
||||
return newId;
|
||||
}
|
||||
|
||||
bool ScreenConfig::removeWidget(uint8_t id) {
|
||||
for (uint8_t i = 0; i < widgetCount; i++) {
|
||||
if (widgets[i].id == id) {
|
||||
// Shift remaining widgets
|
||||
for (uint8_t j = i; j < widgetCount - 1; j++) {
|
||||
widgets[j] = widgets[j + 1];
|
||||
}
|
||||
widgetCount--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WidgetConfig* ScreenConfig::findWidget(uint8_t id) {
|
||||
for (uint8_t i = 0; i < widgetCount; i++) {
|
||||
if (widgets[i].id == id) return &widgets[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const WidgetConfig* ScreenConfig::findWidget(uint8_t id) const {
|
||||
for (uint8_t i = 0; i < widgetCount; i++) {
|
||||
if (widgets[i].id == id) return &widgets[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// GuiConfig implementation
|
||||
void GuiConfig::clear() {
|
||||
screenCount = 0;
|
||||
startScreenId = 0;
|
||||
standbyEnabled = false;
|
||||
standbyScreenId = 0xFF;
|
||||
standbyMinutes = 0;
|
||||
for (size_t i = 0; i < MAX_SCREENS; i++) {
|
||||
screens[i].clear(static_cast<uint8_t>(i), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
ScreenConfig* GuiConfig::findScreen(uint8_t id) {
|
||||
for (uint8_t i = 0; i < screenCount; i++) {
|
||||
if (screens[i].id == id) return &screens[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ScreenConfig* GuiConfig::findScreen(uint8_t id) const {
|
||||
for (uint8_t i = 0; i < screenCount; i++) {
|
||||
if (screens[i].id == id) return &screens[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@ -121,6 +121,14 @@ struct ScreenConfig {
|
||||
uint8_t widgetCount;
|
||||
WidgetConfig widgets[MAX_WIDGETS];
|
||||
|
||||
// Modal-specific properties (only used when mode == MODAL)
|
||||
int16_t modalX; // Modal position X (0 = centered)
|
||||
int16_t modalY; // Modal position Y (0 = centered)
|
||||
int16_t modalWidth; // Modal width (0 = auto from content)
|
||||
int16_t modalHeight; // Modal height (0 = auto from content)
|
||||
uint8_t modalBorderRadius;
|
||||
bool modalDimBackground; // Dim the background behind modal
|
||||
|
||||
void clear(uint8_t newId = 0, const char* newName = nullptr);
|
||||
int addWidget(const WidgetConfig& widget); // Returns widget ID or -1
|
||||
bool removeWidget(uint8_t id);
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
#include "WidgetManager.hpp"
|
||||
#include "widgets/WidgetFactory.hpp"
|
||||
#include "SdCard.hpp"
|
||||
#include "esp_lv_adapter.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "Gui.hpp"
|
||||
#include "cJSON.h"
|
||||
#include <memory>
|
||||
#include <new>
|
||||
@ -14,226 +14,6 @@
|
||||
static const char* TAG = "WidgetMgr";
|
||||
static constexpr uint8_t SCREEN_ID_NONE = 0xFF;
|
||||
|
||||
// Button click callback
|
||||
static void button_click_cb(lv_event_t* e) {
|
||||
WidgetConfig* cfg = static_cast<WidgetConfig*>(lv_event_get_user_data(e));
|
||||
if (!cfg) return;
|
||||
lv_obj_t* target = static_cast<lv_obj_t*>(lv_event_get_target(e));
|
||||
WidgetManager::instance().handleButtonAction(*cfg, target);
|
||||
}
|
||||
|
||||
// WidgetConfig implementation
|
||||
void WidgetConfig::serialize(uint8_t* buf) const {
|
||||
memset(buf, 0, SERIALIZED_SIZE);
|
||||
size_t pos = 0;
|
||||
|
||||
buf[pos++] = id;
|
||||
buf[pos++] = static_cast<uint8_t>(type);
|
||||
buf[pos++] = x & 0xFF; buf[pos++] = (x >> 8) & 0xFF;
|
||||
buf[pos++] = y & 0xFF; buf[pos++] = (y >> 8) & 0xFF;
|
||||
buf[pos++] = width & 0xFF; buf[pos++] = (width >> 8) & 0xFF;
|
||||
buf[pos++] = height & 0xFF; buf[pos++] = (height >> 8) & 0xFF;
|
||||
buf[pos++] = visible ? 1 : 0;
|
||||
|
||||
buf[pos++] = static_cast<uint8_t>(textSource);
|
||||
memcpy(&buf[pos], text, MAX_TEXT_LEN); pos += MAX_TEXT_LEN;
|
||||
buf[pos++] = knxAddress & 0xFF; buf[pos++] = (knxAddress >> 8) & 0xFF;
|
||||
buf[pos++] = fontSize;
|
||||
|
||||
buf[pos++] = textColor.r; buf[pos++] = textColor.g; buf[pos++] = textColor.b;
|
||||
buf[pos++] = bgColor.r; buf[pos++] = bgColor.g; buf[pos++] = bgColor.b;
|
||||
buf[pos++] = bgOpacity;
|
||||
buf[pos++] = borderRadius;
|
||||
|
||||
buf[pos++] = shadow.offsetX;
|
||||
buf[pos++] = shadow.offsetY;
|
||||
buf[pos++] = shadow.blur;
|
||||
buf[pos++] = shadow.spread;
|
||||
buf[pos++] = shadow.color.r; buf[pos++] = shadow.color.g; buf[pos++] = shadow.color.b;
|
||||
buf[pos++] = shadow.enabled ? 1 : 0;
|
||||
|
||||
buf[pos++] = isToggle ? 1 : 0;
|
||||
buf[pos++] = knxAddressWrite & 0xFF; buf[pos++] = (knxAddressWrite >> 8) & 0xFF;
|
||||
buf[pos++] = static_cast<uint8_t>(action);
|
||||
buf[pos++] = targetScreen;
|
||||
}
|
||||
|
||||
void WidgetConfig::deserialize(const uint8_t* buf) {
|
||||
size_t pos = 0;
|
||||
|
||||
id = buf[pos++];
|
||||
type = static_cast<WidgetType>(buf[pos++]);
|
||||
x = buf[pos] | (buf[pos+1] << 8); pos += 2;
|
||||
y = buf[pos] | (buf[pos+1] << 8); pos += 2;
|
||||
width = buf[pos] | (buf[pos+1] << 8); pos += 2;
|
||||
height = buf[pos] | (buf[pos+1] << 8); pos += 2;
|
||||
visible = buf[pos++] != 0;
|
||||
|
||||
textSource = static_cast<TextSource>(buf[pos++]);
|
||||
memcpy(text, &buf[pos], MAX_TEXT_LEN); pos += MAX_TEXT_LEN;
|
||||
text[MAX_TEXT_LEN - 1] = '\0';
|
||||
knxAddress = buf[pos] | (buf[pos+1] << 8); pos += 2;
|
||||
fontSize = buf[pos++];
|
||||
|
||||
textColor.r = buf[pos++]; textColor.g = buf[pos++]; textColor.b = buf[pos++];
|
||||
bgColor.r = buf[pos++]; bgColor.g = buf[pos++]; bgColor.b = buf[pos++];
|
||||
bgOpacity = buf[pos++];
|
||||
borderRadius = buf[pos++];
|
||||
|
||||
shadow.offsetX = static_cast<int8_t>(buf[pos++]);
|
||||
shadow.offsetY = static_cast<int8_t>(buf[pos++]);
|
||||
shadow.blur = buf[pos++];
|
||||
shadow.spread = buf[pos++];
|
||||
shadow.color.r = buf[pos++]; shadow.color.g = buf[pos++]; shadow.color.b = buf[pos++];
|
||||
shadow.enabled = buf[pos++] != 0;
|
||||
|
||||
isToggle = buf[pos++] != 0;
|
||||
knxAddressWrite = buf[pos] | (buf[pos+1] << 8);
|
||||
pos += 2;
|
||||
action = static_cast<ButtonAction>(buf[pos++]);
|
||||
targetScreen = buf[pos++];
|
||||
}
|
||||
|
||||
WidgetConfig WidgetConfig::createLabel(uint8_t id, int16_t x, int16_t y, const char* labelText) {
|
||||
WidgetConfig cfg = {};
|
||||
cfg.id = id;
|
||||
cfg.type = WidgetType::LABEL;
|
||||
cfg.x = x;
|
||||
cfg.y = y;
|
||||
cfg.width = 150;
|
||||
cfg.height = 40;
|
||||
cfg.visible = true;
|
||||
cfg.textSource = TextSource::STATIC;
|
||||
strncpy(cfg.text, labelText, MAX_TEXT_LEN - 1);
|
||||
cfg.fontSize = 1; // 18pt
|
||||
cfg.textColor = {255, 255, 255};
|
||||
cfg.bgColor = {0, 0, 0};
|
||||
cfg.bgOpacity = 0;
|
||||
cfg.borderRadius = 0;
|
||||
cfg.shadow.enabled = false;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
WidgetConfig WidgetConfig::createKnxLabel(uint8_t id, int16_t x, int16_t y,
|
||||
TextSource source, uint16_t knxAddr, const char* format) {
|
||||
WidgetConfig cfg = createLabel(id, x, y, format);
|
||||
cfg.textSource = source;
|
||||
cfg.knxAddress = knxAddr;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
WidgetConfig WidgetConfig::createButton(uint8_t id, int16_t x, int16_t y,
|
||||
const char* labelText, uint16_t knxAddrWrite, bool toggle) {
|
||||
WidgetConfig cfg = {};
|
||||
cfg.id = id;
|
||||
cfg.type = WidgetType::BUTTON;
|
||||
cfg.x = x;
|
||||
cfg.y = y;
|
||||
cfg.width = 120;
|
||||
cfg.height = 50;
|
||||
cfg.visible = true;
|
||||
cfg.textSource = TextSource::STATIC;
|
||||
strncpy(cfg.text, labelText, MAX_TEXT_LEN - 1);
|
||||
cfg.fontSize = 1;
|
||||
cfg.textColor = {255, 255, 255};
|
||||
cfg.bgColor = {33, 150, 243}; // Blue
|
||||
cfg.bgOpacity = 255;
|
||||
cfg.borderRadius = 8;
|
||||
cfg.shadow.enabled = true;
|
||||
cfg.shadow.offsetX = 2;
|
||||
cfg.shadow.offsetY = 2;
|
||||
cfg.shadow.blur = 8;
|
||||
cfg.shadow.spread = 0;
|
||||
cfg.shadow.color = {0, 0, 0};
|
||||
cfg.isToggle = toggle;
|
||||
cfg.knxAddressWrite = knxAddrWrite;
|
||||
cfg.action = ButtonAction::KNX;
|
||||
cfg.targetScreen = 0;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// ScreenConfig implementation
|
||||
void ScreenConfig::clear(uint8_t newId, const char* newName) {
|
||||
id = newId;
|
||||
mode = ScreenMode::FULLSCREEN;
|
||||
backgroundColor = {26, 26, 46}; // Dark blue background
|
||||
widgetCount = 0;
|
||||
memset(widgets, 0, sizeof(widgets));
|
||||
memset(name, 0, sizeof(name));
|
||||
if (newName && newName[0] != '\0') {
|
||||
strncpy(name, newName, sizeof(name) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
int ScreenConfig::addWidget(const WidgetConfig& widget) {
|
||||
if (widgetCount >= MAX_WIDGETS) return -1;
|
||||
|
||||
// Find next free ID
|
||||
uint8_t newId = 0;
|
||||
for (uint8_t i = 0; i < widgetCount; i++) {
|
||||
if (widgets[i].id >= newId) newId = widgets[i].id + 1;
|
||||
}
|
||||
|
||||
widgets[widgetCount] = widget;
|
||||
widgets[widgetCount].id = newId;
|
||||
widgetCount++;
|
||||
return newId;
|
||||
}
|
||||
|
||||
bool ScreenConfig::removeWidget(uint8_t id) {
|
||||
for (uint8_t i = 0; i < widgetCount; i++) {
|
||||
if (widgets[i].id == id) {
|
||||
// Shift remaining widgets
|
||||
for (uint8_t j = i; j < widgetCount - 1; j++) {
|
||||
widgets[j] = widgets[j + 1];
|
||||
}
|
||||
widgetCount--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WidgetConfig* ScreenConfig::findWidget(uint8_t id) {
|
||||
for (uint8_t i = 0; i < widgetCount; i++) {
|
||||
if (widgets[i].id == id) return &widgets[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const WidgetConfig* ScreenConfig::findWidget(uint8_t id) const {
|
||||
for (uint8_t i = 0; i < widgetCount; i++) {
|
||||
if (widgets[i].id == id) return &widgets[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// GuiConfig implementation
|
||||
void GuiConfig::clear() {
|
||||
screenCount = 0;
|
||||
startScreenId = 0;
|
||||
standbyEnabled = false;
|
||||
standbyScreenId = 0xFF;
|
||||
standbyMinutes = 0;
|
||||
for (size_t i = 0; i < MAX_SCREENS; i++) {
|
||||
screens[i].clear(static_cast<uint8_t>(i), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
ScreenConfig* GuiConfig::findScreen(uint8_t id) {
|
||||
for (uint8_t i = 0; i < screenCount; i++) {
|
||||
if (screens[i].id == id) return &screens[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ScreenConfig* GuiConfig::findScreen(uint8_t id) const {
|
||||
for (uint8_t i = 0; i < screenCount; i++) {
|
||||
if (screens[i].id == id) return &screens[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// WidgetManager implementation
|
||||
WidgetManager& WidgetManager::instance() {
|
||||
static WidgetManager inst;
|
||||
@ -241,7 +21,7 @@ WidgetManager& WidgetManager::instance() {
|
||||
}
|
||||
|
||||
WidgetManager::WidgetManager() {
|
||||
widgetObjects_.fill(nullptr);
|
||||
// widgets_ is default-initialized to nullptr
|
||||
createDefaultConfig();
|
||||
activeScreenId_ = config_.startScreenId;
|
||||
lastActivityUs_ = esp_timer_get_time();
|
||||
@ -296,7 +76,6 @@ void WidgetManager::loadFromSdCard() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get file size
|
||||
fseek(f, 0, SEEK_END);
|
||||
long size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
@ -307,7 +86,6 @@ void WidgetManager::loadFromSdCard() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read file content
|
||||
char* json = new char[size + 1];
|
||||
if (!json) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for config");
|
||||
@ -319,7 +97,6 @@ void WidgetManager::loadFromSdCard() {
|
||||
fclose(f);
|
||||
json[read] = '\0';
|
||||
|
||||
// Parse JSON using cJSON
|
||||
bool success = updateConfigFromJson(json);
|
||||
delete[] json;
|
||||
|
||||
@ -336,7 +113,6 @@ void WidgetManager::saveToSdCard() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate JSON using cJSON
|
||||
char* json = new char[32768];
|
||||
if (!json) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for JSON");
|
||||
@ -345,7 +121,6 @@ void WidgetManager::saveToSdCard() {
|
||||
|
||||
getConfigJson(json, 32768);
|
||||
|
||||
// Write to file
|
||||
FILE* f = fopen(CONFIG_FILE, "w");
|
||||
if (!f) {
|
||||
ESP_LOGE(TAG, "Failed to open config file for writing");
|
||||
@ -417,10 +192,11 @@ void WidgetManager::applyScreen(uint8_t screenId) {
|
||||
closeModal();
|
||||
}
|
||||
|
||||
// First destroy C++ widgets (which deletes LVGL objects)
|
||||
destroyAllWidgets();
|
||||
|
||||
if (esp_lv_adapter_lock(-1) == ESP_OK) {
|
||||
lv_obj_t* root = lv_scr_act();
|
||||
lv_obj_clean(root);
|
||||
widgetObjects_.fill(nullptr);
|
||||
createAllWidgets(*screen, root);
|
||||
esp_lv_adapter_unlock();
|
||||
}
|
||||
@ -431,34 +207,111 @@ void WidgetManager::showModalScreen(const ScreenConfig& screen) {
|
||||
closeModal();
|
||||
}
|
||||
|
||||
// Destroy any existing widgets before creating modal widgets
|
||||
destroyAllWidgets();
|
||||
|
||||
if (esp_lv_adapter_lock(-1) != ESP_OK) return;
|
||||
|
||||
lv_obj_t* overlay = lv_obj_create(lv_layer_top());
|
||||
lv_obj_set_style_bg_opa(overlay, LV_OPA_COVER, 0);
|
||||
lv_obj_clear_flag(overlay, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
lv_disp_t* disp = lv_disp_get_default();
|
||||
int32_t hor = disp ? lv_disp_get_hor_res(disp) : 1280;
|
||||
int32_t ver = disp ? lv_disp_get_ver_res(disp) : 800;
|
||||
lv_obj_set_size(overlay, hor, ver);
|
||||
int32_t dispWidth = disp ? lv_disp_get_hor_res(disp) : 1280;
|
||||
int32_t dispHeight = disp ? lv_disp_get_ver_res(disp) : 800;
|
||||
|
||||
modalContainer_ = overlay;
|
||||
// Create semi-transparent background overlay if dimming enabled
|
||||
lv_obj_t* dimmer = nullptr;
|
||||
if (screen.modalDimBackground) {
|
||||
dimmer = lv_obj_create(lv_layer_top());
|
||||
lv_obj_remove_style_all(dimmer);
|
||||
lv_obj_set_size(dimmer, dispWidth, dispHeight);
|
||||
lv_obj_set_style_bg_color(dimmer, lv_color_black(), 0);
|
||||
lv_obj_set_style_bg_opa(dimmer, LV_OPA_50, 0);
|
||||
lv_obj_clear_flag(dimmer, LV_OBJ_FLAG_SCROLLABLE);
|
||||
}
|
||||
|
||||
// Create modal container
|
||||
lv_obj_t* modal = lv_obj_create(lv_layer_top());
|
||||
lv_obj_clear_flag(modal, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
// Calculate modal size
|
||||
int32_t modalWidth = screen.modalWidth;
|
||||
int32_t modalHeight = screen.modalHeight;
|
||||
|
||||
// Auto-size: calculate from widget bounds if not specified
|
||||
if (modalWidth <= 0 || modalHeight <= 0) {
|
||||
int32_t maxX = 0, maxY = 0;
|
||||
for (uint8_t i = 0; i < screen.widgetCount; i++) {
|
||||
const WidgetConfig& w = screen.widgets[i];
|
||||
if (w.visible) {
|
||||
int32_t right = w.x + w.width;
|
||||
int32_t bottom = w.y + w.height;
|
||||
if (right > maxX) maxX = right;
|
||||
if (bottom > maxY) maxY = bottom;
|
||||
}
|
||||
}
|
||||
if (modalWidth <= 0) modalWidth = maxX + 40; // Add padding
|
||||
if (modalHeight <= 0) modalHeight = maxY + 40;
|
||||
}
|
||||
|
||||
lv_obj_set_size(modal, modalWidth, modalHeight);
|
||||
|
||||
// Position modal (0 = centered)
|
||||
if (screen.modalX == 0 && screen.modalY == 0) {
|
||||
lv_obj_center(modal);
|
||||
} else {
|
||||
lv_obj_set_pos(modal, screen.modalX, screen.modalY);
|
||||
}
|
||||
|
||||
// Style modal
|
||||
lv_obj_set_style_bg_color(modal, lv_color_make(
|
||||
screen.backgroundColor.r,
|
||||
screen.backgroundColor.g,
|
||||
screen.backgroundColor.b), 0);
|
||||
lv_obj_set_style_bg_opa(modal, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_radius(modal, screen.modalBorderRadius, 0);
|
||||
lv_obj_set_style_border_width(modal, 0, 0);
|
||||
lv_obj_set_style_pad_all(modal, 0, 0);
|
||||
|
||||
// Add shadow for modal
|
||||
lv_obj_set_style_shadow_color(modal, lv_color_black(), 0);
|
||||
lv_obj_set_style_shadow_opa(modal, LV_OPA_30, 0);
|
||||
lv_obj_set_style_shadow_width(modal, 20, 0);
|
||||
lv_obj_set_style_shadow_spread(modal, 5, 0);
|
||||
|
||||
modalContainer_ = modal;
|
||||
modalDimmer_ = dimmer;
|
||||
modalScreenId_ = screen.id;
|
||||
createAllWidgets(screen, modalContainer_);
|
||||
|
||||
// Create widgets inside modal (not on full screen)
|
||||
screen_ = modal;
|
||||
for (uint8_t i = 0; i < screen.widgetCount; i++) {
|
||||
const WidgetConfig& cfg = screen.widgets[i];
|
||||
auto widget = WidgetFactory::create(cfg);
|
||||
if (widget && cfg.id < MAX_WIDGETS) {
|
||||
widget->create(modal);
|
||||
widget->applyStyle();
|
||||
widgets_[cfg.id] = std::move(widget);
|
||||
}
|
||||
}
|
||||
|
||||
esp_lv_adapter_unlock();
|
||||
ESP_LOGI(TAG, "Modal screen %d opened (%ldx%ld)", screen.id, modalWidth, modalHeight);
|
||||
}
|
||||
|
||||
void WidgetManager::closeModal() {
|
||||
if (!modalContainer_) return;
|
||||
|
||||
// First destroy C++ widgets (which deletes their LVGL objects)
|
||||
destroyAllWidgets();
|
||||
|
||||
if (esp_lv_adapter_lock(-1) == ESP_OK) {
|
||||
if (modalDimmer_) {
|
||||
lv_obj_delete(modalDimmer_);
|
||||
}
|
||||
lv_obj_delete(modalContainer_);
|
||||
esp_lv_adapter_unlock();
|
||||
}
|
||||
modalContainer_ = nullptr;
|
||||
modalDimmer_ = nullptr;
|
||||
modalScreenId_ = SCREEN_ID_NONE;
|
||||
widgetObjects_.fill(nullptr);
|
||||
}
|
||||
|
||||
void WidgetManager::showScreen(uint8_t screenId) {
|
||||
@ -505,7 +358,6 @@ void WidgetManager::handleButtonAction(const WidgetConfig& cfg, lv_obj_t* target
|
||||
ESP_LOGI(TAG, "Button %d clicked, KNX write to %d, state=%d",
|
||||
cfg.id, cfg.knxAddressWrite, state);
|
||||
// TODO: Send KNX telegram
|
||||
// Gui::knxWorker.writeSwitch(cfg.knxAddressWrite, state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -586,175 +438,37 @@ void WidgetManager::onUserActivity() {
|
||||
}
|
||||
|
||||
void WidgetManager::destroyAllWidgets() {
|
||||
for (auto& obj : widgetObjects_) {
|
||||
if (obj != nullptr) {
|
||||
lv_obj_delete(obj);
|
||||
obj = nullptr;
|
||||
}
|
||||
for (auto& widget : widgets_) {
|
||||
widget.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void WidgetManager::createAllWidgets(const ScreenConfig& screen, lv_obj_t* parent) {
|
||||
screen_ = parent;
|
||||
widgetObjects_.fill(nullptr);
|
||||
|
||||
// Set background color
|
||||
lv_obj_set_style_bg_color(parent, lv_color_make(
|
||||
screen.backgroundColor.r,
|
||||
screen.backgroundColor.g,
|
||||
screen.backgroundColor.b), 0);
|
||||
lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0);
|
||||
|
||||
// Create all widgets
|
||||
for (uint8_t i = 0; i < screen.widgetCount; i++) {
|
||||
const WidgetConfig& cfg = screen.widgets[i];
|
||||
lv_obj_t* obj = createWidget(cfg, parent);
|
||||
if (obj != nullptr && cfg.id < MAX_WIDGETS) {
|
||||
widgetObjects_[cfg.id] = obj;
|
||||
auto widget = WidgetFactory::create(cfg);
|
||||
if (widget && cfg.id < MAX_WIDGETS) {
|
||||
widget->create(parent);
|
||||
widget->applyStyle();
|
||||
widgets_[cfg.id] = std::move(widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lv_obj_t* WidgetManager::createWidget(const WidgetConfig& cfg, lv_obj_t* parent) {
|
||||
if (!cfg.visible) return nullptr;
|
||||
|
||||
lv_obj_t* obj = nullptr;
|
||||
|
||||
switch (cfg.type) {
|
||||
case WidgetType::LABEL: {
|
||||
obj = lv_label_create(parent);
|
||||
lv_label_set_text(obj, cfg.text);
|
||||
break;
|
||||
}
|
||||
case WidgetType::BUTTON: {
|
||||
obj = lv_btn_create(parent);
|
||||
if (cfg.isToggle) {
|
||||
lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE);
|
||||
}
|
||||
lv_obj_add_event_cb(obj, button_click_cb, LV_EVENT_CLICKED,
|
||||
const_cast<WidgetConfig*>(&cfg));
|
||||
|
||||
// Create label inside button
|
||||
lv_obj_t* label = lv_label_create(obj);
|
||||
lv_label_set_text(label, cfg.text);
|
||||
lv_obj_set_style_text_color(label, lv_color_make(
|
||||
cfg.textColor.r, cfg.textColor.g, cfg.textColor.b), 0);
|
||||
lv_obj_set_style_text_font(label, getFontBySize(cfg.fontSize), 0);
|
||||
lv_obj_center(label);
|
||||
break;
|
||||
}
|
||||
case WidgetType::LED: {
|
||||
obj = lv_led_create(parent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (obj != nullptr) {
|
||||
lv_obj_set_pos(obj, cfg.x, cfg.y);
|
||||
if (cfg.width > 0 && cfg.height > 0) {
|
||||
lv_obj_set_size(obj, cfg.width, cfg.height);
|
||||
}
|
||||
if (cfg.type == WidgetType::LED) {
|
||||
applyLedStyle(obj, cfg);
|
||||
} else {
|
||||
applyStyle(obj, cfg);
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void WidgetManager::applyStyle(lv_obj_t* obj, const WidgetConfig& cfg) {
|
||||
// Text color
|
||||
lv_obj_set_style_text_color(obj, lv_color_make(
|
||||
cfg.textColor.r, cfg.textColor.g, cfg.textColor.b), 0);
|
||||
|
||||
// Font
|
||||
lv_obj_set_style_text_font(obj, getFontBySize(cfg.fontSize), 0);
|
||||
|
||||
// Background (for buttons and labels with bg)
|
||||
if (cfg.bgOpacity > 0) {
|
||||
lv_obj_set_style_bg_color(obj, lv_color_make(
|
||||
cfg.bgColor.r, cfg.bgColor.g, cfg.bgColor.b), 0);
|
||||
lv_obj_set_style_bg_opa(obj, cfg.bgOpacity, 0);
|
||||
}
|
||||
|
||||
// Border radius
|
||||
if (cfg.borderRadius > 0) {
|
||||
lv_obj_set_style_radius(obj, cfg.borderRadius, 0);
|
||||
}
|
||||
|
||||
// Shadow
|
||||
if (cfg.shadow.enabled) {
|
||||
lv_obj_set_style_shadow_color(obj, lv_color_make(
|
||||
cfg.shadow.color.r, cfg.shadow.color.g, cfg.shadow.color.b), 0);
|
||||
lv_obj_set_style_shadow_opa(obj, 180, 0);
|
||||
lv_obj_set_style_shadow_width(obj, cfg.shadow.blur, 0);
|
||||
lv_obj_set_style_shadow_spread(obj, cfg.shadow.spread, 0);
|
||||
lv_obj_set_style_shadow_offset_x(obj, cfg.shadow.offsetX, 0);
|
||||
lv_obj_set_style_shadow_offset_y(obj, cfg.shadow.offsetY, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void WidgetManager::applyLedStyle(lv_obj_t* obj, const WidgetConfig& cfg) {
|
||||
lv_obj_set_style_radius(obj, LV_RADIUS_CIRCLE, 0);
|
||||
lv_led_set_color(obj, lv_color_make(
|
||||
cfg.bgColor.r, cfg.bgColor.g, cfg.bgColor.b));
|
||||
lv_led_set_brightness(obj, cfg.bgOpacity);
|
||||
|
||||
if (cfg.shadow.enabled) {
|
||||
lv_obj_set_style_shadow_color(obj, lv_color_make(
|
||||
cfg.shadow.color.r, cfg.shadow.color.g, cfg.shadow.color.b), 0);
|
||||
lv_obj_set_style_shadow_opa(obj, 180, 0);
|
||||
lv_obj_set_style_shadow_width(obj, cfg.shadow.blur, 0);
|
||||
lv_obj_set_style_shadow_spread(obj, cfg.shadow.spread, 0);
|
||||
lv_obj_set_style_shadow_offset_x(obj, cfg.shadow.offsetX, 0);
|
||||
lv_obj_set_style_shadow_offset_y(obj, cfg.shadow.offsetY, 0);
|
||||
}
|
||||
}
|
||||
|
||||
const lv_font_t* WidgetManager::getFontBySize(uint8_t sizeIndex) {
|
||||
// Font sizes: 0=14, 1=18, 2=22, 3=28, 4=36, 5=48
|
||||
// These must be enabled in sdkconfig (CONFIG_LV_FONT_MONTSERRAT_*)
|
||||
switch (sizeIndex) {
|
||||
case 0: return &lv_font_montserrat_14;
|
||||
#if LV_FONT_MONTSERRAT_18
|
||||
case 1: return &lv_font_montserrat_18;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_22
|
||||
case 2: return &lv_font_montserrat_22;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_28
|
||||
case 3: return &lv_font_montserrat_28;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_36
|
||||
case 4: return &lv_font_montserrat_36;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_48
|
||||
case 5: return &lv_font_montserrat_48;
|
||||
#endif
|
||||
default: return &lv_font_montserrat_14;
|
||||
}
|
||||
}
|
||||
|
||||
void WidgetManager::onKnxValue(uint16_t groupAddr, float value) {
|
||||
if (esp_lv_adapter_lock(100) != ESP_OK) return;
|
||||
|
||||
const ScreenConfig* screen = activeScreen();
|
||||
if (!screen) {
|
||||
esp_lv_adapter_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < screen->widgetCount; i++) {
|
||||
const WidgetConfig& cfg = screen->widgets[i];
|
||||
if (cfg.knxAddress == groupAddr && cfg.textSource == TextSource::KNX_DPT_TEMP) {
|
||||
lv_obj_t* obj = widgetObjects_[cfg.id];
|
||||
if (obj != nullptr) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), cfg.text, value);
|
||||
lv_label_set_text(obj, buf);
|
||||
}
|
||||
for (auto& widget : widgets_) {
|
||||
if (widget && widget->getKnxAddress() == groupAddr) {
|
||||
widget->onKnxValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -764,24 +478,9 @@ void WidgetManager::onKnxValue(uint16_t groupAddr, float value) {
|
||||
void WidgetManager::onKnxSwitch(uint16_t groupAddr, bool value) {
|
||||
if (esp_lv_adapter_lock(100) != ESP_OK) return;
|
||||
|
||||
const ScreenConfig* screen = activeScreen();
|
||||
if (!screen) {
|
||||
esp_lv_adapter_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < screen->widgetCount; i++) {
|
||||
const WidgetConfig& cfg = screen->widgets[i];
|
||||
if (cfg.knxAddress == groupAddr && cfg.textSource == TextSource::KNX_DPT_SWITCH) {
|
||||
lv_obj_t* obj = widgetObjects_[cfg.id];
|
||||
if (obj == nullptr) continue;
|
||||
|
||||
if (cfg.type == WidgetType::LABEL) {
|
||||
lv_label_set_text(obj, value ? "EIN" : "AUS");
|
||||
} else if (cfg.type == WidgetType::LED) {
|
||||
uint8_t brightness = value ? (cfg.bgOpacity > 0 ? cfg.bgOpacity : 255) : 0;
|
||||
lv_led_set_brightness(obj, brightness);
|
||||
}
|
||||
for (auto& widget : widgets_) {
|
||||
if (widget && widget->getKnxAddress() == groupAddr) {
|
||||
widget->onKnxSwitch(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -791,25 +490,21 @@ void WidgetManager::onKnxSwitch(uint16_t groupAddr, bool value) {
|
||||
void WidgetManager::onKnxText(uint16_t groupAddr, const char* text) {
|
||||
if (esp_lv_adapter_lock(100) != ESP_OK) return;
|
||||
|
||||
const ScreenConfig* screen = activeScreen();
|
||||
if (!screen) {
|
||||
esp_lv_adapter_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < screen->widgetCount; i++) {
|
||||
const WidgetConfig& cfg = screen->widgets[i];
|
||||
if (cfg.knxAddress == groupAddr && cfg.textSource == TextSource::KNX_DPT_TEXT) {
|
||||
lv_obj_t* obj = widgetObjects_[cfg.id];
|
||||
if (obj != nullptr) {
|
||||
lv_label_set_text(obj, text);
|
||||
}
|
||||
for (auto& widget : widgets_) {
|
||||
if (widget && widget->getKnxAddress() == groupAddr) {
|
||||
widget->onKnxText(text);
|
||||
}
|
||||
}
|
||||
|
||||
esp_lv_adapter_unlock();
|
||||
}
|
||||
|
||||
// Helper function to parse hex color string
|
||||
static uint32_t parseHexColor(const char* colorStr) {
|
||||
if (!colorStr || colorStr[0] != '#') return 0;
|
||||
return strtoul(colorStr + 1, nullptr, 16);
|
||||
}
|
||||
|
||||
void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
if (!root) {
|
||||
@ -824,7 +519,6 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
|
||||
cJSON_AddNumberToObject(standby, "screen", config_.standbyScreenId);
|
||||
cJSON_AddNumberToObject(standby, "minutes", config_.standbyMinutes);
|
||||
|
||||
// Add screens array
|
||||
cJSON* screens = cJSON_AddArrayToObject(root, "screens");
|
||||
|
||||
for (uint8_t s = 0; s < config_.screenCount; s++) {
|
||||
@ -840,6 +534,17 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
|
||||
screen.backgroundColor.r, screen.backgroundColor.g, screen.backgroundColor.b);
|
||||
cJSON_AddStringToObject(screenJson, "bgColor", bgColorStr);
|
||||
|
||||
// Modal-specific properties
|
||||
if (screen.mode == ScreenMode::MODAL) {
|
||||
cJSON* modal = cJSON_AddObjectToObject(screenJson, "modal");
|
||||
cJSON_AddNumberToObject(modal, "x", screen.modalX);
|
||||
cJSON_AddNumberToObject(modal, "y", screen.modalY);
|
||||
cJSON_AddNumberToObject(modal, "w", screen.modalWidth);
|
||||
cJSON_AddNumberToObject(modal, "h", screen.modalHeight);
|
||||
cJSON_AddNumberToObject(modal, "radius", screen.modalBorderRadius);
|
||||
cJSON_AddBoolToObject(modal, "dim", screen.modalDimBackground);
|
||||
}
|
||||
|
||||
cJSON* widgets = cJSON_AddArrayToObject(screenJson, "widgets");
|
||||
for (uint8_t i = 0; i < screen.widgetCount; i++) {
|
||||
const WidgetConfig& w = screen.widgets[i];
|
||||
@ -857,13 +562,11 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
|
||||
cJSON_AddNumberToObject(widget, "knxAddr", w.knxAddress);
|
||||
cJSON_AddNumberToObject(widget, "fontSize", w.fontSize);
|
||||
|
||||
// Text color
|
||||
char textColorStr[8];
|
||||
snprintf(textColorStr, sizeof(textColorStr), "#%02X%02X%02X",
|
||||
w.textColor.r, w.textColor.g, w.textColor.b);
|
||||
cJSON_AddStringToObject(widget, "textColor", textColorStr);
|
||||
|
||||
// Background color
|
||||
char widgetBgColorStr[8];
|
||||
snprintf(widgetBgColorStr, sizeof(widgetBgColorStr), "#%02X%02X%02X",
|
||||
w.bgColor.r, w.bgColor.g, w.bgColor.b);
|
||||
@ -872,7 +575,6 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
|
||||
cJSON_AddNumberToObject(widget, "bgOpacity", w.bgOpacity);
|
||||
cJSON_AddNumberToObject(widget, "radius", w.borderRadius);
|
||||
|
||||
// Shadow object
|
||||
cJSON* shadow = cJSON_AddObjectToObject(widget, "shadow");
|
||||
cJSON_AddBoolToObject(shadow, "enabled", w.shadow.enabled);
|
||||
cJSON_AddNumberToObject(shadow, "x", w.shadow.offsetX);
|
||||
@ -895,7 +597,6 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
|
||||
cJSON_AddItemToArray(screens, screenJson);
|
||||
}
|
||||
|
||||
// Print to buffer
|
||||
char* jsonStr = cJSON_PrintUnformatted(root);
|
||||
if (jsonStr) {
|
||||
strncpy(buf, jsonStr, bufSize - 1);
|
||||
@ -908,12 +609,6 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
// Helper function to parse hex color string
|
||||
static uint32_t parseHexColor(const char* colorStr) {
|
||||
if (!colorStr || colorStr[0] != '#') return 0;
|
||||
return strtoul(colorStr + 1, nullptr, 16);
|
||||
}
|
||||
|
||||
bool WidgetManager::updateConfigFromJson(const char* json) {
|
||||
cJSON* root = cJSON_Parse(json);
|
||||
if (!root) {
|
||||
@ -943,7 +638,6 @@ bool WidgetManager::updateConfigFromJson(const char* json) {
|
||||
w.action = ButtonAction::KNX;
|
||||
w.targetScreen = 0;
|
||||
|
||||
// Parse basic properties
|
||||
cJSON* id = cJSON_GetObjectItem(widget, "id");
|
||||
if (cJSON_IsNumber(id)) w.id = id->valueint;
|
||||
|
||||
@ -980,7 +674,6 @@ bool WidgetManager::updateConfigFromJson(const char* json) {
|
||||
cJSON* fontSize = cJSON_GetObjectItem(widget, "fontSize");
|
||||
if (cJSON_IsNumber(fontSize)) w.fontSize = fontSize->valueint;
|
||||
|
||||
// Parse colors
|
||||
cJSON* textColor = cJSON_GetObjectItem(widget, "textColor");
|
||||
if (cJSON_IsString(textColor)) {
|
||||
w.textColor = Color::fromHex(parseHexColor(textColor->valuestring));
|
||||
@ -997,7 +690,6 @@ bool WidgetManager::updateConfigFromJson(const char* json) {
|
||||
cJSON* radius = cJSON_GetObjectItem(widget, "radius");
|
||||
if (cJSON_IsNumber(radius)) w.borderRadius = radius->valueint;
|
||||
|
||||
// Parse shadow
|
||||
cJSON* shadow = cJSON_GetObjectItem(widget, "shadow");
|
||||
if (cJSON_IsObject(shadow)) {
|
||||
cJSON* enabled = cJSON_GetObjectItem(shadow, "enabled");
|
||||
@ -1075,6 +767,28 @@ bool WidgetManager::updateConfigFromJson(const char* json) {
|
||||
screen.backgroundColor = Color::fromHex(parseHexColor(bgColor->valuestring));
|
||||
}
|
||||
|
||||
// Parse modal-specific properties
|
||||
cJSON* modal = cJSON_GetObjectItem(screenJson, "modal");
|
||||
if (cJSON_IsObject(modal)) {
|
||||
cJSON* mx = cJSON_GetObjectItem(modal, "x");
|
||||
if (cJSON_IsNumber(mx)) screen.modalX = mx->valueint;
|
||||
|
||||
cJSON* my = cJSON_GetObjectItem(modal, "y");
|
||||
if (cJSON_IsNumber(my)) screen.modalY = my->valueint;
|
||||
|
||||
cJSON* mw = cJSON_GetObjectItem(modal, "w");
|
||||
if (cJSON_IsNumber(mw)) screen.modalWidth = mw->valueint;
|
||||
|
||||
cJSON* mh = cJSON_GetObjectItem(modal, "h");
|
||||
if (cJSON_IsNumber(mh)) screen.modalHeight = mh->valueint;
|
||||
|
||||
cJSON* mr = cJSON_GetObjectItem(modal, "radius");
|
||||
if (cJSON_IsNumber(mr)) screen.modalBorderRadius = mr->valueint;
|
||||
|
||||
cJSON* dim = cJSON_GetObjectItem(modal, "dim");
|
||||
if (cJSON_IsBool(dim)) screen.modalDimBackground = cJSON_IsTrue(dim);
|
||||
}
|
||||
|
||||
cJSON* widgets = cJSON_GetObjectItem(screenJson, "widgets");
|
||||
if (!parseWidgets(widgets, screen)) {
|
||||
screen.widgetCount = 0;
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "WidgetConfig.hpp"
|
||||
#include "widgets/Widget.hpp"
|
||||
#include "lvgl.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
class WidgetManager {
|
||||
public:
|
||||
@ -56,10 +58,6 @@ private:
|
||||
void saveToSdCard();
|
||||
void destroyAllWidgets();
|
||||
void createAllWidgets(const ScreenConfig& screen, lv_obj_t* parent);
|
||||
lv_obj_t* createWidget(const WidgetConfig& cfg, lv_obj_t* parent);
|
||||
void applyStyle(lv_obj_t* obj, const WidgetConfig& cfg);
|
||||
void applyLedStyle(lv_obj_t* obj, const WidgetConfig& cfg);
|
||||
const lv_font_t* getFontBySize(uint8_t sizeIndex);
|
||||
|
||||
void createDefaultConfig();
|
||||
void applyScreen(uint8_t screenId);
|
||||
@ -85,8 +83,9 @@ private:
|
||||
uint8_t navTargetScreen_ = 0xFF;
|
||||
int64_t lastActivityUs_ = 0;
|
||||
|
||||
// Runtime widget references (indexed by widget ID)
|
||||
std::array<lv_obj_t*, MAX_WIDGETS> widgetObjects_;
|
||||
// Runtime widget instances (indexed by widget ID)
|
||||
std::array<std::unique_ptr<Widget>, MAX_WIDGETS> widgets_;
|
||||
lv_obj_t* screen_ = nullptr;
|
||||
lv_obj_t* modalContainer_ = nullptr;
|
||||
lv_obj_t* modalDimmer_ = nullptr;
|
||||
};
|
||||
|
||||
56
main/widgets/ButtonWidget.cpp
Normal file
56
main/widgets/ButtonWidget.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
#include "ButtonWidget.hpp"
|
||||
#include "../WidgetManager.hpp"
|
||||
|
||||
ButtonWidget::ButtonWidget(const WidgetConfig& config)
|
||||
: Widget(config)
|
||||
, label_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void ButtonWidget::clickCallback(lv_event_t* e) {
|
||||
ButtonWidget* widget = static_cast<ButtonWidget*>(lv_event_get_user_data(e));
|
||||
if (!widget) return;
|
||||
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) {
|
||||
obj_ = lv_btn_create(parent);
|
||||
|
||||
if (config_.isToggle) {
|
||||
lv_obj_add_flag(obj_, LV_OBJ_FLAG_CHECKABLE);
|
||||
}
|
||||
|
||||
lv_obj_add_event_cb(obj_, clickCallback, LV_EVENT_CLICKED, this);
|
||||
|
||||
// Create label inside button
|
||||
label_ = lv_label_create(obj_);
|
||||
lv_label_set_text(label_, config_.text);
|
||||
lv_obj_center(label_);
|
||||
|
||||
lv_obj_set_pos(obj_, config_.x, config_.y);
|
||||
if (config_.width > 0 && config_.height > 0) {
|
||||
lv_obj_set_size(obj_, config_.width, config_.height);
|
||||
}
|
||||
|
||||
return obj_;
|
||||
}
|
||||
|
||||
void ButtonWidget::applyStyle() {
|
||||
if (obj_ == nullptr) return;
|
||||
|
||||
// Apply common style to button
|
||||
applyCommonStyle();
|
||||
|
||||
// Apply text style to label
|
||||
if (label_ != nullptr) {
|
||||
lv_obj_set_style_text_color(label_, lv_color_make(
|
||||
config_.textColor.r, config_.textColor.g, config_.textColor.b), 0);
|
||||
lv_obj_set_style_text_font(label_, getFontBySize(config_.fontSize), 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool ButtonWidget::isChecked() const {
|
||||
if (obj_ == nullptr) return false;
|
||||
return (lv_obj_get_state(obj_) & LV_STATE_CHECKED) != 0;
|
||||
}
|
||||
19
main/widgets/ButtonWidget.hpp
Normal file
19
main/widgets/ButtonWidget.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "Widget.hpp"
|
||||
|
||||
class ButtonWidget : public Widget {
|
||||
public:
|
||||
explicit ButtonWidget(const WidgetConfig& config);
|
||||
|
||||
lv_obj_t* create(lv_obj_t* parent) override;
|
||||
void applyStyle() override;
|
||||
|
||||
// Check if button is in checked state
|
||||
bool isChecked() const;
|
||||
|
||||
private:
|
||||
lv_obj_t* label_ = nullptr;
|
||||
|
||||
static void clickCallback(lv_event_t* e);
|
||||
};
|
||||
42
main/widgets/LabelWidget.cpp
Normal file
42
main/widgets/LabelWidget.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "LabelWidget.hpp"
|
||||
#include <cstdio>
|
||||
|
||||
LabelWidget::LabelWidget(const WidgetConfig& config)
|
||||
: Widget(config)
|
||||
{
|
||||
}
|
||||
|
||||
lv_obj_t* LabelWidget::create(lv_obj_t* parent) {
|
||||
obj_ = lv_label_create(parent);
|
||||
lv_label_set_text(obj_, config_.text);
|
||||
|
||||
lv_obj_set_pos(obj_, config_.x, config_.y);
|
||||
if (config_.width > 0 && config_.height > 0) {
|
||||
lv_obj_set_size(obj_, config_.width, config_.height);
|
||||
}
|
||||
|
||||
return obj_;
|
||||
}
|
||||
|
||||
void LabelWidget::onKnxValue(float value) {
|
||||
if (obj_ == nullptr) return;
|
||||
if (config_.textSource != TextSource::KNX_DPT_TEMP) return;
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), config_.text, value);
|
||||
lv_label_set_text(obj_, buf);
|
||||
}
|
||||
|
||||
void LabelWidget::onKnxSwitch(bool value) {
|
||||
if (obj_ == nullptr) return;
|
||||
if (config_.textSource != TextSource::KNX_DPT_SWITCH) return;
|
||||
|
||||
lv_label_set_text(obj_, value ? "EIN" : "AUS");
|
||||
}
|
||||
|
||||
void LabelWidget::onKnxText(const char* text) {
|
||||
if (obj_ == nullptr) return;
|
||||
if (config_.textSource != TextSource::KNX_DPT_TEXT) return;
|
||||
|
||||
lv_label_set_text(obj_, text);
|
||||
}
|
||||
15
main/widgets/LabelWidget.hpp
Normal file
15
main/widgets/LabelWidget.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "Widget.hpp"
|
||||
|
||||
class LabelWidget : public Widget {
|
||||
public:
|
||||
explicit LabelWidget(const WidgetConfig& config);
|
||||
|
||||
lv_obj_t* create(lv_obj_t* parent) override;
|
||||
|
||||
// KNX updates
|
||||
void onKnxValue(float value) override;
|
||||
void onKnxSwitch(bool value) override;
|
||||
void onKnxText(const char* text) override;
|
||||
};
|
||||
38
main/widgets/LedWidget.cpp
Normal file
38
main/widgets/LedWidget.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "LedWidget.hpp"
|
||||
|
||||
LedWidget::LedWidget(const WidgetConfig& config)
|
||||
: Widget(config)
|
||||
{
|
||||
}
|
||||
|
||||
lv_obj_t* LedWidget::create(lv_obj_t* parent) {
|
||||
obj_ = lv_led_create(parent);
|
||||
|
||||
lv_obj_set_pos(obj_, config_.x, config_.y);
|
||||
if (config_.width > 0 && config_.height > 0) {
|
||||
lv_obj_set_size(obj_, config_.width, config_.height);
|
||||
}
|
||||
|
||||
return obj_;
|
||||
}
|
||||
|
||||
void LedWidget::applyStyle() {
|
||||
if (obj_ == nullptr) return;
|
||||
|
||||
// LED-specific styling
|
||||
lv_obj_set_style_radius(obj_, LV_RADIUS_CIRCLE, 0);
|
||||
lv_led_set_color(obj_, lv_color_make(
|
||||
config_.bgColor.r, config_.bgColor.g, config_.bgColor.b));
|
||||
lv_led_set_brightness(obj_, config_.bgOpacity);
|
||||
|
||||
// Shadow
|
||||
applyShadowStyle();
|
||||
}
|
||||
|
||||
void LedWidget::onKnxSwitch(bool value) {
|
||||
if (obj_ == nullptr) return;
|
||||
if (config_.textSource != TextSource::KNX_DPT_SWITCH) return;
|
||||
|
||||
uint8_t brightness = value ? (config_.bgOpacity > 0 ? config_.bgOpacity : 255) : 0;
|
||||
lv_led_set_brightness(obj_, brightness);
|
||||
}
|
||||
14
main/widgets/LedWidget.hpp
Normal file
14
main/widgets/LedWidget.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "Widget.hpp"
|
||||
|
||||
class LedWidget : public Widget {
|
||||
public:
|
||||
explicit LedWidget(const WidgetConfig& config);
|
||||
|
||||
lv_obj_t* create(lv_obj_t* parent) override;
|
||||
void applyStyle() override;
|
||||
|
||||
// KNX update (LED responds to switch)
|
||||
void onKnxSwitch(bool value) override;
|
||||
};
|
||||
96
main/widgets/Widget.cpp
Normal file
96
main/widgets/Widget.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "Widget.hpp"
|
||||
|
||||
Widget::Widget(const WidgetConfig& config)
|
||||
: config_(config)
|
||||
, obj_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
Widget::~Widget() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
void Widget::destroy() {
|
||||
if (obj_ != nullptr) {
|
||||
lv_obj_delete(obj_);
|
||||
obj_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::applyStyle() {
|
||||
if (obj_ == nullptr) return;
|
||||
applyCommonStyle();
|
||||
}
|
||||
|
||||
void Widget::onKnxValue(float /*value*/) {
|
||||
// Default: do nothing
|
||||
}
|
||||
|
||||
void Widget::onKnxSwitch(bool /*value*/) {
|
||||
// Default: do nothing
|
||||
}
|
||||
|
||||
void Widget::onKnxText(const char* /*text*/) {
|
||||
// Default: do nothing
|
||||
}
|
||||
|
||||
void Widget::applyCommonStyle() {
|
||||
if (obj_ == nullptr) return;
|
||||
|
||||
// Text color
|
||||
lv_obj_set_style_text_color(obj_, lv_color_make(
|
||||
config_.textColor.r, config_.textColor.g, config_.textColor.b), 0);
|
||||
|
||||
// Font
|
||||
lv_obj_set_style_text_font(obj_, getFontBySize(config_.fontSize), 0);
|
||||
|
||||
// Background (for buttons and labels with bg)
|
||||
if (config_.bgOpacity > 0) {
|
||||
lv_obj_set_style_bg_color(obj_, lv_color_make(
|
||||
config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0);
|
||||
lv_obj_set_style_bg_opa(obj_, config_.bgOpacity, 0);
|
||||
}
|
||||
|
||||
// Border radius
|
||||
if (config_.borderRadius > 0) {
|
||||
lv_obj_set_style_radius(obj_, config_.borderRadius, 0);
|
||||
}
|
||||
|
||||
// Shadow
|
||||
applyShadowStyle();
|
||||
}
|
||||
|
||||
void Widget::applyShadowStyle() {
|
||||
if (obj_ == nullptr || !config_.shadow.enabled) return;
|
||||
|
||||
lv_obj_set_style_shadow_color(obj_, lv_color_make(
|
||||
config_.shadow.color.r, config_.shadow.color.g, config_.shadow.color.b), 0);
|
||||
lv_obj_set_style_shadow_opa(obj_, 180, 0);
|
||||
lv_obj_set_style_shadow_width(obj_, config_.shadow.blur, 0);
|
||||
lv_obj_set_style_shadow_spread(obj_, config_.shadow.spread, 0);
|
||||
lv_obj_set_style_shadow_offset_x(obj_, config_.shadow.offsetX, 0);
|
||||
lv_obj_set_style_shadow_offset_y(obj_, config_.shadow.offsetY, 0);
|
||||
}
|
||||
|
||||
const lv_font_t* Widget::getFontBySize(uint8_t sizeIndex) {
|
||||
// Font sizes: 0=14, 1=18, 2=22, 3=28, 4=36, 5=48
|
||||
switch (sizeIndex) {
|
||||
case 0: return &lv_font_montserrat_14;
|
||||
#if LV_FONT_MONTSERRAT_18
|
||||
case 1: return &lv_font_montserrat_18;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_22
|
||||
case 2: return &lv_font_montserrat_22;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_28
|
||||
case 3: return &lv_font_montserrat_28;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_36
|
||||
case 4: return &lv_font_montserrat_36;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_48
|
||||
case 5: return &lv_font_montserrat_48;
|
||||
#endif
|
||||
default: return &lv_font_montserrat_14;
|
||||
}
|
||||
}
|
||||
55
main/widgets/Widget.hpp
Normal file
55
main/widgets/Widget.hpp
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "../WidgetConfig.hpp"
|
||||
#include "lvgl.h"
|
||||
|
||||
class Widget {
|
||||
public:
|
||||
explicit Widget(const WidgetConfig& config);
|
||||
virtual ~Widget();
|
||||
|
||||
// Delete copy/move
|
||||
Widget(const Widget&) = delete;
|
||||
Widget& operator=(const Widget&) = delete;
|
||||
|
||||
// Destroy LVGL object
|
||||
void destroy();
|
||||
|
||||
// Access to LVGL object
|
||||
lv_obj_t* getLvglObject() const { return obj_; }
|
||||
|
||||
// Widget ID
|
||||
uint8_t getId() const { return config_.id; }
|
||||
|
||||
// KNX group address for read binding
|
||||
uint16_t getKnxAddress() const { return config_.knxAddress; }
|
||||
|
||||
// TextSource for KNX callback filtering
|
||||
TextSource getTextSource() const { return config_.textSource; }
|
||||
|
||||
// Widget type
|
||||
WidgetType getType() const { return config_.type; }
|
||||
|
||||
// Config access (for button action handling)
|
||||
const WidgetConfig& getConfig() const { return config_; }
|
||||
|
||||
// Create LVGL widget on parent - must be implemented by subclasses
|
||||
virtual lv_obj_t* create(lv_obj_t* parent) = 0;
|
||||
|
||||
// Apply styling after create() - can be overridden
|
||||
virtual void applyStyle();
|
||||
|
||||
// KNX callbacks - default implementations do nothing
|
||||
virtual void onKnxValue(float value);
|
||||
virtual void onKnxSwitch(bool value);
|
||||
virtual void onKnxText(const char* text);
|
||||
|
||||
protected:
|
||||
// Common style helper functions
|
||||
void applyCommonStyle();
|
||||
void applyShadowStyle();
|
||||
static const lv_font_t* getFontBySize(uint8_t sizeIndex);
|
||||
|
||||
const WidgetConfig& config_;
|
||||
lv_obj_t* obj_ = nullptr;
|
||||
};
|
||||
19
main/widgets/WidgetFactory.cpp
Normal file
19
main/widgets/WidgetFactory.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "WidgetFactory.hpp"
|
||||
#include "LabelWidget.hpp"
|
||||
#include "ButtonWidget.hpp"
|
||||
#include "LedWidget.hpp"
|
||||
|
||||
std::unique_ptr<Widget> WidgetFactory::create(const WidgetConfig& config) {
|
||||
if (!config.visible) return nullptr;
|
||||
|
||||
switch (config.type) {
|
||||
case WidgetType::LABEL:
|
||||
return std::make_unique<LabelWidget>(config);
|
||||
case WidgetType::BUTTON:
|
||||
return std::make_unique<ButtonWidget>(config);
|
||||
case WidgetType::LED:
|
||||
return std::make_unique<LedWidget>(config);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
11
main/widgets/WidgetFactory.hpp
Normal file
11
main/widgets/WidgetFactory.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "Widget.hpp"
|
||||
#include "../WidgetConfig.hpp"
|
||||
#include <memory>
|
||||
|
||||
class WidgetFactory {
|
||||
public:
|
||||
// Create widget based on WidgetType
|
||||
static std::unique_ptr<Widget> create(const WidgetConfig& config);
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user