#include "RoomCardTileWidget.hpp" #include "../Fonts.hpp" #include #include RoomCardTileWidget::RoomCardTileWidget(const WidgetConfig& config) : RoomCardWidgetBase(config) { for (size_t i = 0; i < MAX_TEXTLINES; ++i) { textLineLabels_[i] = nullptr; } } void RoomCardTileWidget::clearLvglObject() { RoomCardWidgetBase::clearLvglObject(); decorIcon_ = nullptr; for (size_t i = 0; i < MAX_TEXTLINES; ++i) { textLineLabels_[i] = nullptr; } } lv_obj_t* RoomCardTileWidget::create(lv_obj_t* parent) { parseTextBase(); // Parses roomName_ from first line of text screenParent_ = parent; // Create main container obj_ = lv_obj_create(parent); lv_obj_remove_style_all(obj_); lv_obj_set_pos(obj_, config_.x, config_.y); lv_obj_set_size(obj_, config_.width, config_.height); lv_obj_clear_flag(obj_, LV_OBJ_FLAG_SCROLLABLE); lv_obj_add_flag(obj_, LV_OBJ_FLAG_CLICKABLE); // Add click handler for navigation lv_obj_add_event_cb(obj_, cardClickCallback, LV_EVENT_CLICKED, this); createTileLayout(); createTextLines(); createSubButtonsCommon(); return obj_; } void RoomCardTileWidget::createTileLayout() { // Tile style: rectangular card with rounded corners int16_t radius = config_.borderRadius; if (radius <= 0 || radius > 50) { radius = 16; } lv_obj_set_style_radius(obj_, radius, 0); lv_obj_set_style_bg_opa(obj_, config_.bgOpacity, 0); lv_obj_set_style_bg_color(obj_, lv_color_hex(config_.bgColor.toLvColor()), 0); lv_obj_set_style_clip_corner(obj_, true, 0); int16_t padding = 16; // Room name (top-left) roomLabel_ = lv_label_create(obj_); lv_obj_clear_flag(roomLabel_, LV_OBJ_FLAG_CLICKABLE); lv_label_set_text(roomLabel_, roomName_); lv_obj_set_pos(roomLabel_, padding, padding); // Large decorative icon (bottom-left, partially visible) if (config_.iconCodepoint > 0 && Fonts::hasIconFont()) { decorIcon_ = lv_label_create(obj_); lv_obj_clear_flag(decorIcon_, LV_OBJ_FLAG_CLICKABLE); char iconText[5]; encodeUtf8(config_.iconCodepoint, iconText); lv_label_set_text(decorIcon_, iconText); int16_t iconX = (config_.iconPositionX != 0) ? config_.iconPositionX : -20; int16_t iconY = (config_.iconPositionY != 0) ? config_.iconPositionY : config_.height - 120; lv_obj_set_pos(decorIcon_, iconX, iconY); } } void RoomCardTileWidget::createTextLines() { if (config_.textLineCount == 0) return; int16_t padding = 16; int16_t currentY = 48; // Below room name for (uint8_t i = 0; i < config_.textLineCount && i < MAX_TEXTLINES; ++i) { const TextLineConfig& tl = config_.textLines[i]; if (!tl.enabled) continue; // Get font for this line uint8_t fontIdx = tl.fontSize; if (fontIdx > 5) fontIdx = 1; // Default to 18px const lv_font_t* lineFont = Fonts::bySizeIndex(fontIdx); const lv_font_t* iconFont = Fonts::iconFont(fontIdx); // Create a row container lv_obj_t* row = lv_obj_create(obj_); lv_obj_remove_style_all(row); lv_obj_set_size(row, LV_SIZE_CONTENT, LV_SIZE_CONTENT); lv_obj_set_pos(row, padding, currentY); lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_gap(row, 6, 0); lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE); lv_obj_clear_flag(row, LV_OBJ_FLAG_CLICKABLE); // Create icon if set if (tl.iconCodepoint > 0 && Fonts::hasIconFont()) { lv_obj_t* icon = lv_label_create(row); lv_obj_clear_flag(icon, LV_OBJ_FLAG_CLICKABLE); char iconText[5]; encodeUtf8(tl.iconCodepoint, iconText); lv_label_set_text(icon, iconText); if (iconFont) { lv_obj_set_style_text_font(icon, iconFont, 0); } lv_obj_set_style_text_color(icon, lv_color_hex(config_.textColor.toLvColor()), 0); } // Create value label lv_obj_t* label = lv_label_create(row); lv_obj_clear_flag(label, LV_OBJ_FLAG_CLICKABLE); // Set initial text based on source if (tl.textSrc == TextSource::STATIC) { lv_label_set_text(label, tl.text); } else { lv_label_set_text(label, "--"); } if (lineFont) { lv_obj_set_style_text_font(label, lineFont, 0); } lv_obj_set_style_text_color(label, lv_color_hex(config_.textColor.toLvColor()), 0); textLineLabels_[i] = label; // Move Y position for next line currentY += lv_font_get_line_height(lineFont) + 8; } } void RoomCardTileWidget::calculateSubButtonPosition(uint8_t index, const SubButtonConfig& cfg, int16_t& x, int16_t& y) { (void)cfg; // Not used for Tile layout // Buttons are stacked vertically on the right side int16_t subBtnSize = config_.subButtonSize > 0 ? config_.subButtonSize : 40; int16_t gap = 10; int16_t padding = 12; x = config_.width - subBtnSize - padding; y = padding + index * (subBtnSize + gap); } void RoomCardTileWidget::updateTextLineValue(uint8_t index, float value) { if (index >= MAX_TEXTLINES || !textLineLabels_[index]) return; const TextLineConfig& tl = config_.textLines[index]; if (tl.textSrc == TextSource::STATIC) return; char buf[32]; if (tl.text[0] != '\0') { snprintf(buf, sizeof(buf), tl.text, value); } else { // Default formats based on source type switch (tl.textSrc) { case TextSource::KNX_DPT_TEMP: snprintf(buf, sizeof(buf), "%.1f C", value); break; case TextSource::KNX_DPT_PERCENT: snprintf(buf, sizeof(buf), "%.0f %%", value); break; case TextSource::KNX_DPT_POWER: snprintf(buf, sizeof(buf), "%.1f W", value); break; case TextSource::KNX_DPT_ENERGY: snprintf(buf, sizeof(buf), "%.0f kWh", value); break; default: snprintf(buf, sizeof(buf), "%.1f", value); break; } } lv_label_set_text(textLineLabels_[index], buf); } void RoomCardTileWidget::onTextLineValue(uint8_t index, float value) { updateTextLineValue(index, value); } void RoomCardTileWidget::applyStyle() { if (!obj_) return; lv_color_t textColor = lv_color_hex(config_.textColor.toLvColor()); 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_hex(config_.borderColor.toLvColor()), 0); lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0); } else if (config_.shadow.enabled) { // Backward-compatible accent border behavior lv_obj_set_style_border_width(obj_, 3, 0); lv_obj_set_style_border_color(obj_, lv_color_hex(config_.shadow.color.toLvColor()), 0); lv_obj_set_style_border_opa(obj_, 255, 0); } else { lv_obj_set_style_border_width(obj_, 0, 0); } // Room name - large font if (roomLabel_) { const lv_font_t* nameFont = Fonts::bySizeIndex(config_.fontSize); lv_obj_set_style_text_font(roomLabel_, nameFont, 0); lv_obj_set_style_text_color(roomLabel_, textColor, 0); } // Large decorative icon if (decorIcon_) { uint8_t decorIconIdx = config_.iconSize; if (decorIconIdx < 6) decorIconIdx = 8; // Default to 96px for decorative const lv_font_t* bigIconFont = Fonts::iconFont(decorIconIdx); if (bigIconFont) { lv_obj_set_style_text_font(decorIcon_, bigIconFont, 0); lv_obj_set_style_text_color(decorIcon_, textColor, 0); lv_obj_set_style_text_opa(decorIcon_, 50, 0); // Very transparent } } // Style sub-buttons applySubButtonStyle(); }