#include "RoomCardBubbleWidget.hpp" #include "../Fonts.hpp" #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif RoomCardBubbleWidget::RoomCardBubbleWidget(const WidgetConfig& config) : RoomCardWidgetBase(config) { } void RoomCardBubbleWidget::clearLvglObject() { RoomCardWidgetBase::clearLvglObject(); bubble_ = nullptr; roomIcon_ = nullptr; } lv_obj_t* RoomCardBubbleWidget::create(lv_obj_t* parent) { parseTextBase(); 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); createBubbleLayout(); createSubButtonsCommon(); return obj_; } void RoomCardBubbleWidget::createBubbleLayout() { // Calculate bubble size (80% of widget size, circular) int16_t minSide = config_.width < config_.height ? config_.width : config_.height; int16_t bubbleSize = (minSide * 80) / 100; // Create bubble container (centered) bubble_ = lv_obj_create(obj_); lv_obj_remove_style_all(bubble_); lv_obj_set_size(bubble_, bubbleSize, bubbleSize); lv_obj_center(bubble_); lv_obj_clear_flag(bubble_, LV_OBJ_FLAG_SCROLLABLE); lv_obj_add_flag(bubble_, LV_OBJ_FLAG_CLICKABLE); // Circular shape lv_obj_set_style_radius(bubble_, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_bg_opa(bubble_, config_.bgOpacity, 0); lv_obj_set_style_bg_color(bubble_, lv_color_hex(config_.bgColor.toLvColor()), 0); // Set up flex layout for bubble content lv_obj_set_flex_flow(bubble_, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(bubble_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_all(bubble_, 8, 0); lv_obj_set_style_pad_gap(bubble_, 2, 0); // Room icon if (config_.iconCodepoint > 0 && Fonts::hasIconFont()) { roomIcon_ = lv_label_create(bubble_); lv_obj_clear_flag(roomIcon_, LV_OBJ_FLAG_CLICKABLE); char iconText[5]; encodeUtf8(config_.iconCodepoint, iconText); lv_label_set_text(roomIcon_, iconText); } // Temperature label tempLabel_ = lv_label_create(bubble_); lv_obj_clear_flag(tempLabel_, LV_OBJ_FLAG_CLICKABLE); lv_label_set_text(tempLabel_, tempFormat_[0] != '\0' ? "--" : ""); // Room name label if (roomName_[0] != '\0') { roomLabel_ = lv_label_create(bubble_); lv_obj_clear_flag(roomLabel_, LV_OBJ_FLAG_CLICKABLE); lv_label_set_text(roomLabel_, roomName_); } // Click handler for navigation lv_obj_add_event_cb(bubble_, cardClickCallback, LV_EVENT_CLICKED, this); } void RoomCardBubbleWidget::calculateSubButtonPosition(uint8_t index, const SubButtonConfig& cfg, int16_t& x, int16_t& y) { int16_t centerX = config_.width / 2; int16_t centerY = config_.height / 2; int16_t subBtnSize = config_.subButtonSize > 0 ? config_.subButtonSize : 40; int16_t orbitRadius = config_.subButtonDistance > 0 ? config_.subButtonDistance : 80; // Calculate angle: position 0=TOP, going clockwise, 8 positions = 45 degrees each float angle = static_cast(static_cast(cfg.position)) * (M_PI / 4.0f) - (M_PI / 2.0f); x = centerX + static_cast(orbitRadius * cosf(angle)) - subBtnSize / 2; y = centerY + static_cast(orbitRadius * sinf(angle)) - subBtnSize / 2; } void RoomCardBubbleWidget::applyStyle() { if (!obj_) return; lv_color_t textColor = lv_color_hex(config_.textColor.toLvColor()); // Apply shadow to bubble if enabled if (bubble_ && config_.shadow.enabled) { lv_obj_set_style_shadow_width(bubble_, config_.shadow.blur, 0); lv_obj_set_style_shadow_ofs_x(bubble_, config_.shadow.offsetX, 0); lv_obj_set_style_shadow_ofs_y(bubble_, config_.shadow.offsetY, 0); lv_obj_set_style_shadow_color(bubble_, lv_color_hex(config_.shadow.color.toLvColor()), 0); lv_obj_set_style_shadow_opa(bubble_, 255, 0); } if (bubble_ && config_.borderWidth > 0 && config_.borderOpacity > 0) { lv_obj_set_style_border_width(bubble_, config_.borderWidth, 0); lv_obj_set_style_border_color(bubble_, lv_color_hex(config_.borderColor.toLvColor()), 0); lv_obj_set_style_border_opa(bubble_, config_.borderOpacity, 0); } else if (bubble_) { lv_obj_set_style_border_width(bubble_, 0, 0); } // Font sizes const lv_font_t* iconFont = Fonts::iconFont(config_.iconSize); const lv_font_t* textFont = Fonts::bySizeIndex(config_.fontSize); const lv_font_t* labelFont = Fonts::bySizeIndex(config_.fontSize > 0 ? config_.fontSize - 1 : 0); // Style room icon if (roomIcon_ && iconFont) { lv_obj_set_style_text_font(roomIcon_, iconFont, 0); lv_obj_set_style_text_color(roomIcon_, textColor, 0); } // Style temperature if (tempLabel_ && textFont) { lv_obj_set_style_text_font(tempLabel_, textFont, 0); lv_obj_set_style_text_color(tempLabel_, textColor, 0); } // Style room label if (roomLabel_ && labelFont) { lv_obj_set_style_text_font(roomLabel_, labelFont, 0); lv_obj_set_style_text_color(roomLabel_, textColor, 0); lv_obj_set_style_text_opa(roomLabel_, 180, 0); // Slightly dimmed } // Style sub-buttons applySubButtonStyle(); }