#include "PowerLinkWidget.hpp" #include "../WidgetManager.hpp" #include #include PowerLinkWidget::PowerLinkWidget(const WidgetConfig& config) : Widget(config) { for (uint8_t i = 0; i < POINT_COUNT; i++) { points_[i].x = 0; points_[i].y = 0; } } PowerLinkWidget::~PowerLinkWidget() { lv_anim_del(this, nullptr); if (dot_ != nullptr) { if (lv_obj_is_valid(dot_)) { lv_obj_delete(dot_); } dot_ = nullptr; } } static float parseFloatOr(const char* text, float fallback) { if (text == nullptr || text[0] == '\0') return fallback; char* end = nullptr; float value = std::strtof(text, &end); if (end == text || !std::isfinite(value)) return fallback; return value; } void PowerLinkWidget::buildLinePoints() { if (obj_ == nullptr) return; if (config_.x < 0 || config_.y < 0) return; const ScreenConfig* screen = WidgetManager::instance().currentScreen(); if (screen == nullptr) return; const WidgetConfig* fromNode = screen->findWidget(static_cast(config_.x)); const WidgetConfig* toNode = screen->findWidget(static_cast(config_.y)); if (fromNode == nullptr || toNode == nullptr) return; if (fromNode->parentId != config_.parentId || toNode->parentId != config_.parentId) return; float x1 = static_cast(fromNode->x) + static_cast(fromNode->width) * 0.5f; float y1 = static_cast(fromNode->y) + static_cast(fromNode->height) * 0.5f; float x2 = static_cast(toNode->x) + static_cast(toNode->width) * 0.5f; float y2 = static_cast(toNode->y) + static_cast(toNode->height) * 0.5f; float dx = x2 - x1; float dy = y2 - y1; float len = std::sqrt(dx * dx + dy * dy); if (len < 0.001f) return; float lineWidth = config_.width > 0 ? static_cast(config_.width) : 3.0f; if (lineWidth < 3.0f) lineWidth = 3.0f; int16_t minSideA = fromNode->width < fromNode->height ? fromNode->width : fromNode->height; int16_t minSideB = toNode->width < toNode->height ? toNode->width : toNode->height; float radiusA = static_cast(minSideA) * 0.5f - lineWidth * 0.5f; float radiusB = static_cast(minSideB) * 0.5f - lineWidth * 0.5f; if (radiusA < 0.0f) radiusA = 0.0f; if (radiusB < 0.0f) radiusB = 0.0f; float ux = dx / len; float uy = dy / len; float startX = x1 + ux * radiusA; float startY = y1 + uy * radiusA; float endX = x2 - ux * radiusB; float endY = y2 - uy * radiusB; if (len <= radiusA + radiusB + 1.0f) { startX = x1; startY = y1; endX = x2; endY = y2; } float dxTrim = endX - startX; float dyTrim = endY - startY; float lenTrim = std::sqrt(dxTrim * dxTrim + dyTrim * dyTrim); if (lenTrim < 0.001f) lenTrim = len; float nx = -dyTrim / lenTrim; float ny = dxTrim / lenTrim; float midX = (startX + endX) * 0.5f; float midY = (startY + endY) * 0.5f; float curve = std::fmin(40.0f, lenTrim * 0.25f); float curveSign = (config_.iconPosition == 2) ? -1.0f : 1.0f; if (config_.iconPosition == 0) { curveSign = (config_.id % 2 == 0) ? 1.0f : -1.0f; } else if (config_.iconPosition == 1) { curveSign = 1.0f; } else if (config_.iconPosition == 3) { curveSign = -1.0f; } p0x_ = startX; p0y_ = startY; p1x_ = midX + nx * curve * curveSign; p1y_ = midY + ny * curve * curveSign; p2x_ = endX; p2y_ = endY; pathLen_ = lenTrim; float minXf = p0x_; float minYf = p0y_; float maxXf = p0x_; float maxYf = p0y_; for (uint8_t i = 0; i < POINT_COUNT; i++) { float t = static_cast(i) / static_cast(POINT_COUNT - 1); float inv = 1.0f - t; float x = inv * inv * p0x_ + 2.0f * inv * t * p1x_ + t * t * p2x_; float y = inv * inv * p0y_ + 2.0f * inv * t * p1y_ + t * t * p2y_; if (x < minXf) minXf = x; if (y < minYf) minYf = y; if (x > maxXf) maxXf = x; if (y > maxYf) maxYf = y; } int32_t minX = static_cast(std::floor(minXf)); int32_t minY = static_cast(std::floor(minYf)); int32_t maxX = static_cast(std::ceil(maxXf)); int32_t maxY = static_cast(std::ceil(maxYf)); for (uint8_t i = 0; i < POINT_COUNT; i++) { float t = static_cast(i) / static_cast(POINT_COUNT - 1); float inv = 1.0f - t; float x = inv * inv * p0x_ + 2.0f * inv * t * p1x_ + t * t * p2x_; float y = inv * inv * p0y_ + 2.0f * inv * t * p1y_ + t * t * p2y_; points_[i].x = x - minX; points_[i].y = y - minY; } lv_obj_set_pos(obj_, minX, minY); lv_obj_set_size(obj_, (maxX - minX) + 1, (maxY - minY) + 1); lv_line_set_points(obj_, points_, POINT_COUNT); dotAnimExec(this, 0); } lv_obj_t* PowerLinkWidget::create(lv_obj_t* parent) { obj_ = lv_line_create(parent); if (obj_ == nullptr) { return nullptr; } lv_obj_clear_flag(obj_, LV_OBJ_FLAG_CLICKABLE); lv_obj_move_to_index(obj_, 0); buildLinePoints(); dot_ = lv_obj_create(parent); if (dot_ != nullptr) { lv_obj_remove_style_all(dot_); lv_obj_clear_flag(dot_, LV_OBJ_FLAG_CLICKABLE); lv_obj_clear_flag(dot_, LV_OBJ_FLAG_SCROLLABLE); lv_obj_move_to_index(dot_, 1); } startDotAnimation(); return obj_; } void PowerLinkWidget::applyStyle() { if (obj_ == nullptr) return; uint16_t lineWidth = config_.width > 0 ? config_.width : 3; if (lineWidth < 3) lineWidth = 3; lv_obj_set_style_line_color(obj_, lv_color_make( config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0); lv_obj_set_style_line_width(obj_, lineWidth, 0); lv_obj_set_style_line_opa(obj_, config_.bgOpacity, 0); lv_obj_set_style_line_dash_width(obj_, 0, 0); lv_obj_set_style_line_dash_gap(obj_, 0, 0); if (dot_ != nullptr) { uint16_t dotSize = static_cast(lineWidth * 3); if (dotSize < 6) dotSize = 6; if (dotSize > 16) dotSize = 16; dotRadius_ = dotSize * 0.5f; lv_obj_set_size(dot_, dotSize, dotSize); lv_obj_set_style_radius(dot_, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_bg_color(dot_, lv_color_make( config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0); lv_obj_set_style_bg_opa(dot_, config_.bgOpacity, 0); dotAnimExec(this, 0); } } void PowerLinkWidget::startDotAnimation() { if (dot_ == nullptr || obj_ == nullptr) return; if (config_.textSource == TextSource::STATIC) { float speed = parseFloatOr(config_.text, 60.0f); updateAnimation(speed, false); } else { updateAnimation(0.0f, false); } } void PowerLinkWidget::dotAnimExec(void* var, int32_t value) { auto* self = static_cast(var); if (self == nullptr || self->dot_ == nullptr) return; float t = static_cast(value) / 1000.0f; if (self->reverse_) { t = 1.0f - t; } float inv = 1.0f - t; float x = inv * inv * self->p0x_ + 2.0f * inv * t * self->p1x_ + t * t * self->p2x_; float y = inv * inv * self->p0y_ + 2.0f * inv * t * self->p1y_ + t * t * self->p2y_; lv_obj_set_pos(self->dot_, static_cast(x - self->dotRadius_), static_cast(y - self->dotRadius_)); } void PowerLinkWidget::updateAnimation(float speed, bool reverse) { if (dot_ == nullptr || obj_ == nullptr) return; speed_ = speed; reverse_ = reverse; lv_anim_del(this, nullptr); if (speed_ <= 0.01f || pathLen_ <= 0.5f) { lv_obj_add_flag(dot_, LV_OBJ_FLAG_HIDDEN); return; } lv_obj_clear_flag(dot_, LV_OBJ_FLAG_HIDDEN); int32_t duration = static_cast((pathLen_ / speed_) * 1000.0f); if (duration < 800) duration = 800; if (duration > 12000) duration = 12000; lv_anim_t anim; lv_anim_init(&anim); lv_anim_set_var(&anim, this); lv_anim_set_exec_cb(&anim, PowerLinkWidget::dotAnimExec); lv_anim_set_values(&anim, 0, 1000); lv_anim_set_repeat_count(&anim, LV_ANIM_REPEAT_INFINITE); lv_anim_set_path_cb(&anim, lv_anim_path_linear); lv_anim_set_time(&anim, duration); lv_anim_start(&anim); dotAnimExec(this, 0); } void PowerLinkWidget::onKnxValue(float value) { if (config_.textSource != TextSource::KNX_DPT_TEMP && config_.textSource != TextSource::KNX_DPT_PERCENT) return; float factor = parseFloatOr(config_.text, 1.0f); float speed = std::fabs(value) * factor; updateAnimation(speed, value < 0.0f); } void PowerLinkWidget::onKnxSwitch(bool value) { if (config_.textSource != TextSource::KNX_DPT_SWITCH) return; float factor = parseFloatOr(config_.text, 1.0f); float speed = value ? factor : 0.0f; updateAnimation(speed, false); }