#include "LabelWidget.hpp" #include "../Fonts.hpp" #include #include LabelWidget::LabelWidget(const WidgetConfig& config) : Widget(config) , container_(nullptr) , textLabel_(nullptr) , iconLabel_(nullptr) { } static lv_text_align_t toLvTextAlign(uint8_t align) { if (align == static_cast(TextAlign::LEFT)) return LV_TEXT_ALIGN_LEFT; if (align == static_cast(TextAlign::RIGHT)) return LV_TEXT_ALIGN_RIGHT; return LV_TEXT_ALIGN_CENTER; } static lv_flex_align_t toFlexAlign(uint8_t align) { if (align == static_cast(TextAlign::LEFT)) return LV_FLEX_ALIGN_START; if (align == static_cast(TextAlign::RIGHT)) return LV_FLEX_ALIGN_END; return LV_FLEX_ALIGN_CENTER; } static void set_label_text_if_changed(lv_obj_t* label, const char* text) { if (!label || !text) return; const char* current = lv_label_get_text(label); if (current && strcmp(current, text) == 0) return; lv_label_set_text(label, text); } int LabelWidget::encodeUtf8(uint32_t codepoint, char* buf) { if (codepoint < 0x80) { buf[0] = static_cast(codepoint); buf[1] = '\0'; return 1; } else if (codepoint < 0x800) { buf[0] = static_cast(0xC0 | (codepoint >> 6)); buf[1] = static_cast(0x80 | (codepoint & 0x3F)); buf[2] = '\0'; return 2; } else if (codepoint < 0x10000) { buf[0] = static_cast(0xE0 | (codepoint >> 12)); buf[1] = static_cast(0x80 | ((codepoint >> 6) & 0x3F)); buf[2] = static_cast(0x80 | (codepoint & 0x3F)); buf[3] = '\0'; return 3; } else if (codepoint < 0x110000) { buf[0] = static_cast(0xF0 | (codepoint >> 18)); buf[1] = static_cast(0x80 | ((codepoint >> 12) & 0x3F)); buf[2] = static_cast(0x80 | ((codepoint >> 6) & 0x3F)); buf[3] = static_cast(0x80 | (codepoint & 0x3F)); buf[4] = '\0'; return 4; } buf[0] = '\0'; return 0; } void LabelWidget::setupFlexLayout() { if (container_ == nullptr) return; // Determine flex direction based on icon position bool isVertical = (config_.iconPosition == static_cast(IconPosition::TOP) || config_.iconPosition == static_cast(IconPosition::BOTTOM)); lv_obj_set_flex_flow(container_, isVertical ? LV_FLEX_FLOW_COLUMN : LV_FLEX_FLOW_ROW); lv_flex_align_t mainAlign = LV_FLEX_ALIGN_CENTER; lv_flex_align_t crossAlign = LV_FLEX_ALIGN_CENTER; lv_flex_align_t contentAlign = toFlexAlign(config_.textAlign); if (contentAlign != LV_FLEX_ALIGN_CENTER) { if (isVertical) { crossAlign = contentAlign; } else { mainAlign = contentAlign; } } lv_obj_set_flex_align(container_, mainAlign, crossAlign, LV_FLEX_ALIGN_CENTER); // Set gap between icon and text int gap = config_.iconGap > 0 ? config_.iconGap : 8; lv_obj_set_style_pad_gap(container_, gap, 0); } lv_obj_t* LabelWidget::create(lv_obj_t* parent) { bool hasIcon = config_.iconCodepoint > 0 && Fonts::hasIconFont(); bool needsContainer = hasIcon || config_.bgOpacity > 0 || config_.borderRadius > 0; if (needsContainer) { // Create container object for background/radius/flex layout obj_ = lv_obj_create(parent); if (obj_ == nullptr) { return nullptr; } lv_obj_remove_style_all(obj_); 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); } else { lv_obj_set_size(obj_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); } container_ = obj_; if (hasIcon) { // Create elements in correct order based on icon position bool iconFirst = (config_.iconPosition == static_cast(IconPosition::LEFT) || config_.iconPosition == static_cast(IconPosition::TOP)); if (iconFirst) { iconLabel_ = lv_label_create(container_); char iconText[5]; encodeUtf8(config_.iconCodepoint, iconText); lv_label_set_text(iconLabel_, iconText); lv_obj_clear_flag(iconLabel_, LV_OBJ_FLAG_CLICKABLE); } textLabel_ = lv_label_create(container_); lv_label_set_text(textLabel_, config_.text); lv_obj_clear_flag(textLabel_, LV_OBJ_FLAG_CLICKABLE); if (!iconFirst) { iconLabel_ = lv_label_create(container_); char iconText[5]; encodeUtf8(config_.iconCodepoint, iconText); lv_label_set_text(iconLabel_, iconText); lv_obj_clear_flag(iconLabel_, LV_OBJ_FLAG_CLICKABLE); } setupFlexLayout(); } else { // Container with just text label (for bg/radius) textLabel_ = lv_label_create(container_); lv_label_set_text(textLabel_, config_.text); lv_obj_center(textLabel_); lv_obj_clear_flag(textLabel_, LV_OBJ_FLAG_CLICKABLE); } } else { // Simple label without container obj_ = lv_label_create(parent); textLabel_ = obj_; 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); } } if (obj_ != nullptr) { lv_obj_clear_flag(obj_, LV_OBJ_FLAG_CLICKABLE); } return obj_; } void LabelWidget::applyStyle() { if (obj_ == nullptr) return; // Apply background and border radius if container exists if (container_ != nullptr) { 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); } if (config_.borderRadius > 0) { lv_obj_set_style_radius(obj_, config_.borderRadius, 0); } } // Apply text style if (textLabel_ != nullptr) { lv_obj_set_style_text_color(textLabel_, lv_color_make( config_.textColor.r, config_.textColor.g, config_.textColor.b), 0); lv_obj_set_style_text_font(textLabel_, Fonts::bySizeIndex(config_.fontSize), 0); lv_obj_set_style_text_align(textLabel_, toLvTextAlign(config_.textAlign), 0); } // Apply icon style if (iconLabel_ != nullptr) { lv_obj_set_style_text_color(iconLabel_, lv_color_make( config_.textColor.r, config_.textColor.g, config_.textColor.b), 0); uint8_t sizeIdx = config_.iconSize < 6 ? config_.iconSize : config_.fontSize; lv_obj_set_style_text_font(iconLabel_, Fonts::iconFont(sizeIdx), 0); } // For simple label (no container), apply common style if (container_ == nullptr) { applyCommonStyle(); } } void LabelWidget::onKnxValue(float value) { lv_obj_t* label = textLabel_ ? textLabel_ : obj_; if (label == nullptr) return; if (config_.textSource != TextSource::KNX_DPT_TEMP && config_.textSource != TextSource::KNX_DPT_PERCENT && config_.textSource != TextSource::KNX_DPT_POWER && config_.textSource != TextSource::KNX_DPT_ENERGY && config_.textSource != TextSource::KNX_DPT_DECIMALFACTOR) { return; } char buf[32]; if (config_.textSource == TextSource::KNX_DPT_PERCENT || config_.textSource == TextSource::KNX_DPT_DECIMALFACTOR) { int intValue = static_cast(value + 0.5f); snprintf(buf, sizeof(buf), config_.text, intValue); } else { snprintf(buf, sizeof(buf), config_.text, value); } set_label_text_if_changed(label, buf); } void LabelWidget::onKnxSwitch(bool value) { lv_obj_t* label = textLabel_ ? textLabel_ : obj_; if (label == nullptr) return; if (config_.textSource != TextSource::KNX_DPT_SWITCH) return; set_label_text_if_changed(label, value ? "EIN" : "AUS"); } void LabelWidget::onKnxText(const char* text) { lv_obj_t* label = textLabel_ ? textLabel_ : obj_; if (label == nullptr) return; if (config_.textSource != TextSource::KNX_DPT_TEXT) return; set_label_text_if_changed(label, text); } void LabelWidget::onKnxTime(const struct tm& value, TextSource source) { lv_obj_t* label = textLabel_ ? textLabel_ : obj_; if (label == nullptr) return; if (config_.textSource != source) return; if (source != TextSource::KNX_DPT_TIME && source != TextSource::KNX_DPT_DATE && source != TextSource::KNX_DPT_DATETIME) { return; } int year = value.tm_year; if (year > 0 && year < 1900) { year += 1900; } int month = value.tm_mon; if (month < 1 || month > 12) { if (month >= 0 && month <= 11) { month += 1; } } const char* fmt = config_.text; if (!fmt || fmt[0] == '\0' || strchr(fmt, '%') == nullptr) { if (source == TextSource::KNX_DPT_TIME) { fmt = "%02d:%02d:%02d"; } else if (source == TextSource::KNX_DPT_DATE) { fmt = "%02d.%02d.%04d"; } else { fmt = "%02d.%02d.%04d %02d:%02d:%02d"; } } char buf[32]; if (source == TextSource::KNX_DPT_TIME) { snprintf(buf, sizeof(buf), fmt, value.tm_hour, value.tm_min, value.tm_sec); } else if (source == TextSource::KNX_DPT_DATE) { snprintf(buf, sizeof(buf), fmt, value.tm_mday, month, year); } else { snprintf(buf, sizeof(buf), fmt, value.tm_mday, month, year, value.tm_hour, value.tm_min, value.tm_sec); } set_label_text_if_changed(label, buf); }