#include "Widget.hpp" #include "../Fonts.hpp" #include "../WidgetManager.hpp" #include "lvgl.h" Widget::Widget(const WidgetConfig& config) : config_(config) , obj_(nullptr) { } Widget::~Widget() { destroy(); } void Widget::destroy() { if (obj_ != nullptr) { if (lv_obj_is_valid(obj_)) { lv_obj_delete(obj_); } obj_ = nullptr; } } void Widget::applyStyle() { if (obj_ == nullptr) return; applyCommonStyle(); } void Widget::onKnxValue(float value) { cachedPrimaryValue_ = value; hasCachedPrimary_ = true; evaluateConditions(cachedPrimaryValue_, cachedSecondaryValue_, cachedTertiaryValue_); } void Widget::onKnxValue2(float value) { cachedSecondaryValue_ = value; hasCachedSecondary_ = true; evaluateConditions(cachedPrimaryValue_, cachedSecondaryValue_, cachedTertiaryValue_); } void Widget::onKnxValue3(float value) { cachedTertiaryValue_ = value; hasCachedTertiary_ = true; evaluateConditions(cachedPrimaryValue_, cachedSecondaryValue_, cachedTertiaryValue_); } void Widget::onKnxSwitch(bool /*value*/) { // Default: do nothing } void Widget::onKnxText(const char* /*text*/) { // Default: do nothing } void Widget::onKnxTime(const struct tm& /*value*/, TextSource /*source*/) { // Default: do nothing } void Widget::onHistoryUpdate() { // Default: do nothing } bool Widget::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; // Get the value to check based on source 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; // Evaluate condition 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) { applyConditionStyle(bestMatch->style); return true; } return false; } void Widget::applyConditionStyle(const ConditionStyle& style) { if (obj_ == nullptr) return; // Handle hide flag if (style.flags & ConditionStyle::FLAG_HIDE) { lv_obj_add_flag(obj_, LV_OBJ_FLAG_HIDDEN); return; } else { lv_obj_clear_flag(obj_, LV_OBJ_FLAG_HIDDEN); } // Apply text color if flag set if (style.flags & ConditionStyle::FLAG_USE_TEXT_COLOR) { lv_obj_set_style_text_color(obj_, lv_color_make( style.textColor.r, style.textColor.g, style.textColor.b), 0); } // Apply background color if flag set if (style.flags & ConditionStyle::FLAG_USE_BG_COLOR) { lv_obj_set_style_bg_color(obj_, lv_color_make( style.bgColor.r, style.bgColor.g, style.bgColor.b), 0); } // Apply background opacity if flag set if (style.flags & ConditionStyle::FLAG_USE_BG_OPACITY) { lv_obj_set_style_bg_opa(obj_, style.bgOpacity, 0); } // Icon changes are handled by subclasses that have icons if (style.iconCodepoint != 0) { currentConditionIcon_ = style.iconCodepoint; } } bool Widget::shouldTransformColors() const { return WidgetManager::instance().isNightMode() && !config_.themeFixed; } Color Widget::themeColor(const Color& c) const { return shouldTransformColors() ? c.invertLightness() : c; } void Widget::applyCommonStyle() { if (obj_ == nullptr) return; Color textCol = themeColor(config_.textColor); Color bgCol = themeColor(config_.bgColor); Color borderCol = themeColor(config_.borderColor); // Text color lv_obj_set_style_text_color(obj_, lv_color_make(textCol.r, textCol.g, textCol.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(bgCol.r, bgCol.g, bgCol.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); } // Border if (config_.borderWidth > 0 && config_.borderOpacity > 0) { lv_obj_set_style_border_width(obj_, config_.borderWidth, 0); lv_obj_set_style_border_color(obj_, lv_color_make(borderCol.r, borderCol.g, borderCol.b), 0); lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0); } else { lv_obj_set_style_border_width(obj_, 0, 0); } // Shadow applyShadowStyle(); } void Widget::applyShadowStyle() { if (obj_ == nullptr || !config_.shadow.enabled || config_.type == WidgetType::BUTTON) return; // Limit shadow values to prevent memory issues on ESP32 constexpr int16_t MAX_SHADOW_BLUR = 15; constexpr int16_t MAX_SHADOW_SPREAD = 8; int16_t blur = config_.shadow.blur; int16_t spread = config_.shadow.spread; if (blur > MAX_SHADOW_BLUR) blur = MAX_SHADOW_BLUR; if (blur < 0) blur = 0; if (spread > MAX_SHADOW_SPREAD) spread = MAX_SHADOW_SPREAD; if (spread < 0) spread = 0; if (blur == 0) return; Color sc = themeColor(config_.shadow.color); lv_color_t shadowColor = lv_color_make(sc.r, sc.g, sc.b); // Default state shadow lv_obj_set_style_shadow_color(obj_, shadowColor, 0); lv_obj_set_style_shadow_opa(obj_, 180, 0); lv_obj_set_style_shadow_width(obj_, blur, 0); lv_obj_set_style_shadow_spread(obj_, 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) { return Fonts::bySizeIndex(sizeIndex); }