knxdisplay/main/widgets/ClockWidget.cpp
2026-01-30 19:07:52 +01:00

162 lines
5.7 KiB
C++

#include "ClockWidget.hpp"
#include <cmath>
#include <initializer_list>
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);
}