From 752b944b5cec637a28f5c6dde76d87438fc4e2d4 Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Mon, 9 Feb 2026 10:31:41 +0100 Subject: [PATCH] Fixes --- main/WidgetManager.cpp | 7 + main/widgets/ButtonWidget.cpp | 128 ++++++++++++++++++ main/widgets/ButtonWidget.hpp | 3 + .../widgets/elements/ButtonElement.vue | 4 +- .../widgets/settings/ButtonSettings.vue | 86 +++++++++++- web-interface/src/constants.js | 3 +- web-interface/src/stores/editor.js | 4 + 7 files changed, 227 insertions(+), 8 deletions(-) diff --git a/main/WidgetManager.cpp b/main/WidgetManager.cpp index 5381fa1..210fc23 100644 --- a/main/WidgetManager.cpp +++ b/main/WidgetManager.cpp @@ -1033,6 +1033,13 @@ void WidgetManager::applyCachedValuesToWidgets() { } } + if (widget->getType() == WidgetType::BUTTON) { + bool state = false; + if (addr != 0 && getCachedKnxSwitch(addr, &state)) { + widget->onKnxSwitch(state); + } + } + // Secondary address (left value) uint16_t addr2 = widget->getKnxAddress2(); TextSource source2 = widget->getTextSource2(); diff --git a/main/widgets/ButtonWidget.cpp b/main/widgets/ButtonWidget.cpp index 9519937..2224529 100644 --- a/main/widgets/ButtonWidget.cpp +++ b/main/widgets/ButtonWidget.cpp @@ -240,6 +240,127 @@ void ButtonWidget::applyStyle() { } } +void ButtonWidget::onKnxSwitch(bool value) { + cachedPrimaryValue_ = value ? 1.0f : 0.0f; + hasCachedPrimary_ = true; + + if (obj_ && config_.isToggle) { + if (value) { + lv_obj_add_state(obj_, LV_STATE_CHECKED); + } else { + lv_obj_clear_state(obj_, LV_STATE_CHECKED); + } + } + + evaluateConditions(cachedPrimaryValue_, cachedSecondaryValue_, cachedTertiaryValue_); +} + +bool ButtonWidget::evaluateConditions(float primaryValue, float secondaryValue, float tertiaryValue) { + if (config_.conditionCount == 0) return false; + + const StyleCondition* bestMatch = nullptr; + uint8_t bestPriority = 255; + + for (uint8_t i = 0; i < config_.conditionCount && i < MAX_CONDITIONS; ++i) { + const StyleCondition& cond = config_.conditions[i]; + if (!cond.enabled) continue; + + float checkValue = 0.0f; + bool hasValue = false; + switch (cond.source) { + case ConditionSource::PRIMARY: + checkValue = primaryValue; + hasValue = hasCachedPrimary_; + break; + case ConditionSource::SECONDARY: + checkValue = secondaryValue; + hasValue = hasCachedSecondary_; + break; + case ConditionSource::TERTIARY: + checkValue = tertiaryValue; + hasValue = hasCachedTertiary_; + break; + } + + if (!hasValue) continue; + + bool matches = false; + switch (cond.op) { + case ConditionOp::LESS: + matches = checkValue < cond.threshold; + break; + case ConditionOp::LESS_EQUAL: + matches = checkValue <= cond.threshold; + break; + case ConditionOp::EQUAL: + matches = checkValue == cond.threshold; + break; + case ConditionOp::GREATER_EQUAL: + matches = checkValue >= cond.threshold; + break; + case ConditionOp::GREATER: + matches = checkValue > cond.threshold; + break; + case ConditionOp::NOT_EQUAL: + matches = checkValue != cond.threshold; + break; + } + + if (matches && cond.priority < bestPriority) { + bestMatch = &cond; + bestPriority = cond.priority; + } + } + + if (!bestMatch) { + return false; + } + + if (bestMatch->style.iconCodepoint != 0 && + bestMatch->style.iconCodepoint != currentConditionIcon_) { + updateIcon(bestMatch->style.iconCodepoint); + currentConditionIcon_ = bestMatch->style.iconCodepoint; + } + + if (bestMatch->style.flags & ConditionStyle::FLAG_USE_TEXT_COLOR) { + lv_color_t color = lv_color_make( + bestMatch->style.textColor.r, + bestMatch->style.textColor.g, + bestMatch->style.textColor.b); + if (label_) { + lv_obj_set_style_text_color(label_, color, 0); + } + if (iconLabel_) { + lv_obj_set_style_text_color(iconLabel_, color, 0); + } + } + + if (bestMatch->style.flags & ConditionStyle::FLAG_USE_BG_COLOR) { + lv_obj_set_style_bg_color(obj_, lv_color_make( + bestMatch->style.bgColor.r, + bestMatch->style.bgColor.g, + bestMatch->style.bgColor.b), 0); + } + + if (bestMatch->style.flags & ConditionStyle::FLAG_USE_BG_OPACITY) { + lv_obj_set_style_bg_opa(obj_, bestMatch->style.bgOpacity, 0); + } + + if (bestMatch->style.flags & ConditionStyle::FLAG_HIDE) { + lv_obj_add_flag(obj_, LV_OBJ_FLAG_HIDDEN); + if (shadowObj_ && lv_obj_is_valid(shadowObj_)) { + lv_obj_add_flag(shadowObj_, LV_OBJ_FLAG_HIDDEN); + } + } else { + lv_obj_clear_flag(obj_, LV_OBJ_FLAG_HIDDEN); + if (shadowObj_ && lv_obj_is_valid(shadowObj_)) { + lv_obj_clear_flag(shadowObj_, LV_OBJ_FLAG_HIDDEN); + } + } + + return true; +} + bool ButtonWidget::isChecked() const { if (obj_ == nullptr) return false; return (lv_obj_get_state(obj_) & LV_STATE_CHECKED) != 0; @@ -288,3 +409,10 @@ void ButtonWidget::applyFakeShadowStyle() { lv_obj_set_style_radius(shadowObj_, config_.borderRadius + config_.shadow.spread, 0); } } + +void ButtonWidget::updateIcon(uint32_t codepoint) { + if (!iconLabel_ || codepoint == 0) return; + char iconText[5]; + encodeUtf8(codepoint, iconText); + lv_label_set_text(iconLabel_, iconText); +} diff --git a/main/widgets/ButtonWidget.hpp b/main/widgets/ButtonWidget.hpp index 1eab728..987fe7e 100644 --- a/main/widgets/ButtonWidget.hpp +++ b/main/widgets/ButtonWidget.hpp @@ -9,6 +9,8 @@ public: lv_obj_t* create(lv_obj_t* parent) override; void applyStyle() override; + void onKnxSwitch(bool value) override; + bool evaluateConditions(float primaryValue, float secondaryValue, float tertiaryValue) override; // Check if button is in checked state bool isChecked() const; @@ -23,6 +25,7 @@ private: void applyTextAlignment(); void createFakeShadow(lv_obj_t* parent); void applyFakeShadowStyle(); + void updateIcon(uint32_t codepoint); static int encodeUtf8(uint32_t codepoint, char* buf); static void clickCallback(lv_event_t* e); }; diff --git a/web-interface/src/components/widgets/elements/ButtonElement.vue b/web-interface/src/components/widgets/elements/ButtonElement.vue index 3fc4d90..f9cff00 100644 --- a/web-interface/src/components/widgets/elements/ButtonElement.vue +++ b/web-interface/src/components/widgets/elements/ButtonElement.vue @@ -57,7 +57,7 @@