knxdisplay/main/widgets/PowerLinkWidget.cpp
2026-01-28 22:41:05 +01:00

266 lines
8.9 KiB
C++

#include "PowerLinkWidget.hpp"
#include "../WidgetManager.hpp"
#include <cmath>
#include <cstdlib>
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<uint8_t>(config_.x));
const WidgetConfig* toNode = screen->findWidget(static_cast<uint8_t>(config_.y));
if (fromNode == nullptr || toNode == nullptr) return;
if (fromNode->parentId != config_.parentId || toNode->parentId != config_.parentId) return;
float x1 = static_cast<float>(fromNode->x) + static_cast<float>(fromNode->width) * 0.5f;
float y1 = static_cast<float>(fromNode->y) + static_cast<float>(fromNode->height) * 0.5f;
float x2 = static_cast<float>(toNode->x) + static_cast<float>(toNode->width) * 0.5f;
float y2 = static_cast<float>(toNode->y) + static_cast<float>(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<float>(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<float>(minSideA) * 0.5f - lineWidth * 0.5f;
float radiusB = static_cast<float>(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<float>(i) / static_cast<float>(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<int32_t>(std::floor(minXf));
int32_t minY = static_cast<int32_t>(std::floor(minYf));
int32_t maxX = static_cast<int32_t>(std::ceil(maxXf));
int32_t maxY = static_cast<int32_t>(std::ceil(maxYf));
for (uint8_t i = 0; i < POINT_COUNT; i++) {
float t = static_cast<float>(i) / static_cast<float>(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<uint16_t>(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<PowerLinkWidget*>(var);
if (self == nullptr || self->dot_ == nullptr) return;
float t = static_cast<float>(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<int32_t>(x - self->dotRadius_),
static_cast<int32_t>(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<int32_t>((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 &&
config_.textSource != TextSource::KNX_DPT_POWER &&
config_.textSource != TextSource::KNX_DPT_ENERGY &&
config_.textSource != TextSource::KNX_DPT_DECIMALFACTOR) 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);
}