Fixes
This commit is contained in:
parent
fbf7fa12a2
commit
75a7e18913
@ -1,13 +1,33 @@
|
|||||||
#include "PowerLinkWidget.hpp"
|
#include "PowerLinkWidget.hpp"
|
||||||
#include "../WidgetManager.hpp"
|
#include "../WidgetManager.hpp"
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
PowerLinkWidget::PowerLinkWidget(const WidgetConfig& config)
|
PowerLinkWidget::PowerLinkWidget(const WidgetConfig& config)
|
||||||
: Widget(config)
|
: Widget(config)
|
||||||
{
|
{
|
||||||
points_[0].x = 0;
|
for (uint8_t i = 0; i < POINT_COUNT; i++) {
|
||||||
points_[0].y = 0;
|
points_[i].x = 0;
|
||||||
points_[1].x = 1;
|
points_[i].y = 0;
|
||||||
points_[1].y = 1;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
void PowerLinkWidget::buildLinePoints() {
|
||||||
@ -22,24 +42,101 @@ void PowerLinkWidget::buildLinePoints() {
|
|||||||
if (fromNode == nullptr || toNode == nullptr) return;
|
if (fromNode == nullptr || toNode == nullptr) return;
|
||||||
if (fromNode->parentId != config_.parentId || toNode->parentId != config_.parentId) return;
|
if (fromNode->parentId != config_.parentId || toNode->parentId != config_.parentId) return;
|
||||||
|
|
||||||
int16_t x1 = fromNode->x + fromNode->width / 2;
|
float x1 = static_cast<float>(fromNode->x) + static_cast<float>(fromNode->width) * 0.5f;
|
||||||
int16_t y1 = fromNode->y + fromNode->height / 2;
|
float y1 = static_cast<float>(fromNode->y) + static_cast<float>(fromNode->height) * 0.5f;
|
||||||
int16_t x2 = toNode->x + toNode->width / 2;
|
float x2 = static_cast<float>(toNode->x) + static_cast<float>(toNode->width) * 0.5f;
|
||||||
int16_t y2 = toNode->y + toNode->height / 2;
|
float y2 = static_cast<float>(toNode->y) + static_cast<float>(toNode->height) * 0.5f;
|
||||||
|
|
||||||
int16_t minX = x1 < x2 ? x1 : x2;
|
float dx = x2 - x1;
|
||||||
int16_t minY = y1 < y2 ? y1 : y2;
|
float dy = y2 - y1;
|
||||||
int16_t maxX = x1 > x2 ? x1 : x2;
|
float len = std::sqrt(dx * dx + dy * dy);
|
||||||
int16_t maxY = y1 > y2 ? y1 : y2;
|
if (len < 0.001f) return;
|
||||||
|
|
||||||
points_[0].x = x1 - minX;
|
float lineWidth = config_.width > 0 ? static_cast<float>(config_.width) : 3.0f;
|
||||||
points_[0].y = y1 - minY;
|
if (lineWidth < 3.0f) lineWidth = 3.0f;
|
||||||
points_[1].x = x2 - minX;
|
int16_t minSideA = fromNode->width < fromNode->height ? fromNode->width : fromNode->height;
|
||||||
points_[1].y = y2 - minY;
|
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_pos(obj_, minX, minY);
|
||||||
lv_obj_set_size(obj_, (maxX - minX) + 1, (maxY - minY) + 1);
|
lv_obj_set_size(obj_, (maxX - minX) + 1, (maxY - minY) + 1);
|
||||||
lv_line_set_points(obj_, points_, 2);
|
lv_line_set_points(obj_, points_, POINT_COUNT);
|
||||||
|
|
||||||
|
dotAnimExec(this, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
lv_obj_t* PowerLinkWidget::create(lv_obj_t* parent) {
|
lv_obj_t* PowerLinkWidget::create(lv_obj_t* parent) {
|
||||||
@ -49,19 +146,117 @@ lv_obj_t* PowerLinkWidget::create(lv_obj_t* parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lv_obj_clear_flag(obj_, LV_OBJ_FLAG_CLICKABLE);
|
lv_obj_clear_flag(obj_, LV_OBJ_FLAG_CLICKABLE);
|
||||||
|
lv_obj_move_to_index(obj_, 0);
|
||||||
buildLinePoints();
|
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_;
|
return obj_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PowerLinkWidget::applyStyle() {
|
void PowerLinkWidget::applyStyle() {
|
||||||
if (obj_ == nullptr) return;
|
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(
|
lv_obj_set_style_line_color(obj_, lv_color_make(
|
||||||
config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0);
|
config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0);
|
||||||
lv_obj_set_style_line_width(obj_, config_.width > 0 ? config_.width : 2, 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_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);
|
||||||
|
|
||||||
int32_t dash = config_.iconGap > 0 ? config_.iconGap : 6;
|
if (dot_ != nullptr) {
|
||||||
lv_obj_set_style_line_dash_width(obj_, dash, 0);
|
uint16_t dotSize = static_cast<uint16_t>(lineWidth * 3);
|
||||||
lv_obj_set_style_line_dash_gap(obj_, dash + 4, 0);
|
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) 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,10 +5,28 @@
|
|||||||
class PowerLinkWidget : public Widget {
|
class PowerLinkWidget : public Widget {
|
||||||
public:
|
public:
|
||||||
explicit PowerLinkWidget(const WidgetConfig& config);
|
explicit PowerLinkWidget(const WidgetConfig& config);
|
||||||
|
~PowerLinkWidget() override;
|
||||||
lv_obj_t* create(lv_obj_t* parent) override;
|
lv_obj_t* create(lv_obj_t* parent) override;
|
||||||
void applyStyle() override;
|
void applyStyle() override;
|
||||||
|
void onKnxValue(float value) override;
|
||||||
|
void onKnxSwitch(bool value) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
lv_point_precise_t points_[2] = {};
|
static constexpr uint8_t POINT_COUNT = 16;
|
||||||
|
lv_point_precise_t points_[POINT_COUNT] = {};
|
||||||
|
lv_obj_t* dot_ = nullptr;
|
||||||
|
float p0x_ = 0.0f;
|
||||||
|
float p0y_ = 0.0f;
|
||||||
|
float p1x_ = 0.0f;
|
||||||
|
float p1y_ = 0.0f;
|
||||||
|
float p2x_ = 0.0f;
|
||||||
|
float p2y_ = 0.0f;
|
||||||
|
float pathLen_ = 0.0f;
|
||||||
|
float dotRadius_ = 3.0f;
|
||||||
|
float speed_ = 0.0f;
|
||||||
|
bool reverse_ = false;
|
||||||
void buildLinePoints();
|
void buildLinePoints();
|
||||||
|
void startDotAnimation();
|
||||||
|
void updateAnimation(float speed, bool reverse);
|
||||||
|
static void dotAnimExec(void* var, int32_t value);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -131,13 +131,45 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-[11px] text-muted mb-2">{{ linkModeHint }}</div>
|
<div class="text-[11px] text-muted mb-2">{{ linkModeHint }}</div>
|
||||||
<div v-if="powerFlowLinkItems.length" class="mt-2">
|
<div v-if="powerFlowLinkItems.length" class="mt-2">
|
||||||
<div v-for="link in powerFlowLinkItems" :key="link.id" class="flex items-center gap-2 mb-2 text-[11px] text-muted">
|
<div v-for="link in powerFlowLinkItems" :key="link.id" class="border border-border rounded-lg px-2.5 py-2 mb-2 bg-panel-2">
|
||||||
<span class="px-1.5 py-0.5 rounded-md bg-panel-2 border border-border text-text max-w-[90px] truncate">{{ link.fromLabel }}</span>
|
<div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
|
||||||
|
<span class="px-1.5 py-0.5 rounded-md bg-white border border-border text-text max-w-[90px] truncate">{{ link.fromLabel }}</span>
|
||||||
<span>-></span>
|
<span>-></span>
|
||||||
<span class="px-1.5 py-0.5 rounded-md bg-panel-2 border border-border text-text max-w-[90px] truncate">{{ link.toLabel }}</span>
|
<span class="px-1.5 py-0.5 rounded-md bg-white border border-border text-text max-w-[90px] truncate">{{ link.toLabel }}</span>
|
||||||
<input class="h-[22px] w-[32px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="link.widget.bgColor">
|
|
||||||
<button class="ml-auto w-6 h-6 rounded-md border border-red-200 bg-[#f7dede] text-[#b3261e] grid place-items-center text-[11px] cursor-pointer hover:bg-[#f2cfcf]" @click="removePowerLink(link.id)">x</button>
|
<button class="ml-auto w-6 h-6 rounded-md border border-red-200 bg-[#f7dede] text-[#b3261e] grid place-items-center text-[11px] cursor-pointer hover:bg-[#f2cfcf]" @click="removePowerLink(link.id)">x</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
|
||||||
|
<label class="w-[70px] text-[11px] text-muted">Linie</label>
|
||||||
|
<input class="h-[22px] w-[32px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="link.widget.bgColor">
|
||||||
|
<input class="w-[70px] bg-white border border-border rounded-md px-2 py-1 text-[11px]" type="number" min="1" max="12" v-model.number="link.widget.w">
|
||||||
|
<span class="text-[10px] text-muted">px</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
|
||||||
|
<label class="w-[70px] text-[11px] text-muted">Speed</label>
|
||||||
|
<select class="flex-1 bg-white border border-border rounded-md px-2 py-1 text-[11px]" v-model.number="link.widget.textSrc">
|
||||||
|
<option v-for="opt in sourceOptions.powerlink" :key="opt" :value="opt">{{ textSources[opt] }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div v-if="link.widget.textSrc === 0" class="flex items-center gap-2 mb-2 text-[11px] text-muted">
|
||||||
|
<label class="w-[70px] text-[11px] text-muted">Wert</label>
|
||||||
|
<input class="flex-1 bg-white border border-border rounded-md px-2 py-1 text-[11px]" type="text" inputmode="decimal" v-model="link.widget.text" placeholder="z.B. 60">
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex flex-col gap-2 text-[11px] text-muted">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<label class="w-[70px] text-[11px] text-muted">Faktor</label>
|
||||||
|
<input class="flex-1 bg-white border border-border rounded-md px-2 py-1 text-[11px]" type="text" inputmode="decimal" v-model="link.widget.text" placeholder="z.B. 0.2">
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<label class="w-[70px] text-[11px] text-muted">KNX</label>
|
||||||
|
<select class="flex-1 bg-white border border-border rounded-md px-2 py-1 text-[11px]" v-model.number="link.widget.knxAddr">
|
||||||
|
<option :value="0">-- Waehlen --</option>
|
||||||
|
<option v-for="addr in store.knxAddresses" :key="addr.index" :value="addr.index">
|
||||||
|
GO{{ addr.index }} ({{ addr.addrStr }})
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-[11px] text-muted">Keine Verbindungen.</div>
|
<div v-else class="text-[11px] text-muted">Keine Verbindungen.</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -53,19 +53,22 @@
|
|||||||
:stroke="link.color"
|
:stroke="link.color"
|
||||||
:stroke-width="link.width"
|
:stroke-width="link.width"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
:stroke-dasharray="`${link.dash} ${link.dash + 4}`"
|
|
||||||
fill="none"
|
fill="none"
|
||||||
:opacity="link.opacity"
|
:opacity="link.opacity"
|
||||||
/>
|
/>
|
||||||
<circle
|
<circle
|
||||||
v-for="link in powerFlowLinks"
|
v-for="link in powerFlowLinks"
|
||||||
:key="`dot-${link.id}`"
|
:key="`dot-${link.id}`"
|
||||||
:cx="link.dotX"
|
:r="link.dotRadius"
|
||||||
:cy="link.dotY"
|
|
||||||
r="3"
|
|
||||||
:fill="link.color"
|
:fill="link.color"
|
||||||
:opacity="link.opacity"
|
:opacity="link.opacity"
|
||||||
|
>
|
||||||
|
<animateMotion
|
||||||
|
:dur="`${link.duration}s`"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
:path="link.path"
|
||||||
/>
|
/>
|
||||||
|
</circle>
|
||||||
</svg>
|
</svg>
|
||||||
<div v-if="!powerNodes.length" class="absolute inset-0 grid place-items-center text-[12px] text-muted">
|
<div v-if="!powerNodes.length" class="absolute inset-0 grid place-items-center text-[12px] text-muted">
|
||||||
Power Nodes hinzufuegen
|
Power Nodes hinzufuegen
|
||||||
@ -345,6 +348,7 @@ const powerFlowLinks = computed(() => {
|
|||||||
const toNode = nodeMap.get(link.y);
|
const toNode = nodeMap.get(link.y);
|
||||||
if (!fromNode || !toNode) return [];
|
if (!fromNode || !toNode) return [];
|
||||||
|
|
||||||
|
const lineWidth = Math.max(3, link.w || 3);
|
||||||
const fromCenter = {
|
const fromCenter = {
|
||||||
x: (fromNode.x + fromNode.w / 2) * s,
|
x: (fromNode.x + fromNode.w / 2) * s,
|
||||||
y: (fromNode.y + fromNode.h / 2) * s
|
y: (fromNode.y + fromNode.h / 2) * s
|
||||||
@ -357,25 +361,51 @@ const powerFlowLinks = computed(() => {
|
|||||||
const dx = toCenter.x - fromCenter.x;
|
const dx = toCenter.x - fromCenter.x;
|
||||||
const dy = toCenter.y - fromCenter.y;
|
const dy = toCenter.y - fromCenter.y;
|
||||||
const len = Math.hypot(dx, dy) || 1;
|
const len = Math.hypot(dx, dy) || 1;
|
||||||
const nx = -dy / len;
|
const ux = dx / len;
|
||||||
const ny = dx / len;
|
const uy = dy / len;
|
||||||
const midX = (fromCenter.x + toCenter.x) / 2;
|
const fromRadius = Math.max(0, (Math.min(fromNode.w, fromNode.h) * 0.5 - lineWidth * 0.5) * s);
|
||||||
const midY = (fromCenter.y + toCenter.y) / 2;
|
const toRadius = Math.max(0, (Math.min(toNode.w, toNode.h) * 0.5 - lineWidth * 0.5) * s);
|
||||||
|
|
||||||
|
let startX = fromCenter.x + ux * fromRadius;
|
||||||
|
let startY = fromCenter.y + uy * fromRadius;
|
||||||
|
let endX = toCenter.x - ux * toRadius;
|
||||||
|
let endY = toCenter.y - uy * toRadius;
|
||||||
|
|
||||||
|
if (len <= fromRadius + toRadius + 1) {
|
||||||
|
startX = fromCenter.x;
|
||||||
|
startY = fromCenter.y;
|
||||||
|
endX = toCenter.x;
|
||||||
|
endY = toCenter.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dxTrim = endX - startX;
|
||||||
|
const dyTrim = endY - startY;
|
||||||
|
const lenTrim = Math.hypot(dxTrim, dyTrim) || 1;
|
||||||
|
const nx = -dyTrim / lenTrim;
|
||||||
|
const ny = dxTrim / lenTrim;
|
||||||
|
const midX = (startX + endX) / 2;
|
||||||
|
const midY = (startY + endY) / 2;
|
||||||
const curveSign = idx % 2 === 0 ? 1 : -1;
|
const curveSign = idx % 2 === 0 ? 1 : -1;
|
||||||
const curve = Math.min(42 * s, len * 0.3) * curveSign;
|
const curve = Math.min(42 * s, lenTrim * 0.3) * curveSign;
|
||||||
const cpx = midX + nx * curve;
|
const cpx = midX + nx * curve;
|
||||||
const cpy = midY + ny * curve;
|
const cpy = midY + ny * curve;
|
||||||
const dash = Math.max(2, link.iconGap || 6);
|
const dotRadius = Math.min(8, Math.max(4, lineWidth * 1.6));
|
||||||
|
const rawValue = parseFloat(link.text);
|
||||||
|
const hasRaw = Number.isFinite(rawValue);
|
||||||
|
const isStatic = (link.textSrc ?? 0) === 0;
|
||||||
|
const factor = hasRaw ? rawValue : (isStatic ? 60 : 1);
|
||||||
|
const previewValue = 50;
|
||||||
|
const speed = Math.max(5, isStatic ? factor : Math.abs(previewValue) * factor);
|
||||||
|
const duration = Math.max(2, Math.min(10, lenTrim / speed));
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
id: link.id,
|
id: link.id,
|
||||||
path: `M ${fromCenter.x} ${fromCenter.y} Q ${cpx} ${cpy} ${toCenter.x} ${toCenter.y}`,
|
path: `M ${startX} ${startY} Q ${cpx} ${cpy} ${endX} ${endY}`,
|
||||||
color: link.bgColor || '#6fa7d8',
|
color: link.bgColor || '#6fa7d8',
|
||||||
opacity: clamp((link.bgOpacity ?? 255) / 255, 0.1, 1),
|
opacity: clamp((link.bgOpacity ?? 255) / 255, 0.1, 1),
|
||||||
width: Math.max(1, link.w || 2),
|
width: lineWidth,
|
||||||
dash,
|
dotRadius,
|
||||||
dotX: midX + nx * curve * 0.5,
|
duration
|
||||||
dotY: midY + ny * curve * 0.5
|
|
||||||
}];
|
}];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -71,7 +71,8 @@ export const sourceOptions = {
|
|||||||
button: [0],
|
button: [0],
|
||||||
led: [0, 2],
|
led: [0, 2],
|
||||||
icon: [0, 2],
|
icon: [0, 2],
|
||||||
powernode: [0, 1, 2, 3, 4]
|
powernode: [0, 1, 2, 3, 4],
|
||||||
|
powerlink: [0, 1, 3]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ICON_DEFAULTS = {
|
export const ICON_DEFAULTS = {
|
||||||
@ -269,7 +270,7 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconGap: 8
|
iconGap: 8
|
||||||
},
|
},
|
||||||
powerlink: {
|
powerlink: {
|
||||||
w: 2,
|
w: 3,
|
||||||
h: 0,
|
h: 0,
|
||||||
text: '',
|
text: '',
|
||||||
textSrc: 0,
|
textSrc: 0,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user