272 lines
9.1 KiB
C++
272 lines
9.1 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;
|
|
bool shouldHide = (speed <= 0.01f || pathLen_ <= 0.5f);
|
|
bool isHidden = lv_obj_has_flag(dot_, LV_OBJ_FLAG_HIDDEN);
|
|
if (std::fabs(speed - speed_) < 0.01f && reverse == reverse_ && isHidden == shouldHide) {
|
|
return;
|
|
}
|
|
|
|
speed_ = speed;
|
|
reverse_ = reverse;
|
|
|
|
lv_anim_del(this, nullptr);
|
|
|
|
if (shouldHide) {
|
|
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);
|
|
}
|