diff --git a/.cache/clangd/index/RoomCardWidgetBase.cpp.E82CB8390DB7EE04.idx b/.cache/clangd/index/RoomCardWidgetBase.cpp.E82CB8390DB7EE04.idx index 428d613..41e8598 100644 Binary files a/.cache/clangd/index/RoomCardWidgetBase.cpp.E82CB8390DB7EE04.idx and b/.cache/clangd/index/RoomCardWidgetBase.cpp.E82CB8390DB7EE04.idx differ diff --git a/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx b/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx index 6514f20..34fe364 100644 Binary files a/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx and b/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx differ diff --git a/main/WidgetConfig.cpp b/main/WidgetConfig.cpp index aba8c5d..24f0c3a 100644 --- a/main/WidgetConfig.cpp +++ b/main/WidgetConfig.cpp @@ -131,12 +131,18 @@ void WidgetConfig::serialize(uint8_t* buf) const { buf[pos++] = (sb.knxAddrRead >> 8) & 0xFF; buf[pos++] = sb.knxAddrWrite & 0xFF; buf[pos++] = (sb.knxAddrWrite >> 8) & 0xFF; - buf[pos++] = sb.colorOn.r; - buf[pos++] = sb.colorOn.g; - buf[pos++] = sb.colorOn.b; - buf[pos++] = sb.colorOff.r; - buf[pos++] = sb.colorOff.g; - buf[pos++] = sb.colorOff.b; + buf[pos++] = sb.bgColorOn.r; + buf[pos++] = sb.bgColorOn.g; + buf[pos++] = sb.bgColorOn.b; + buf[pos++] = sb.bgColorOff.r; + buf[pos++] = sb.bgColorOff.g; + buf[pos++] = sb.bgColorOff.b; + buf[pos++] = sb.iconColorOn.r; + buf[pos++] = sb.iconColorOn.g; + buf[pos++] = sb.iconColorOn.b; + buf[pos++] = sb.iconColorOff.r; + buf[pos++] = sb.iconColorOff.g; + buf[pos++] = sb.iconColorOff.b; buf[pos++] = static_cast(sb.position); buf[pos++] = static_cast(sb.action); buf[pos++] = sb.targetScreen; @@ -307,7 +313,7 @@ void WidgetConfig::deserialize(const uint8_t* buf) { for (size_t i = 0; i < MAX_SUBBUTTONS; ++i) { SubButtonConfig& sb = subButtons[i]; - if (pos + 24 <= SERIALIZED_SIZE) { + if (pos + 30 <= SERIALIZED_SIZE) { sb.iconCodepointOff = buf[pos] | (buf[pos + 1] << 8) | (buf[pos + 2] << 16) | (buf[pos + 3] << 24); pos += 4; @@ -316,12 +322,18 @@ void WidgetConfig::deserialize(const uint8_t* buf) { pos += 4; sb.knxAddrRead = buf[pos] | (buf[pos + 1] << 8); pos += 2; sb.knxAddrWrite = buf[pos] | (buf[pos + 1] << 8); pos += 2; - sb.colorOn.r = buf[pos++]; - sb.colorOn.g = buf[pos++]; - sb.colorOn.b = buf[pos++]; - sb.colorOff.r = buf[pos++]; - sb.colorOff.g = buf[pos++]; - sb.colorOff.b = buf[pos++]; + sb.bgColorOn.r = buf[pos++]; + sb.bgColorOn.g = buf[pos++]; + sb.bgColorOn.b = buf[pos++]; + sb.bgColorOff.r = buf[pos++]; + sb.bgColorOff.g = buf[pos++]; + sb.bgColorOff.b = buf[pos++]; + sb.iconColorOn.r = buf[pos++]; + sb.iconColorOn.g = buf[pos++]; + sb.iconColorOn.b = buf[pos++]; + sb.iconColorOff.r = buf[pos++]; + sb.iconColorOff.g = buf[pos++]; + sb.iconColorOff.b = buf[pos++]; sb.position = static_cast(buf[pos++]); sb.action = static_cast(buf[pos++]); sb.targetScreen = buf[pos++]; @@ -566,6 +578,8 @@ void GuiConfig::clear() { knxDateAddress = 0; knxDateTimeAddress = 0; knxNightModeAddress = 0; + screenAnimType = ScreenAnimType::FADE; + screenAnimDuration = 300; // Default 300ms for (size_t i = 0; i < MAX_SCREENS; i++) { screens[i].clear(static_cast(i), nullptr); } diff --git a/main/WidgetConfig.hpp b/main/WidgetConfig.hpp index de52883..d232e7e 100644 --- a/main/WidgetConfig.hpp +++ b/main/WidgetConfig.hpp @@ -169,20 +169,22 @@ enum class SubButtonAction : uint8_t { NAVIGATE = 1, // Navigate to screen }; -// Sub-button configuration for RoomCard (24 bytes) +// Sub-button configuration for RoomCard (30 bytes) struct SubButtonConfig { uint32_t iconCodepointOff; // 4 bytes - Icon codepoint when OFF uint32_t iconCodepointOn; // 4 bytes - Icon codepoint when ON (0 = use iconCodepointOff) uint16_t knxAddrRead; // 2 bytes - KNX address to read status uint16_t knxAddrWrite; // 2 bytes - KNX address to write on click - Color colorOn; // 3 bytes - Color when ON - Color colorOff; // 3 bytes - Color when OFF + Color bgColorOn; // 3 bytes - Background color when ON + Color bgColorOff; // 3 bytes - Background color when OFF + Color iconColorOn; // 3 bytes - Icon color when ON + Color iconColorOff; // 3 bytes - Icon color when OFF SubButtonPosition position; // 1 byte - Position around bubble SubButtonAction action; // 1 byte - Action type uint8_t targetScreen; // 1 byte - Target screen for navigate bool enabled; // 1 byte - Is this sub-button active? uint8_t _padding[2]; // 2 bytes - Alignment padding - // Total: 24 bytes per SubButton + // Total: 30 bytes per SubButton }; // Text line configuration for RoomCard (24 bytes) @@ -297,8 +299,8 @@ struct WidgetConfig { uint8_t arcValueFontSize; // Center value font size index // Serialization size (fixed for NVS storage) - // 345 + 24 (6 subbuttons * 4 bytes for iconCodepointOn) = 369 - static constexpr size_t SERIALIZED_SIZE = 369; + // 369 + 36 (6 subbuttons * 6 bytes for icon colors) = 405 + static constexpr size_t SERIALIZED_SIZE = 405; void serialize(uint8_t* buf) const; void deserialize(const uint8_t* buf); @@ -341,6 +343,20 @@ struct ScreenConfig { const WidgetConfig* findWidget(uint8_t id) const; }; +// Screen transition animation types +enum class ScreenAnimType : uint8_t { + NONE = 0, + FADE = 1, + SLIDE_LEFT = 2, + SLIDE_RIGHT = 3, + SLIDE_UP = 4, + SLIDE_DOWN = 5, + OVER_LEFT = 6, + OVER_RIGHT = 7, + OVER_UP = 8, + OVER_DOWN = 9, +}; + struct GuiConfig { uint8_t screenCount; ScreenConfig screens[MAX_SCREENS]; @@ -353,6 +369,10 @@ struct GuiConfig { uint16_t knxDateTimeAddress; uint16_t knxNightModeAddress; + // Screen transition animation + ScreenAnimType screenAnimType; + uint16_t screenAnimDuration; // Duration in ms (0 = default 300ms) + void clear(); ScreenConfig* findScreen(uint8_t id); const ScreenConfig* findScreen(uint8_t id) const; diff --git a/main/WidgetManager.cpp b/main/WidgetManager.cpp index e9890c0..36812f3 100644 --- a/main/WidgetManager.cpp +++ b/main/WidgetManager.cpp @@ -359,6 +359,23 @@ void WidgetManager::applyScreen(uint8_t screenId) { esp_lv_adapter_unlock(); } +// Helper to convert ScreenAnimType to LVGL animation type (LVGL 9.x API) +static lv_screen_load_anim_t screenAnimToLvgl(ScreenAnimType type) { + switch (type) { + case ScreenAnimType::NONE: return LV_SCREEN_LOAD_ANIM_NONE; + case ScreenAnimType::FADE: return LV_SCREEN_LOAD_ANIM_FADE_IN; + case ScreenAnimType::SLIDE_LEFT: return LV_SCREEN_LOAD_ANIM_MOVE_LEFT; + case ScreenAnimType::SLIDE_RIGHT: return LV_SCREEN_LOAD_ANIM_MOVE_RIGHT; + case ScreenAnimType::SLIDE_UP: return LV_SCREEN_LOAD_ANIM_MOVE_TOP; + case ScreenAnimType::SLIDE_DOWN: return LV_SCREEN_LOAD_ANIM_MOVE_BOTTOM; + case ScreenAnimType::OVER_LEFT: return LV_SCREEN_LOAD_ANIM_OVER_LEFT; + case ScreenAnimType::OVER_RIGHT: return LV_SCREEN_LOAD_ANIM_OVER_RIGHT; + case ScreenAnimType::OVER_UP: return LV_SCREEN_LOAD_ANIM_OVER_TOP; + case ScreenAnimType::OVER_DOWN: return LV_SCREEN_LOAD_ANIM_OVER_BOTTOM; + default: return LV_SCREEN_LOAD_ANIM_FADE_IN; + } +} + void WidgetManager::applyScreenLocked(uint8_t screenId) { ESP_LOGI(TAG, "applyScreen(%d) - start", screenId); @@ -375,35 +392,40 @@ void WidgetManager::applyScreenLocked(uint8_t screenId) { closeModalLocked(); } - lv_display_t* disp = lv_display_get_default(); - if (disp) { - lv_display_enable_invalidation(disp, false); - } - - // SAFE DESTRUCTION: + // SAFE DESTRUCTION of C++ widgets: // 1. Mark all C++ widgets as "LVGL object already gone" for (auto& widget : widgets_) { if (widget) widget->clearLvglObject(); } - - // 2. Delete all LVGL objects on layers we use - lv_obj_clean(lv_scr_act()); + // 2. Clean layer_top (sub-buttons etc) lv_obj_clean(lv_layer_top()); - - // 3. Now destroy C++ objects (their destructors won't call lv_obj_delete) + // 3. Destroy C++ objects (their destructors won't call lv_obj_delete) destroyAllWidgets(); + // Create a NEW screen object for the transition + lv_obj_t* newScreen = lv_obj_create(NULL); + lv_obj_remove_style_all(newScreen); + lv_obj_set_size(newScreen, LV_PCT(100), LV_PCT(100)); + lv_obj_clear_flag(newScreen, LV_OBJ_FLAG_SCROLLABLE); + ESP_LOGI(TAG, "Creating new widgets for screen '%s' (%d widgets)...", screen->name, screen->widgetCount); - lv_obj_t* root = lv_scr_act(); - createAllWidgets(*screen, root); + createAllWidgets(*screen, newScreen); ESP_LOGI(TAG, "Widgets created"); applyCachedValuesToWidgets(); - if (disp) { - lv_display_enable_invalidation(disp, true); - } - lv_obj_invalidate(lv_scr_act()); + // Get animation settings + ESP_LOGI(TAG, "Animation config: type=%d, duration=%u", + (int)config_->screenAnimType, config_->screenAnimDuration); + lv_screen_load_anim_t animType = screenAnimToLvgl(config_->screenAnimType); + uint32_t animDuration = config_->screenAnimDuration > 0 ? config_->screenAnimDuration : 300; + + ESP_LOGI(TAG, "Loading screen with animation: lvgl_type=%d, duration=%lums", + (int)animType, (unsigned long)animDuration); + + // Load the new screen with animation (old screen auto-deleted after animation) + // Parameters: new_scr, anim_type, time_ms, delay_ms, auto_del_old_scr + lv_screen_load_anim(newScreen, animType, animDuration, 0, true); ESP_LOGI(TAG, "applyScreen(%d) - complete", screenId); } @@ -1503,6 +1525,11 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const { cJSON_AddNumberToObject(knx, "dateTime", config_->knxDateTimeAddress); cJSON_AddNumberToObject(knx, "night", config_->knxNightModeAddress); + // Screen transition animation + cJSON* anim = cJSON_AddObjectToObject(root, "screenAnim"); + cJSON_AddNumberToObject(anim, "type", static_cast(config_->screenAnimType)); + cJSON_AddNumberToObject(anim, "duration", config_->screenAnimDuration); + cJSON* screens = cJSON_AddArrayToObject(root, "screens"); for (uint8_t s = 0; s < config_->screenCount; s++) { @@ -1722,13 +1749,19 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const { cJSON_AddNumberToObject(sbJson, "action", static_cast(sb.action)); cJSON_AddNumberToObject(sbJson, "target", sb.targetScreen); - char colorOnStr[8], colorOffStr[8]; - snprintf(colorOnStr, sizeof(colorOnStr), "#%02X%02X%02X", - sb.colorOn.r, sb.colorOn.g, sb.colorOn.b); - snprintf(colorOffStr, sizeof(colorOffStr), "#%02X%02X%02X", - sb.colorOff.r, sb.colorOff.g, sb.colorOff.b); - cJSON_AddStringToObject(sbJson, "colorOn", colorOnStr); - cJSON_AddStringToObject(sbJson, "colorOff", colorOffStr); + char bgColorOnStr[8], bgColorOffStr[8], iconColorOnStr[8], iconColorOffStr[8]; + snprintf(bgColorOnStr, sizeof(bgColorOnStr), "#%02X%02X%02X", + sb.bgColorOn.r, sb.bgColorOn.g, sb.bgColorOn.b); + snprintf(bgColorOffStr, sizeof(bgColorOffStr), "#%02X%02X%02X", + sb.bgColorOff.r, sb.bgColorOff.g, sb.bgColorOff.b); + snprintf(iconColorOnStr, sizeof(iconColorOnStr), "#%02X%02X%02X", + sb.iconColorOn.r, sb.iconColorOn.g, sb.iconColorOn.b); + snprintf(iconColorOffStr, sizeof(iconColorOffStr), "#%02X%02X%02X", + sb.iconColorOff.r, sb.iconColorOff.g, sb.iconColorOff.b); + cJSON_AddStringToObject(sbJson, "bgColorOn", bgColorOnStr); + cJSON_AddStringToObject(sbJson, "bgColorOff", bgColorOffStr); + cJSON_AddStringToObject(sbJson, "iconColorOn", iconColorOnStr); + cJSON_AddStringToObject(sbJson, "iconColorOff", iconColorOffStr); cJSON_AddItemToArray(subButtons, sbJson); } @@ -2199,14 +2232,42 @@ bool WidgetManager::updateConfigFromJson(const char* json) { sb.targetScreen = target->valueint; } - cJSON* colorOn = cJSON_GetObjectItem(sbItem, "colorOn"); - if (cJSON_IsString(colorOn)) { - sb.colorOn = Color::fromHex(parseHexColor(colorOn->valuestring)); + // Background colors + cJSON* bgColorOn = cJSON_GetObjectItem(sbItem, "bgColorOn"); + if (cJSON_IsString(bgColorOn)) { + sb.bgColorOn = Color::fromHex(parseHexColor(bgColorOn->valuestring)); + } else { + // Backward compatibility: try old "colorOn" field + cJSON* colorOn = cJSON_GetObjectItem(sbItem, "colorOn"); + if (cJSON_IsString(colorOn)) { + sb.bgColorOn = Color::fromHex(parseHexColor(colorOn->valuestring)); + } } - cJSON* colorOff = cJSON_GetObjectItem(sbItem, "colorOff"); - if (cJSON_IsString(colorOff)) { - sb.colorOff = Color::fromHex(parseHexColor(colorOff->valuestring)); + cJSON* bgColorOff = cJSON_GetObjectItem(sbItem, "bgColorOff"); + if (cJSON_IsString(bgColorOff)) { + sb.bgColorOff = Color::fromHex(parseHexColor(bgColorOff->valuestring)); + } else { + // Backward compatibility: try old "colorOff" field + cJSON* colorOff = cJSON_GetObjectItem(sbItem, "colorOff"); + if (cJSON_IsString(colorOff)) { + sb.bgColorOff = Color::fromHex(parseHexColor(colorOff->valuestring)); + } + } + + // Icon colors (default to white if not specified) + cJSON* iconColorOn = cJSON_GetObjectItem(sbItem, "iconColorOn"); + if (cJSON_IsString(iconColorOn)) { + sb.iconColorOn = Color::fromHex(parseHexColor(iconColorOn->valuestring)); + } else { + sb.iconColorOn = {255, 255, 255}; // Default white + } + + cJSON* iconColorOff = cJSON_GetObjectItem(sbItem, "iconColorOff"); + if (cJSON_IsString(iconColorOff)) { + sb.iconColorOff = Color::fromHex(parseHexColor(iconColorOff->valuestring)); + } else { + sb.iconColorOff = {255, 255, 255}; // Default white } sbIdx++; @@ -2410,6 +2471,19 @@ bool WidgetManager::updateConfigFromJson(const char* json) { if (cJSON_IsNumber(nightAddr)) newConfig->knxNightModeAddress = nightAddr->valueint; } + // Screen transition animation + cJSON* anim = cJSON_GetObjectItem(root, "screenAnim"); + if (cJSON_IsObject(anim)) { + cJSON* animType = cJSON_GetObjectItem(anim, "type"); + if (cJSON_IsNumber(animType)) { + newConfig->screenAnimType = static_cast(animType->valueint); + } + cJSON* animDuration = cJSON_GetObjectItem(anim, "duration"); + if (cJSON_IsNumber(animDuration)) { + newConfig->screenAnimDuration = static_cast(animDuration->valueint); + } + } + if (newConfig->screenCount == 0) { cJSON_Delete(root); return false; diff --git a/main/widgets/RoomCardWidgetBase.cpp b/main/widgets/RoomCardWidgetBase.cpp index 3491047..6d1d93a 100644 --- a/main/widgets/RoomCardWidgetBase.cpp +++ b/main/widgets/RoomCardWidgetBase.cpp @@ -150,7 +150,10 @@ void RoomCardWidgetBase::applySubButtonStyle() { for (uint8_t i = 0; i < config_.subButtonCount && i < MAX_SUBBUTTONS; ++i) { if (subButtonIcons_[i] && subBtnIconFont) { lv_obj_set_style_text_font(subButtonIcons_[i], subBtnIconFont, 0); - lv_obj_set_style_text_color(subButtonIcons_[i], lv_color_white(), 0); + // Set initial icon color based on state (OFF by default) + const Color& iconColor = subButtonStates_[i] ? + config_.subButtons[i].iconColorOn : config_.subButtons[i].iconColorOff; + lv_obj_set_style_text_color(subButtonIcons_[i], lv_color_hex(iconColor.toLvColor()), 0); } } } @@ -160,11 +163,12 @@ void RoomCardWidgetBase::updateSubButtonColor(uint8_t index) { const SubButtonConfig& cfg = config_.subButtons[index]; bool isOn = subButtonStates_[index]; - const Color& color = isOn ? cfg.colorOn : cfg.colorOff; - lv_obj_set_style_bg_color(subButtonObjs_[index], lv_color_hex(color.toLvColor()), 0); + // Update background color + const Color& bgColor = isOn ? cfg.bgColorOn : cfg.bgColorOff; + lv_obj_set_style_bg_color(subButtonObjs_[index], lv_color_hex(bgColor.toLvColor()), 0); - // Update icon based on state + // Update icon and icon color based on state if (subButtonIcons_[index]) { uint32_t iconCodepoint = isOn && cfg.iconCodepointOn > 0 ? cfg.iconCodepointOn : cfg.iconCodepointOff; if (iconCodepoint > 0) { @@ -172,6 +176,10 @@ void RoomCardWidgetBase::updateSubButtonColor(uint8_t index) { encodeUtf8(iconCodepoint, iconText); lv_label_set_text(subButtonIcons_[index], iconText); } + + // Set icon color + const Color& iconColor = isOn ? cfg.iconColorOn : cfg.iconColorOff; + lv_obj_set_style_text_color(subButtonIcons_[index], lv_color_hex(iconColor.toLvColor()), 0); } } diff --git a/sdkconfig b/sdkconfig index 5ccc069..8e04bc6 100644 --- a/sdkconfig +++ b/sdkconfig @@ -2861,7 +2861,7 @@ CONFIG_LV_USE_BUILTIN_SPRINTF=y # # HAL Settings # -CONFIG_LV_DEF_REFR_PERIOD=33 +CONFIG_LV_DEF_REFR_PERIOD=16 CONFIG_LV_DPI_DEF=130 # end of HAL Settings @@ -2933,12 +2933,12 @@ CONFIG_LV_USE_DRAW_SW_ASM=0 # CONFIG_LV_USE_LOG=y # CONFIG_LV_LOG_LEVEL_TRACE is not set -CONFIG_LV_LOG_LEVEL_INFO=y +# CONFIG_LV_LOG_LEVEL_INFO is not set # CONFIG_LV_LOG_LEVEL_WARN is not set -# CONFIG_LV_LOG_LEVEL_ERROR is not set +CONFIG_LV_LOG_LEVEL_ERROR=y # CONFIG_LV_LOG_LEVEL_USER is not set # CONFIG_LV_LOG_LEVEL_NONE is not set -CONFIG_LV_LOG_LEVEL=1 +CONFIG_LV_LOG_LEVEL=3 CONFIG_LV_LOG_PRINTF=y CONFIG_LV_LOG_USE_TIMESTAMP=y CONFIG_LV_LOG_USE_FILE_LINE=y diff --git a/web-interface/src/components/SettingsModal.vue b/web-interface/src/components/SettingsModal.vue index 083aec4..00c7907 100644 --- a/web-interface/src/components/SettingsModal.vue +++ b/web-interface/src/components/SettingsModal.vue @@ -30,6 +30,28 @@ +
+
Screen-Animation
+
+ + +
+
+ + +
+
KNX Zeit
diff --git a/web-interface/src/components/widgets/elements/RoomCardElement.vue b/web-interface/src/components/widgets/elements/RoomCardElement.vue index 345c948..aae0571 100644 --- a/web-interface/src/components/widgets/elements/RoomCardElement.vue +++ b/web-interface/src/components/widgets/elements/RoomCardElement.vue @@ -24,8 +24,8 @@ class="absolute rounded-full flex items-center justify-center shadow-md" :style="getSubButtonStyle(sb, idx)" > - - {{ String.fromCodePoint(sb.icon) }} + + {{ String.fromCodePoint(getSubButtonIcon(sb)) }}
@@ -56,8 +56,8 @@ class="absolute rounded-full flex items-center justify-center shadow-md" :style="getSubButtonStyleTile(sb, idx)" > - - {{ String.fromCodePoint(sb.icon) }} + + {{ String.fromCodePoint(getSubButtonIcon(sb)) }}
@@ -162,13 +162,15 @@ const getSubButtonStyleTile = (sb, idx) => { const btnSize = (props.widget.subButtonSize || 40) * s; const gap = 10 * s; const padding = 12 * s; + // Use bgColorOff with fallback to old colorOff for compatibility + const bgColor = sb.bgColorOff || sb.colorOff || '#666666'; return { width: btnSize + 'px', height: btnSize + 'px', right: padding + 'px', top: (padding + idx * (btnSize + gap)) + 'px', - backgroundColor: sb.colorOff || '#666666', + backgroundColor: bgColor, }; }; @@ -241,7 +243,8 @@ function getSubButtonStyle(sb, idx) { const angle = (pos * (Math.PI / 4)) - (Math.PI / 2); const x = centerX + orbitRadius * Math.cos(angle) - subBtnSize / 2; const y = centerY + orbitRadius * Math.sin(angle) - subBtnSize / 2; - const bgColor = sb.colorOff || '#666666'; + // Use bgColorOff with fallback to old colorOff for compatibility + const bgColor = sb.bgColorOff || sb.colorOff || '#666666'; return { width: `${subBtnSize}px`, @@ -257,12 +260,19 @@ function getSubButtonIconStyle(sb) { const s = props.scale; const subBtnSize = (props.widget.subButtonSize || 40) * s; const iconSize = subBtnSize * 0.5; + // Use iconColorOff with fallback for compatibility + const iconColor = sb.iconColorOff || '#ffffff'; return { fontSize: `${iconSize}px`, - color: '#ffffff' + color: iconColor }; } +function getSubButtonIcon(sb) { + // Use iconOff with fallback to old icon field for compatibility + return sb.iconOff || sb.icon || 0; +} + const computedStyle = computed(() => { const w = props.widget; const s = props.scale; diff --git a/web-interface/src/components/widgets/settings/RoomCardSettings.vue b/web-interface/src/components/widgets/settings/RoomCardSettings.vue index 129049e..ca94ddd 100644 --- a/web-interface/src/components/widgets/settings/RoomCardSettings.vue +++ b/web-interface/src/components/widgets/settings/RoomCardSettings.vue @@ -212,12 +212,19 @@ -
- +
+ An: - + Aus: - + +
+
+ + An: + + Aus: +
@@ -331,8 +338,10 @@ const subButtonCount = computed({ knxWrite: 0, action: 0, target: 0, - colorOn: '#FFCC00', - colorOff: '#666666' + bgColorOn: '#FFCC00', + bgColorOff: '#666666', + iconColorOn: '#FFFFFF', + iconColorOff: '#FFFFFF' }); } if (props.widget.subButtons.length > target) { diff --git a/web-interface/src/stores/editor.js b/web-interface/src/stores/editor.js index 1f515a1..26e83de 100644 --- a/web-interface/src/stores/editor.js +++ b/web-interface/src/stores/editor.js @@ -8,6 +8,7 @@ export const useEditorStore = defineStore('editor', () => { startScreen: 0, standby: { enabled: false, screen: -1, minutes: 5 }, knx: { time: 0, date: 0, dateTime: 0, night: 0 }, + screenAnim: { type: 1, duration: 300 }, // 1 = Fade, 300ms screens: [] }); @@ -209,6 +210,12 @@ export const useEditorStore = defineStore('editor', () => { if (config.knx.dateTime === undefined) config.knx.dateTime = 0; if (config.knx.night === undefined) config.knx.night = 0; } + if (!config.screenAnim) { + config.screenAnim = { type: 1, duration: 300 }; + } else { + if (config.screenAnim.type === undefined) config.screenAnim.type = 1; + if (config.screenAnim.duration === undefined) config.screenAnim.duration = 300; + } mapLegacyKnxAddresses(); // Recalculate IDs