162 lines
5.7 KiB
C++
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);
|
|
} |