Fixes
This commit is contained in:
parent
21126bb3a8
commit
2ea18624fc
@ -12,6 +12,7 @@ idf_component_register(SRCS "HistoryStore.cpp" "KnxWorker.cpp" "Nvs.cpp" "main.c
|
||||
"widgets/PowerLinkWidget.cpp"
|
||||
"widgets/ChartWidget.cpp"
|
||||
"widgets/ClockWidget.cpp"
|
||||
"widgets/RectangleWidget.cpp"
|
||||
"widgets/RoomCardWidgetBase.cpp"
|
||||
"widgets/RoomCardBubbleWidget.cpp"
|
||||
"widgets/RoomCardTileWidget.cpp"
|
||||
|
||||
@ -26,6 +26,9 @@ void WidgetConfig::serialize(uint8_t* buf) const {
|
||||
buf[pos++] = bgColor.r; buf[pos++] = bgColor.g; buf[pos++] = bgColor.b;
|
||||
buf[pos++] = bgOpacity;
|
||||
buf[pos++] = borderRadius;
|
||||
buf[pos++] = borderWidth;
|
||||
buf[pos++] = borderColor.r; buf[pos++] = borderColor.g; buf[pos++] = borderColor.b;
|
||||
buf[pos++] = borderOpacity;
|
||||
|
||||
buf[pos++] = shadow.offsetX;
|
||||
buf[pos++] = shadow.offsetY;
|
||||
@ -162,6 +165,9 @@ void WidgetConfig::deserialize(const uint8_t* buf) {
|
||||
bgColor.r = buf[pos++]; bgColor.g = buf[pos++]; bgColor.b = buf[pos++];
|
||||
bgOpacity = buf[pos++];
|
||||
borderRadius = buf[pos++];
|
||||
borderWidth = buf[pos++];
|
||||
borderColor.r = buf[pos++]; borderColor.g = buf[pos++]; borderColor.b = buf[pos++];
|
||||
borderOpacity = buf[pos++];
|
||||
|
||||
shadow.offsetX = static_cast<int8_t>(buf[pos++]);
|
||||
shadow.offsetY = static_cast<int8_t>(buf[pos++]);
|
||||
@ -325,6 +331,9 @@ WidgetConfig WidgetConfig::createLabel(uint8_t id, int16_t x, int16_t y, const c
|
||||
cfg.bgColor = {0, 0, 0};
|
||||
cfg.bgOpacity = 0;
|
||||
cfg.borderRadius = 0;
|
||||
cfg.borderWidth = 0;
|
||||
cfg.borderColor = {255, 255, 255};
|
||||
cfg.borderOpacity = 0;
|
||||
cfg.shadow.enabled = false;
|
||||
// Icon defaults
|
||||
cfg.iconCodepoint = 0;
|
||||
@ -375,6 +384,9 @@ WidgetConfig WidgetConfig::createButton(uint8_t id, int16_t x, int16_t y,
|
||||
cfg.bgColor = {33, 150, 243}; // Blue
|
||||
cfg.bgOpacity = 255;
|
||||
cfg.borderRadius = 8;
|
||||
cfg.borderWidth = 0;
|
||||
cfg.borderColor = {255, 255, 255};
|
||||
cfg.borderOpacity = 0;
|
||||
cfg.shadow.enabled = true;
|
||||
cfg.shadow.offsetX = 2;
|
||||
cfg.shadow.offsetY = 2;
|
||||
|
||||
@ -29,6 +29,7 @@ enum class WidgetType : uint8_t {
|
||||
CHART = 9,
|
||||
CLOCK = 10,
|
||||
ROOMCARD = 11,
|
||||
RECTANGLE = 12,
|
||||
};
|
||||
|
||||
enum class IconPosition : uint8_t {
|
||||
@ -225,6 +226,9 @@ struct WidgetConfig {
|
||||
Color bgColor;
|
||||
uint8_t bgOpacity; // 0-255
|
||||
uint8_t borderRadius;
|
||||
uint8_t borderWidth; // 0-32
|
||||
Color borderColor;
|
||||
uint8_t borderOpacity; // 0-255
|
||||
|
||||
// Shadow
|
||||
ShadowConfig shadow;
|
||||
@ -280,8 +284,8 @@ struct WidgetConfig {
|
||||
TextLineConfig textLines[MAX_TEXTLINES];
|
||||
|
||||
// Serialization size (fixed for NVS storage)
|
||||
// 197 + 4 (iconPositionX/Y) + 1 (subButtonCount) + 1 (subButtonSize) + 1 (subButtonDistance) + 1 (subButtonOpacity) + 1 (cardStyle) + 120 (6 subButtons * 20) = 326
|
||||
static constexpr size_t SERIALIZED_SIZE = 326;
|
||||
// 326 + 5 (borderWidth + borderColor + borderOpacity) = 331
|
||||
static constexpr size_t SERIALIZED_SIZE = 331;
|
||||
|
||||
void serialize(uint8_t* buf) const;
|
||||
void deserialize(const uint8_t* buf);
|
||||
|
||||
@ -1522,6 +1522,12 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
|
||||
|
||||
cJSON_AddNumberToObject(widget, "bgOpacity", w.bgOpacity);
|
||||
cJSON_AddNumberToObject(widget, "radius", w.borderRadius);
|
||||
cJSON_AddNumberToObject(widget, "borderWidth", w.borderWidth);
|
||||
cJSON_AddNumberToObject(widget, "borderOpacity", w.borderOpacity);
|
||||
char borderColorStr[8];
|
||||
snprintf(borderColorStr, sizeof(borderColorStr), "#%02X%02X%02X",
|
||||
w.borderColor.r, w.borderColor.g, w.borderColor.b);
|
||||
cJSON_AddStringToObject(widget, "borderColor", borderColorStr);
|
||||
|
||||
cJSON* shadow = cJSON_AddObjectToObject(widget, "shadow");
|
||||
cJSON_AddBoolToObject(shadow, "enabled", w.shadow.enabled);
|
||||
@ -1801,6 +1807,17 @@ bool WidgetManager::updateConfigFromJson(const char* json) {
|
||||
cJSON* radius = cJSON_GetObjectItem(widget, "radius");
|
||||
if (cJSON_IsNumber(radius)) w.borderRadius = radius->valueint;
|
||||
|
||||
cJSON* borderWidth = cJSON_GetObjectItem(widget, "borderWidth");
|
||||
if (cJSON_IsNumber(borderWidth)) w.borderWidth = borderWidth->valueint;
|
||||
|
||||
cJSON* borderOpacity = cJSON_GetObjectItem(widget, "borderOpacity");
|
||||
if (cJSON_IsNumber(borderOpacity)) w.borderOpacity = borderOpacity->valueint;
|
||||
|
||||
cJSON* borderColor = cJSON_GetObjectItem(widget, "borderColor");
|
||||
if (cJSON_IsString(borderColor)) {
|
||||
w.borderColor = Color::fromHex(parseHexColor(borderColor->valuestring));
|
||||
}
|
||||
|
||||
cJSON* shadow = cJSON_GetObjectItem(widget, "shadow");
|
||||
if (cJSON_IsObject(shadow)) {
|
||||
cJSON* enabled = cJSON_GetObjectItem(shadow, "enabled");
|
||||
|
||||
@ -80,7 +80,6 @@ void ChartWidget::applyStyle() {
|
||||
if (!obj_) return;
|
||||
|
||||
Widget::applyStyle();
|
||||
lv_obj_set_style_border_width(obj_, 0, 0);
|
||||
lv_obj_set_style_pad_all(obj_, 0, 0);
|
||||
|
||||
if (chart_) {
|
||||
|
||||
@ -67,9 +67,17 @@ void ClockWidget::applyStyle() {
|
||||
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);
|
||||
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
||||
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
||||
} else {
|
||||
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_obj_set_style_border_opa(obj_, LV_OPA_COVER, 0);
|
||||
}
|
||||
|
||||
lv_color_t handColor = lv_color_make(
|
||||
config_.textColor.r, config_.textColor.g, config_.textColor.b);
|
||||
|
||||
@ -83,6 +83,17 @@ void IconWidget::applyStyle() {
|
||||
lv_obj_set_style_radius(obj_, config_.borderRadius, 0);
|
||||
}
|
||||
|
||||
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
||||
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
||||
} else {
|
||||
lv_obj_set_style_border_width(obj_, 0, 0);
|
||||
}
|
||||
|
||||
applyShadowStyle();
|
||||
|
||||
// Apply icon style
|
||||
if (iconLabel_ != nullptr) {
|
||||
lv_obj_set_style_text_color(iconLabel_, lv_color_make(
|
||||
|
||||
@ -202,6 +202,15 @@ void LabelWidget::applyStyle() {
|
||||
if (config_.borderRadius > 0) {
|
||||
lv_obj_set_style_radius(obj_, config_.borderRadius, 0);
|
||||
}
|
||||
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
||||
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
||||
} else {
|
||||
lv_obj_set_style_border_width(obj_, 0, 0);
|
||||
}
|
||||
applyShadowStyle();
|
||||
}
|
||||
|
||||
// Apply text style
|
||||
|
||||
@ -25,6 +25,15 @@ void LedWidget::applyStyle() {
|
||||
config_.bgColor.r, config_.bgColor.g, config_.bgColor.b));
|
||||
lv_led_set_brightness(obj_, config_.bgOpacity);
|
||||
|
||||
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
||||
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
||||
} else {
|
||||
lv_obj_set_style_border_width(obj_, 0, 0);
|
||||
}
|
||||
|
||||
// Shadow
|
||||
applyShadowStyle();
|
||||
}
|
||||
|
||||
@ -41,10 +41,17 @@ void PowerFlowWidget::applyStyle() {
|
||||
lv_obj_set_style_bg_opa(obj_, LV_OPA_TRANSP, 0);
|
||||
}
|
||||
|
||||
lv_obj_set_style_border_width(obj_, 1, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.textColor.r, config_.textColor.g, config_.textColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, LV_OPA_20, 0);
|
||||
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
||||
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
||||
} else {
|
||||
lv_obj_set_style_border_width(obj_, 1, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.textColor.r, config_.textColor.g, config_.textColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, LV_OPA_20, 0);
|
||||
}
|
||||
|
||||
if (config_.borderRadius > 0) {
|
||||
lv_obj_set_style_radius(obj_, config_.borderRadius, 0);
|
||||
|
||||
@ -206,10 +206,17 @@ void PowerNodeWidget::applyStyle() {
|
||||
if (ring < 2) ring = 2;
|
||||
if (ring > 12) ring = 12;
|
||||
|
||||
lv_obj_set_style_border_width(obj_, ring, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, config_.bgOpacity, 0);
|
||||
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
||||
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
||||
} else {
|
||||
lv_obj_set_style_border_width(obj_, ring, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, config_.bgOpacity, 0);
|
||||
}
|
||||
|
||||
applyShadowStyle();
|
||||
|
||||
|
||||
26
main/widgets/RectangleWidget.cpp
Normal file
26
main/widgets/RectangleWidget.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
#include "RectangleWidget.hpp"
|
||||
|
||||
RectangleWidget::RectangleWidget(const WidgetConfig& config)
|
||||
: Widget(config)
|
||||
{
|
||||
}
|
||||
|
||||
lv_obj_t* RectangleWidget::create(lv_obj_t* parent) {
|
||||
obj_ = lv_obj_create(parent);
|
||||
if (obj_ == nullptr) 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 : 180,
|
||||
config_.height > 0 ? config_.height : 120);
|
||||
lv_obj_clear_flag(obj_, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_clear_flag(obj_, LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
return obj_;
|
||||
}
|
||||
|
||||
void RectangleWidget::applyStyle() {
|
||||
if (obj_ == nullptr) return;
|
||||
applyCommonStyle();
|
||||
}
|
||||
11
main/widgets/RectangleWidget.hpp
Normal file
11
main/widgets/RectangleWidget.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "Widget.hpp"
|
||||
|
||||
class RectangleWidget : public Widget {
|
||||
public:
|
||||
explicit RectangleWidget(const WidgetConfig& config);
|
||||
|
||||
lv_obj_t* create(lv_obj_t* parent) override;
|
||||
void applyStyle() override;
|
||||
};
|
||||
@ -115,6 +115,14 @@ void RoomCardBubbleWidget::applyStyle() {
|
||||
lv_obj_set_style_shadow_opa(bubble_, 255, 0);
|
||||
}
|
||||
|
||||
if (bubble_ && config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
||||
lv_obj_set_style_border_width(bubble_, config_.borderWidth, 0);
|
||||
lv_obj_set_style_border_color(bubble_, lv_color_hex(config_.borderColor.toLvColor()), 0);
|
||||
lv_obj_set_style_border_opa(bubble_, config_.borderOpacity, 0);
|
||||
} else if (bubble_) {
|
||||
lv_obj_set_style_border_width(bubble_, 0, 0);
|
||||
}
|
||||
|
||||
// Font sizes
|
||||
const lv_font_t* iconFont = Fonts::iconFont(config_.iconSize);
|
||||
const lv_font_t* textFont = Fonts::bySizeIndex(config_.fontSize);
|
||||
|
||||
@ -191,11 +191,17 @@ void RoomCardTileWidget::applyStyle() {
|
||||
|
||||
lv_color_t textColor = lv_color_hex(config_.textColor.toLvColor());
|
||||
|
||||
// Apply border if shadow is enabled (used as accent border)
|
||||
if (config_.shadow.enabled) {
|
||||
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
||||
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_hex(config_.borderColor.toLvColor()), 0);
|
||||
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
||||
} else if (config_.shadow.enabled) {
|
||||
// Backward-compatible accent border behavior
|
||||
lv_obj_set_style_border_width(obj_, 3, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_hex(config_.shadow.color.toLvColor()), 0);
|
||||
lv_obj_set_style_border_opa(obj_, 255, 0);
|
||||
} else {
|
||||
lv_obj_set_style_border_width(obj_, 0, 0);
|
||||
}
|
||||
|
||||
// Room name - large font
|
||||
|
||||
@ -39,5 +39,22 @@ void TabPageWidget::applyStyle() {
|
||||
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);
|
||||
} else {
|
||||
lv_obj_set_style_bg_opa(obj_, LV_OPA_TRANSP, 0);
|
||||
}
|
||||
|
||||
if (config_.borderRadius > 0) {
|
||||
lv_obj_set_style_radius(obj_, config_.borderRadius, 0);
|
||||
}
|
||||
|
||||
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
||||
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
||||
} else {
|
||||
lv_obj_set_style_border_width(obj_, 0, 0);
|
||||
}
|
||||
|
||||
applyShadowStyle();
|
||||
}
|
||||
|
||||
@ -183,6 +183,16 @@ void Widget::applyCommonStyle() {
|
||||
lv_obj_set_style_radius(obj_, config_.borderRadius, 0);
|
||||
}
|
||||
|
||||
// Border
|
||||
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
||||
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
||||
config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0);
|
||||
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
||||
} else {
|
||||
lv_obj_set_style_border_width(obj_, 0, 0);
|
||||
}
|
||||
|
||||
// Shadow
|
||||
applyShadowStyle();
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include "ClockWidget.hpp"
|
||||
#include "RoomCardBubbleWidget.hpp"
|
||||
#include "RoomCardTileWidget.hpp"
|
||||
#include "RectangleWidget.hpp"
|
||||
|
||||
std::unique_ptr<Widget> WidgetFactory::create(const WidgetConfig& config) {
|
||||
if (!config.visible) return nullptr;
|
||||
@ -45,6 +46,8 @@ std::unique_ptr<Widget> WidgetFactory::create(const WidgetConfig& config) {
|
||||
return std::make_unique<RoomCardTileWidget>(config);
|
||||
}
|
||||
return std::make_unique<RoomCardBubbleWidget>(config);
|
||||
case WidgetType::RECTANGLE:
|
||||
return std::make_unique<RectangleWidget>(config);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -42,6 +42,10 @@
|
||||
<span class="text-[13px] font-semibold">Chart</span>
|
||||
<span class="text-[11px] text-muted mt-0.5 block">Verlauf</span>
|
||||
</button>
|
||||
<button class="bg-panel-2 border border-border rounded-xl p-2.5 text-left transition hover:-translate-y-0.5 hover:border-accent" @click="store.addWidget('rectangle')">
|
||||
<span class="text-[13px] font-semibold">Rechteck</span>
|
||||
<span class="text-[11px] text-muted mt-0.5 block">Form</span>
|
||||
</button>
|
||||
<button class="bg-panel-2 border border-border rounded-xl p-2.5 text-left transition hover:-translate-y-0.5 hover:border-accent" @click="store.addWidget('roomcard')">
|
||||
<span class="text-[13px] font-semibold">Room Card</span>
|
||||
<span class="text-[11px] text-muted mt-0.5 block">Raum</span>
|
||||
|
||||
@ -60,6 +60,7 @@ import PowerNodeSettings from './widgets/settings/PowerNodeSettings.vue';
|
||||
import ChartSettings from './widgets/settings/ChartSettings.vue';
|
||||
import ClockSettings from './widgets/settings/ClockSettings.vue';
|
||||
import RoomCardSettings from './widgets/settings/RoomCardSettings.vue';
|
||||
import RectangleSettings from './widgets/settings/RectangleSettings.vue';
|
||||
|
||||
const store = useEditorStore();
|
||||
const w = computed(() => store.selectedWidget);
|
||||
@ -82,7 +83,8 @@ const componentMap = {
|
||||
[WIDGET_TYPES.POWERNODE]: markRaw(PowerNodeSettings),
|
||||
[WIDGET_TYPES.CHART]: markRaw(ChartSettings),
|
||||
[WIDGET_TYPES.CLOCK]: markRaw(ClockSettings),
|
||||
[WIDGET_TYPES.ROOMCARD]: markRaw(RoomCardSettings)
|
||||
[WIDGET_TYPES.ROOMCARD]: markRaw(RoomCardSettings),
|
||||
[WIDGET_TYPES.RECTANGLE]: markRaw(RectangleSettings)
|
||||
};
|
||||
|
||||
const settingsComponent = computed(() => {
|
||||
|
||||
@ -26,6 +26,7 @@ import PowerNodeElement from './widgets/elements/PowerNodeElement.vue';
|
||||
import ChartElement from './widgets/elements/ChartElement.vue';
|
||||
import ClockElement from './widgets/elements/ClockElement.vue';
|
||||
import RoomCardElement from './widgets/elements/RoomCardElement.vue';
|
||||
import RectangleElement from './widgets/elements/RectangleElement.vue';
|
||||
|
||||
const props = defineProps({
|
||||
widget: { type: Object, required: true },
|
||||
@ -47,7 +48,8 @@ const componentMap = {
|
||||
[WIDGET_TYPES.POWERNODE]: markRaw(PowerNodeElement),
|
||||
[WIDGET_TYPES.CHART]: markRaw(ChartElement),
|
||||
[WIDGET_TYPES.CLOCK]: markRaw(ClockElement),
|
||||
[WIDGET_TYPES.ROOMCARD]: markRaw(RoomCardElement)
|
||||
[WIDGET_TYPES.ROOMCARD]: markRaw(RoomCardElement),
|
||||
[WIDGET_TYPES.RECTANGLE]: markRaw(RectangleElement)
|
||||
};
|
||||
|
||||
const widgetComponent = computed(() => {
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
import { useEditorStore } from '../../../stores/editor';
|
||||
import { fontSizes, ICON_POSITIONS } from '../../../constants';
|
||||
import { getBaseStyle, justifyForAlign, getShadowStyle } from '../shared/utils';
|
||||
import { getBaseStyle, justifyForAlign, getShadowStyle, getBorderStyle, clamp, hexToRgba } from '../shared/utils';
|
||||
|
||||
// Lazy import to avoid circular dependency
|
||||
const WidgetElement = defineAsyncComponent(() => import('../../WidgetElement.vue'));
|
||||
@ -118,13 +118,15 @@ const computedStyle = computed(() => {
|
||||
|
||||
const style = getBaseStyle(w, s);
|
||||
|
||||
style.background = w.bgColor;
|
||||
const alpha = clamp((w.bgOpacity ?? 255) / 255, 0, 1);
|
||||
style.background = hexToRgba(w.bgColor, alpha);
|
||||
style.borderRadius = `${w.radius * s}px`;
|
||||
style.display = 'flex';
|
||||
style.alignItems = 'center';
|
||||
style.justifyContent = justifyForAlign(textAlign.value);
|
||||
style.fontWeight = '600';
|
||||
|
||||
Object.assign(style, getBorderStyle(w, s));
|
||||
Object.assign(style, getShadowStyle(w, s));
|
||||
|
||||
return style;
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { getBaseStyle, clamp, hexToRgba } from '../shared/utils';
|
||||
import { getBaseStyle, clamp, hexToRgba, getBorderStyle } from '../shared/utils';
|
||||
|
||||
const props = defineProps({
|
||||
widget: { type: Object, required: true },
|
||||
@ -60,6 +60,8 @@ const computedStyle = computed(() => {
|
||||
style.borderRadius = `${w.radius * s}px`;
|
||||
}
|
||||
|
||||
Object.assign(style, getBorderStyle(w, s));
|
||||
|
||||
style.padding = `${12 * s}px`;
|
||||
|
||||
return style;
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
@touchstart.stop="$emit('drag-start', { id: widget.id, event: $event })"
|
||||
@click.stop="$emit('select')"
|
||||
>
|
||||
<div class="relative w-full h-full rounded-full border-2 box-border flex items-center justify-center overflow-hidden" :style="{ borderColor: widget.textColor }">
|
||||
<div class="relative w-full h-full rounded-full box-border flex items-center justify-center overflow-hidden">
|
||||
<!-- Center Dot -->
|
||||
<div class="absolute w-2 h-2 rounded-full z-10" :style="{ backgroundColor: widget.textColor }"></div>
|
||||
<!-- Hour Hand -->
|
||||
@ -33,7 +33,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { getBaseStyle, clamp, hexToRgba } from '../shared/utils';
|
||||
import { getBaseStyle, clamp, hexToRgba, getBorderStyle } from '../shared/utils';
|
||||
|
||||
const props = defineProps({
|
||||
widget: { type: Object, required: true },
|
||||
@ -60,6 +60,12 @@ const computedStyle = computed(() => {
|
||||
style.borderRadius = '50%';
|
||||
}
|
||||
|
||||
if ((w.borderWidth || 0) > 0 && (w.borderOpacity ?? 0) > 0) {
|
||||
Object.assign(style, getBorderStyle(w, s));
|
||||
} else {
|
||||
style.border = `${2 * s}px solid ${w.textColor}`;
|
||||
}
|
||||
|
||||
return style;
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { fontSizes } from '../../../constants';
|
||||
import { getBaseStyle, clamp, hexToRgba } from '../shared/utils';
|
||||
import { getBaseStyle, clamp, hexToRgba, getBorderStyle } from '../shared/utils';
|
||||
|
||||
const props = defineProps({
|
||||
widget: { type: Object, required: true },
|
||||
@ -70,6 +70,8 @@ const computedStyle = computed(() => {
|
||||
style.borderRadius = `${w.radius * s}px`;
|
||||
}
|
||||
|
||||
Object.assign(style, getBorderStyle(w, s));
|
||||
|
||||
return style;
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { fontSizes, ICON_POSITIONS } from '../../../constants';
|
||||
import { getBaseStyle, justifyForAlign, textAlignCss, clamp, hexToRgba } from '../shared/utils';
|
||||
import { getBaseStyle, justifyForAlign, textAlignCss, clamp, hexToRgba, getBorderStyle } from '../shared/utils';
|
||||
|
||||
const props = defineProps({
|
||||
widget: { type: Object, required: true },
|
||||
@ -99,6 +99,8 @@ const computedStyle = computed(() => {
|
||||
style.background = hexToRgba(w.bgColor, alpha);
|
||||
}
|
||||
|
||||
Object.assign(style, getBorderStyle(w, s));
|
||||
|
||||
style.display = 'flex';
|
||||
style.alignItems = 'center';
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { getBaseStyle, clamp, hexToRgba } from '../shared/utils';
|
||||
import { getBaseStyle, clamp, hexToRgba, getBorderStyle } from '../shared/utils';
|
||||
|
||||
const props = defineProps({
|
||||
widget: { type: Object, required: true },
|
||||
@ -58,6 +58,8 @@ const computedStyle = computed(() => {
|
||||
style.boxShadow = 'inset 0 0 0 1px rgba(255,255,255,0.12)';
|
||||
}
|
||||
|
||||
Object.assign(style, getBorderStyle(w, s));
|
||||
|
||||
return style;
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -67,7 +67,7 @@
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
import { useEditorStore } from '../../../stores/editor';
|
||||
import { WIDGET_TYPES } from '../../../constants';
|
||||
import { getBaseStyle, getShadowStyle, clamp, hexToRgba } from '../shared/utils';
|
||||
import { getBaseStyle, getShadowStyle, getBorderStyle, clamp, hexToRgba } from '../shared/utils';
|
||||
|
||||
const WidgetElement = defineAsyncComponent(() => import('../../WidgetElement.vue'));
|
||||
|
||||
@ -192,7 +192,11 @@ const computedStyle = computed(() => {
|
||||
const style = getBaseStyle(w, s);
|
||||
style.borderRadius = `${w.radius * s}px`;
|
||||
style.overflow = 'hidden';
|
||||
style.border = `1px solid ${hexToRgba('#94a3b8', 0.35)}`;
|
||||
if ((w.borderWidth || 0) > 0 && (w.borderOpacity ?? 0) > 0) {
|
||||
Object.assign(style, getBorderStyle(w, s));
|
||||
} else {
|
||||
style.border = `1px solid ${hexToRgba('#94a3b8', 0.35)}`;
|
||||
}
|
||||
|
||||
Object.assign(style, getShadowStyle(w, s));
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
import { computed } from 'vue';
|
||||
import { useEditorStore } from '../../../stores/editor';
|
||||
import { fontSizes } from '../../../constants';
|
||||
import { getBaseStyle, clamp, hexToRgba, splitPowerNodeText } from '../shared/utils';
|
||||
import { getBaseStyle, getBorderStyle, clamp, hexToRgba, splitPowerNodeText } from '../shared/utils';
|
||||
|
||||
const props = defineProps({
|
||||
widget: { type: Object, required: true },
|
||||
@ -97,7 +97,11 @@ const computedStyle = computed(() => {
|
||||
style.justifyContent = 'center';
|
||||
style.borderRadius = '999px';
|
||||
style.background = hexToRgba('#ffffff', 0.96);
|
||||
style.border = `${ring}px solid ${hexToRgba(w.bgColor, ringAlpha)}`;
|
||||
if ((w.borderWidth || 0) > 0 && (w.borderOpacity ?? 0) > 0) {
|
||||
Object.assign(style, getBorderStyle(w, s));
|
||||
} else {
|
||||
style.border = `${ring}px solid ${hexToRgba(w.bgColor, ringAlpha)}`;
|
||||
}
|
||||
style.textAlign = 'center';
|
||||
|
||||
if (w.shadow && w.shadow.enabled) {
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div
|
||||
class="z-[1] select-none touch-none"
|
||||
:class="selected ? 'outline outline-2 outline-accent outline-offset-2' : ''"
|
||||
:style="computedStyle"
|
||||
@mousedown.stop="$emit('drag-start', { id: widget.id, event: $event })"
|
||||
@touchstart.stop="$emit('drag-start', { id: widget.id, event: $event })"
|
||||
@click.stop="$emit('select')"
|
||||
>
|
||||
<div
|
||||
v-if="selected"
|
||||
class="absolute -right-1.5 -bottom-1.5 w-3.5 h-3.5 rounded-[4px] bg-accent border-[2px] border-[#1b1308] shadow-[0_4px_12px_rgba(0,0,0,0.35)] cursor-se-resize z-10"
|
||||
data-resize-handle
|
||||
@mousedown.stop="$emit('resize-start', { id: widget.id, event: $event })"
|
||||
@touchstart.stop="$emit('resize-start', { id: widget.id, event: $event })"
|
||||
>
|
||||
<span class="absolute right-[2px] bottom-[2px] w-[6px] h-[6px] border-r-2 border-b-2 border-[#1b1308a6] rounded-[2px]"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { getBaseStyle, getShadowStyle, clamp, hexToRgba } from '../shared/utils';
|
||||
|
||||
const props = defineProps({
|
||||
widget: { type: Object, required: true },
|
||||
scale: { type: Number, default: 1 },
|
||||
selected: { type: Boolean, default: false }
|
||||
});
|
||||
|
||||
defineEmits(['select', 'drag-start', 'resize-start']);
|
||||
|
||||
const computedStyle = computed(() => {
|
||||
const w = props.widget;
|
||||
const s = props.scale;
|
||||
const style = getBaseStyle(w, s);
|
||||
|
||||
const bgAlpha = clamp((w.bgOpacity ?? 255) / 255, 0, 1);
|
||||
style.background = hexToRgba(w.bgColor || '#000000', bgAlpha);
|
||||
style.borderRadius = `${(w.radius || 0) * s}px`;
|
||||
|
||||
const borderWidth = Math.max(0, (w.borderWidth || 0) * s);
|
||||
const borderAlpha = clamp((w.borderOpacity ?? 0) / 255, 0, 1);
|
||||
if (borderWidth > 0 && borderAlpha > 0) {
|
||||
style.border = `${borderWidth}px solid ${hexToRgba(w.borderColor || '#ffffff', borderAlpha)}`;
|
||||
} else {
|
||||
style.border = 'none';
|
||||
}
|
||||
|
||||
Object.assign(style, getShadowStyle(w, s));
|
||||
return style;
|
||||
});
|
||||
</script>
|
||||
@ -78,7 +78,7 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { fontSizes, iconFontSizes } from '../../../constants';
|
||||
import { getBaseStyle, clamp, hexToRgba } from '../shared/utils';
|
||||
import { getBaseStyle, getBorderStyle, clamp, hexToRgba } from '../shared/utils';
|
||||
|
||||
const props = defineProps({
|
||||
widget: { type: Object, required: true },
|
||||
@ -135,14 +135,10 @@ function getTextLineIconSize(line) {
|
||||
|
||||
const roomCardTileContainerStyle = computed(() => {
|
||||
const alpha = (props.widget.bgOpacity ?? 255) / 255;
|
||||
const style = {
|
||||
return {
|
||||
backgroundColor: hexToRgba(props.widget.bgColor || '#333333', alpha),
|
||||
borderRadius: (props.widget.radius || 16) + 'px',
|
||||
};
|
||||
if (props.widget.shadow?.enabled) {
|
||||
style.border = `3px solid ${props.widget.shadow.color || '#ff6b6b'}`;
|
||||
}
|
||||
return style;
|
||||
});
|
||||
|
||||
const decorIconStyle = computed(() => {
|
||||
@ -194,6 +190,9 @@ const roomCardBubbleStyle = computed(() => {
|
||||
top: `${top}px`,
|
||||
backgroundColor: hexToRgba(props.widget.bgColor, alpha),
|
||||
color: props.widget.textColor,
|
||||
border: ((props.widget.borderWidth || 0) > 0 && (props.widget.borderOpacity ?? 0) > 0)
|
||||
? `${(props.widget.borderWidth || 0) * s}px solid ${hexToRgba(props.widget.borderColor || '#ffffff', clamp((props.widget.borderOpacity ?? 0) / 255, 0, 1))}`
|
||||
: 'none',
|
||||
boxShadow: props.widget.shadow?.enabled
|
||||
? `${(props.widget.shadow.x || 0) * s}px ${(props.widget.shadow.y || 0) * s}px ${(props.widget.shadow.blur || 0) * s}px ${hexToRgba(props.widget.shadow.color || '#000000', 0.3)}`
|
||||
: '0 4px 12px rgba(0,0,0,0.15)'
|
||||
@ -275,7 +274,9 @@ const computedStyle = computed(() => {
|
||||
style.background = hexToRgba(w.bgColor || '#333333', alpha);
|
||||
style.borderRadius = `${(w.radius || 16) * s}px`;
|
||||
style.overflow = 'hidden';
|
||||
if (w.shadow && w.shadow.enabled) {
|
||||
if ((w.borderWidth || 0) > 0 && (w.borderOpacity ?? 0) > 0) {
|
||||
Object.assign(style, getBorderStyle(w, s));
|
||||
} else if (w.shadow && w.shadow.enabled) {
|
||||
style.border = `3px solid ${w.shadow.color || '#ff6b6b'}`;
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
import { useEditorStore } from '../../../stores/editor';
|
||||
import { fontSizes } from '../../../constants';
|
||||
import { clamp, hexToRgba, getBorderStyle } from '../shared/utils';
|
||||
|
||||
const WidgetElement = defineAsyncComponent(() => import('../../WidgetElement.vue'));
|
||||
|
||||
@ -45,7 +46,7 @@ const computedStyle = computed(() => {
|
||||
const w = props.widget;
|
||||
const s = props.scale;
|
||||
|
||||
return {
|
||||
const style = {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
@ -57,5 +58,15 @@ const computedStyle = computed(() => {
|
||||
userSelect: 'none',
|
||||
touchAction: 'none'
|
||||
};
|
||||
|
||||
if ((w.bgOpacity ?? 0) > 0) {
|
||||
style.background = hexToRgba(w.bgColor, clamp((w.bgOpacity ?? 255) / 255, 0, 1));
|
||||
}
|
||||
if ((w.radius || 0) > 0) {
|
||||
style.borderRadius = `${(w.radius || 0) * s}px`;
|
||||
}
|
||||
Object.assign(style, getBorderStyle(w, s));
|
||||
|
||||
return style;
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useEditorStore } from '../../../stores/editor';
|
||||
import { getBaseStyle } from '../shared/utils';
|
||||
import { getBaseStyle, getBorderStyle, clamp, hexToRgba } from '../shared/utils';
|
||||
import TabPageElement from './TabPageElement.vue';
|
||||
|
||||
const props = defineProps({
|
||||
@ -92,8 +92,9 @@ const computedStyle = computed(() => {
|
||||
const s = props.scale;
|
||||
|
||||
const style = getBaseStyle(w, s);
|
||||
style.background = w.bgColor;
|
||||
style.background = hexToRgba(w.bgColor, clamp((w.bgOpacity ?? 255) / 255, 0, 1));
|
||||
style.borderRadius = `${w.radius * s}px`;
|
||||
Object.assign(style, getBorderStyle(w, s));
|
||||
|
||||
return style;
|
||||
});
|
||||
|
||||
@ -18,6 +18,10 @@
|
||||
<div :class="rowClass"><label :class="labelClass">Hintergrund</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Radius</label><input :class="inputClass" type="number" v-model.number="widget.radius"></div>
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
|
||||
<!-- Shadow -->
|
||||
<h4 :class="headingClass">Schatten</h4>
|
||||
|
||||
@ -45,6 +45,10 @@
|
||||
<div :class="rowClass"><label :class="labelClass">Hintergrund</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Radius</label><input :class="inputClass" type="number" v-model.number="widget.radius"></div>
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
|
||||
<!-- Shadow -->
|
||||
<h4 :class="headingClass">Schatten</h4>
|
||||
|
||||
@ -6,6 +6,10 @@
|
||||
<div :class="rowClass"><label :class="labelClass">Zeiger</label><input :class="colorInputClass" type="color" v-model="widget.textColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Radius</label><input :class="inputClass" type="number" v-model.number="widget.radius"></div>
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
|
||||
<!-- Shadow -->
|
||||
<h4 :class="headingClass">Schatten</h4>
|
||||
|
||||
@ -38,6 +38,10 @@
|
||||
<div :class="rowClass"><label :class="labelClass">Hintergrund</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Radius</label><input :class="inputClass" type="number" v-model.number="widget.radius"></div>
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -70,6 +70,10 @@
|
||||
<div :class="rowClass"><label :class="labelClass">Hintergrund</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Radius</label><input :class="inputClass" type="number" v-model.number="widget.radius"></div>
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
|
||||
<!-- Shadow -->
|
||||
<h4 :class="headingClass">Schatten</h4>
|
||||
|
||||
@ -23,6 +23,10 @@
|
||||
<h4 :class="headingClass">Stil</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Helligkeit</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
|
||||
<!-- Glow -->
|
||||
<h4 :class="headingClass">Glow</h4>
|
||||
|
||||
@ -67,6 +67,10 @@
|
||||
<div :class="rowClass"><label :class="labelClass">Hintergrund</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Radius</label><input :class="inputClass" type="number" v-model.number="widget.radius"></div>
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
|
||||
<!-- Shadow -->
|
||||
<h4 :class="headingClass">Schatten</h4>
|
||||
|
||||
@ -150,6 +150,10 @@
|
||||
<div :class="rowClass"><label :class="labelClass">Textfarbe</label><input :class="colorInputClass" type="color" v-model="widget.textColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Ringfarbe</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Ring Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
|
||||
<!-- Glow -->
|
||||
<h4 :class="headingClass">Glow</h4>
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<h4 :class="headingClass">Fuellung</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Radius</label><input :class="inputClass" type="number" min="0" max="200" v-model.number="widget.radius"></div>
|
||||
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
|
||||
<h4 :class="headingClass">Schatten</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Aktiv</label><input class="accent-[var(--accent)]" type="checkbox" v-model="widget.shadow.enabled"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">X</label><input :class="inputClass" type="number" v-model.number="widget.shadow.x"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Y</label><input :class="inputClass" type="number" v-model.number="widget.shadow.y"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Blur</label><input :class="inputClass" type="number" min="0" max="40" v-model.number="widget.shadow.blur"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Spread</label><input :class="inputClass" type="number" min="0" max="20" v-model.number="widget.shadow.spread"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.shadow.color"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { rowClass, labelClass, inputClass, headingClass, colorInputClass } from '../shared/styles';
|
||||
|
||||
defineProps({
|
||||
widget: { type: Object, required: true }
|
||||
});
|
||||
</script>
|
||||
@ -217,6 +217,10 @@
|
||||
<div :class="rowClass"><label :class="labelClass">Textfarbe</label><input :class="colorInputClass" type="color" v-model="widget.textColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Hintergrund</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
|
||||
<!-- Shadow -->
|
||||
<h4 :class="headingClass">Schatten</h4>
|
||||
|
||||
@ -10,6 +10,10 @@
|
||||
<div :class="rowClass"><label :class="labelClass">Hintergrund</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Radius</label><input :class="inputClass" type="number" v-model.number="widget.radius"></div>
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
|
||||
<!-- Shadow -->
|
||||
<h4 :class="headingClass">Schatten</h4>
|
||||
|
||||
@ -21,6 +21,10 @@
|
||||
<div :class="rowClass"><label :class="labelClass">Hintergrund</label><input :class="colorInputClass" type="color" v-model="widget.bgColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.bgOpacity"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Radius</label><input :class="inputClass" type="number" v-model.number="widget.radius"></div>
|
||||
<h4 :class="headingClass">Rand</h4>
|
||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" min="0" max="32" v-model.number="widget.borderWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Farbe</label><input :class="colorInputClass" type="color" v-model="widget.borderColor"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Deckkraft</label><input :class="inputClass" type="number" min="0" max="255" v-model.number="widget.borderOpacity"></div>
|
||||
|
||||
<!-- Shadow -->
|
||||
<h4 :class="headingClass">Schatten</h4>
|
||||
|
||||
@ -48,6 +48,18 @@ export function getShadowStyle(widget, scale) {
|
||||
};
|
||||
}
|
||||
|
||||
// Border style helper (0-255 opacity, width in widget px)
|
||||
export function getBorderStyle(widget, scale) {
|
||||
const width = Math.max(0, (widget.borderWidth || 0) * scale);
|
||||
const alpha = clamp((widget.borderOpacity ?? 0) / 255, 0, 1);
|
||||
if (width <= 0 || alpha <= 0) {
|
||||
return { border: 'none' };
|
||||
}
|
||||
return {
|
||||
border: `${width}px solid ${hexToRgba(widget.borderColor || '#ffffff', alpha)}`
|
||||
};
|
||||
}
|
||||
|
||||
// PowerNode text splitting
|
||||
export function splitPowerNodeText(text) {
|
||||
if (typeof text !== 'string') return { label: '', value: '' };
|
||||
|
||||
@ -14,7 +14,8 @@ export const WIDGET_TYPES = {
|
||||
POWERLINK: 8,
|
||||
CHART: 9,
|
||||
CLOCK: 10,
|
||||
ROOMCARD: 11
|
||||
ROOMCARD: 11,
|
||||
RECTANGLE: 12
|
||||
};
|
||||
|
||||
export const ICON_POSITIONS = {
|
||||
@ -48,7 +49,8 @@ export const TYPE_KEYS = {
|
||||
8: 'powerlink',
|
||||
9: 'chart',
|
||||
10: 'clock',
|
||||
11: 'roomcard'
|
||||
11: 'roomcard',
|
||||
12: 'rectangle'
|
||||
};
|
||||
|
||||
export const TYPE_LABELS = {
|
||||
@ -63,7 +65,8 @@ export const TYPE_LABELS = {
|
||||
powerlink: 'Power Link',
|
||||
chart: 'Chart',
|
||||
clock: 'Uhr (Analog)',
|
||||
roomcard: 'Room Card'
|
||||
roomcard: 'Room Card',
|
||||
rectangle: 'Rechteck'
|
||||
};
|
||||
|
||||
|
||||
@ -107,7 +110,8 @@ export const sourceOptions = {
|
||||
powerlink: [0, 1, 3, 5, 6, 7],
|
||||
chart: [1, 3, 5, 6, 7],
|
||||
clock: [11],
|
||||
roomcard: [0, 1, 3, 5, 6, 7] // Temperature sources
|
||||
roomcard: [0, 1, 3, 5, 6, 7], // Temperature sources
|
||||
rectangle: [0]
|
||||
};
|
||||
|
||||
export const chartPeriods = [
|
||||
@ -462,5 +466,32 @@ export const WIDGET_DEFAULTS = {
|
||||
cardStyle: 0, // 0=Bubble (round), 1=Tile (rectangular)
|
||||
subButtons: [],
|
||||
textLines: [] // Variable text lines with icon, text, textSrc, knxAddr, fontSize
|
||||
},
|
||||
rectangle: {
|
||||
w: 220,
|
||||
h: 140,
|
||||
text: '',
|
||||
textSrc: 0,
|
||||
fontSize: 1,
|
||||
textAlign: TEXT_ALIGNS.CENTER,
|
||||
textColor: '#FFFFFF',
|
||||
bgColor: '#2E7DD1',
|
||||
bgOpacity: 180,
|
||||
radius: 14,
|
||||
borderWidth: 2,
|
||||
borderColor: '#FFFFFF',
|
||||
borderOpacity: 180,
|
||||
shadow: { enabled: false, x: 2, y: 2, blur: 10, spread: 0, color: '#000000' },
|
||||
isToggle: false,
|
||||
knxAddrWrite: 0,
|
||||
knxAddr: 0,
|
||||
action: 0,
|
||||
targetScreen: 0,
|
||||
iconCodepoint: 0,
|
||||
iconPosition: 0,
|
||||
iconSize: 1,
|
||||
iconGap: 0,
|
||||
iconPositionX: 0,
|
||||
iconPositionY: 0
|
||||
}
|
||||
};
|
||||
|
||||
@ -160,6 +160,9 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
bgColor: defaults.bgColor,
|
||||
bgOpacity: 0,
|
||||
radius: 0,
|
||||
borderWidth: 0,
|
||||
borderColor: defaults.borderColor || '#ffffff',
|
||||
borderOpacity: 0,
|
||||
shadow: { ...defaults.shadow, enabled: false },
|
||||
isToggle: false,
|
||||
knxAddrWrite: 0,
|
||||
@ -337,6 +340,7 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
case 'chart': typeValue = WIDGET_TYPES.CHART; break;
|
||||
case 'clock': typeValue = WIDGET_TYPES.CLOCK; break;
|
||||
case 'roomcard': typeValue = WIDGET_TYPES.ROOMCARD; break;
|
||||
case 'rectangle': typeValue = WIDGET_TYPES.RECTANGLE; break;
|
||||
default: typeValue = WIDGET_TYPES.LABEL;
|
||||
}
|
||||
|
||||
@ -402,6 +406,9 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
bgColor: defaults.bgColor,
|
||||
bgOpacity: defaults.bgOpacity,
|
||||
radius: defaults.radius,
|
||||
borderWidth: defaults.borderWidth ?? 0,
|
||||
borderColor: defaults.borderColor || '#ffffff',
|
||||
borderOpacity: defaults.borderOpacity ?? 0,
|
||||
shadow: { ...defaults.shadow },
|
||||
isToggle: defaults.isToggle,
|
||||
knxAddrWrite: defaults.knxAddrWrite,
|
||||
@ -462,6 +469,9 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
bgColor: labelDefaults.bgColor,
|
||||
bgOpacity: 0,
|
||||
radius: 0,
|
||||
borderWidth: 0,
|
||||
borderColor: labelDefaults.borderColor || '#ffffff',
|
||||
borderOpacity: 0,
|
||||
shadow: { ...labelDefaults.shadow, enabled: false },
|
||||
isToggle: false,
|
||||
knxAddrWrite: 0,
|
||||
|
||||
@ -20,6 +20,7 @@ export function minSizeFor(widget) {
|
||||
if (key === 'powerlink') return { w: 1, h: 1 };
|
||||
if (key === 'chart') return { w: 160, h: 120 };
|
||||
if (key === 'roomcard') return { w: 120, h: 120 };
|
||||
if (key === 'rectangle') return { w: 40, h: 30 };
|
||||
return { w: 40, h: 20 };
|
||||
}
|
||||
|
||||
@ -94,6 +95,17 @@ export function normalizeWidget(w, nextWidgetIdRef) {
|
||||
});
|
||||
}
|
||||
|
||||
// Border style defaults (used by rectangle widget, harmless for others)
|
||||
if (w.borderWidth === undefined || w.borderWidth === null) {
|
||||
w.borderWidth = defaults.borderWidth ?? 0;
|
||||
}
|
||||
if (!w.borderColor) {
|
||||
w.borderColor = defaults.borderColor || '#ffffff';
|
||||
}
|
||||
if (w.borderOpacity === undefined || w.borderOpacity === null) {
|
||||
w.borderOpacity = defaults.borderOpacity ?? 0;
|
||||
}
|
||||
|
||||
if (defaults.chart) {
|
||||
const maxSeries = 3;
|
||||
if (!w.chart) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user