knxdisplay/main/widgets/Widget.cpp
2026-02-01 20:49:09 +01:00

222 lines
6.4 KiB
C++

#include "Widget.hpp"
#include "../Fonts.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;
}
}
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 || 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;
lv_color_t shadowColor = lv_color_make(
config_.shadow.color.r, config_.shadow.color.g, config_.shadow.color.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);
}