#include "ClockWidget.hpp" #include #include ClockWidget::ClockWidget(const WidgetConfig& config) : Widget(config) { } lv_obj_t* ClockWidget::create(lv_obj_t* parent) { obj_ = lv_obj_create(parent); if (!obj_) return nullptr; lv_obj_remove_style_all(obj_); lv_obj_set_pos(obj_, config_.x, config_.y); lv_obj_set_size(obj_, config_.width > 0 ? config_.width : 200, config_.height > 0 ? config_.height : 200); lv_obj_clear_flag(obj_, LV_OBJ_FLAG_SCROLLABLE); lv_obj_clear_flag(obj_, LV_OBJ_FLAG_CLICKABLE); // Create hands as rectangles auto createHand = [&](lv_obj_t*& hand, const char* name) { hand = lv_obj_create(obj_); lv_obj_remove_style_all(hand); lv_obj_add_flag(hand, LV_OBJ_FLAG_IGNORE_LAYOUT); lv_obj_clear_flag(hand, LV_OBJ_FLAG_CLICKABLE); // Set pivot to bottom center (will be set in applyStyle implicitly by size?) // We will set alignment there. }; createHand(hourHand_, "Hour"); createHand(minuteHand_, "Minute"); createHand(secondHand_, "Second"); centerDot_ = lv_obj_create(obj_); lv_obj_remove_style_all(centerDot_); lv_obj_add_flag(centerDot_, LV_OBJ_FLAG_IGNORE_LAYOUT); lv_obj_clear_flag(centerDot_, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_size(centerDot_, 12, 12); lv_obj_set_style_radius(centerDot_, LV_RADIUS_CIRCLE, 0); lv_obj_center(centerDot_); // Initial update time_t now; time(&now); struct tm t; localtime_r(&now, &t); updateHands(t); return obj_; } void ClockWidget::applyStyle() { if (!obj_) return; // Face style 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); } else { lv_obj_set_style_radius(obj_, LV_RADIUS_CIRCLE, 0); } lv_obj_set_style_border_width(obj_, 2, 0); lv_obj_set_style_border_color(obj_, lv_color_make( config_.textColor.r, config_.textColor.g, config_.textColor.b), 0); lv_color_t handColor = lv_color_make( config_.textColor.r, config_.textColor.g, config_.textColor.b); lv_color_t secColor = lv_color_make(200, 50, 50); int32_t w = config_.width > 0 ? config_.width : 200; int32_t h = config_.height > 0 ? config_.height : 200; int32_t radius = (w < h ? w : h) / 2; auto configHand = [&](lv_obj_t* hand, int32_t width, int32_t length, lv_color_t color) { if (!hand) return; lv_obj_set_size(hand, width, length); lv_obj_set_style_bg_color(hand, color, 0); lv_obj_set_style_bg_opa(hand, LV_OPA_COVER, 0); lv_obj_set_style_radius(hand, width / 2, 0); // Align to center-bottom of the hand to the center of the clock // For rotation, we position the hand such that its rotation point is at the clock center. // We put the hand's bottom-center at the clock's center. // LVGL Rotation pivot is relative to the object's top-left (0,0) by default? // No, default pivot is center. // We want pivot at (width/2, length). // And we want that pivot point to be at clock center (w/2, h/2). // Position: // x = (ClockW - HandW) / 2 // y = (ClockH/2) - HandLength // This puts the bottom of the hand at the center. lv_obj_align(hand, LV_ALIGN_CENTER, 0, -length/2); // Wait, align center puts the *center* of hand at center of clock. // Hand center is at (w/2, length/2). // We want hand bottom (w/2, length) to be at clock center. // So we shift y by -length/2. // Pivot: // Set pivot to (width/2, length). // lv_obj_set_style_transform_pivot_x(hand, width/2, 0); // lv_obj_set_style_transform_pivot_y(hand, length, 0); // Bottom // BUT: lv_obj_align(..., 0, -length/2) moves it up. // Let's verify. lv_obj_set_style_transform_pivot_x(hand, width / 2, 0); lv_obj_set_style_transform_pivot_y(hand, length - 2, 0); // Slightly above bottom for overlap }; configHand(hourHand_, 6, radius * 0.6, handColor); configHand(minuteHand_, 4, radius * 0.85, handColor); configHand(secondHand_, 2, radius * 0.9, secColor); if (centerDot_) { lv_obj_set_style_bg_color(centerDot_, handColor, 0); lv_obj_set_style_bg_opa(centerDot_, LV_OPA_COVER, 0); } // Force update lastSecond_ = -1; time_t now; time(&now); struct tm t; localtime_r(&now, &t); updateHands(t); } void ClockWidget::updateHands(const struct tm& t) { if (t.tm_sec == lastSecond_ && t.tm_min == lastMinute_ && t.tm_hour == lastHour_) return; lastSecond_ = t.tm_sec; lastMinute_ = t.tm_min; lastHour_ = t.tm_hour; // Calculate angles in 0.1 degree units (LVGL 9) // Second: 6 deg * 10 = 60 per sec int32_t angleSec = t.tm_sec * 60; // Minute: (6 deg * min + 0.1 deg * sec) * 10 = 60 * min + sec int32_t angleMin = t.tm_min * 60 + t.tm_sec; // Hour: (30 deg * hr + 0.5 deg * min) * 10 = 300 * (hr%12) + 5 * min int32_t angleHour = (t.tm_hour % 12) * 300 + t.tm_min * 5; if (secondHand_) lv_obj_set_style_transform_rotation(secondHand_, angleSec, 0); if (minuteHand_) lv_obj_set_style_transform_rotation(minuteHand_, angleMin, 0); if (hourHand_) lv_obj_set_style_transform_rotation(hourHand_, angleHour, 0); } void ClockWidget::onKnxTime(const struct tm& value, TextSource source) { updateHands(value); }