Fixes
This commit is contained in:
parent
a430110f94
commit
e22979c1c7
Binary file not shown.
Binary file not shown.
@ -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<uint8_t>(sb.position);
|
||||
buf[pos++] = static_cast<uint8_t>(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<SubButtonPosition>(buf[pos++]);
|
||||
sb.action = static_cast<SubButtonAction>(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<uint8_t>(i), nullptr);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<int>(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<int>(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<ScreenAnimType>(animType->valueint);
|
||||
}
|
||||
cJSON* animDuration = cJSON_GetObjectItem(anim, "duration");
|
||||
if (cJSON_IsNumber(animDuration)) {
|
||||
newConfig->screenAnimDuration = static_cast<uint16_t>(animDuration->valueint);
|
||||
}
|
||||
}
|
||||
|
||||
if (newConfig->screenCount == 0) {
|
||||
cJSON_Delete(root);
|
||||
return false;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -30,6 +30,28 @@
|
||||
<input class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" type="number" min="0" v-model.number="store.config.standby.minutes">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">Screen-Animation</div>
|
||||
<div class="flex items-center justify-between gap-2.5">
|
||||
<label class="text-[12px] text-muted">Typ</label>
|
||||
<select class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" v-model.number="store.config.screenAnim.type">
|
||||
<option :value="0">Keine</option>
|
||||
<option :value="1">Einblenden</option>
|
||||
<option :value="2">Schieben Links</option>
|
||||
<option :value="3">Schieben Rechts</option>
|
||||
<option :value="4">Schieben Hoch</option>
|
||||
<option :value="5">Schieben Runter</option>
|
||||
<option :value="6">Ueberlagern Links</option>
|
||||
<option :value="7">Ueberlagern Rechts</option>
|
||||
<option :value="8">Ueberlagern Hoch</option>
|
||||
<option :value="9">Ueberlagern Runter</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-2.5">
|
||||
<label class="text-[12px] text-muted">Dauer (ms)</label>
|
||||
<input class="flex-1 bg-panel-2 border border-border rounded-lg px-2 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent" type="number" min="0" max="2000" v-model.number="store.config.screenAnim.duration">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="text-[12px] uppercase tracking-[0.08em] text-[#3a5f88]">KNX Zeit</div>
|
||||
<div class="flex items-center justify-between gap-2.5">
|
||||
|
||||
@ -24,8 +24,8 @@
|
||||
class="absolute rounded-full flex items-center justify-center shadow-md"
|
||||
:style="getSubButtonStyle(sb, idx)"
|
||||
>
|
||||
<span v-if="sb.icon" class="material-symbols-outlined" :style="getSubButtonIconStyle(sb)">
|
||||
{{ String.fromCodePoint(sb.icon) }}
|
||||
<span v-if="getSubButtonIcon(sb)" class="material-symbols-outlined" :style="getSubButtonIconStyle(sb)">
|
||||
{{ String.fromCodePoint(getSubButtonIcon(sb)) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@ -56,8 +56,8 @@
|
||||
class="absolute rounded-full flex items-center justify-center shadow-md"
|
||||
:style="getSubButtonStyleTile(sb, idx)"
|
||||
>
|
||||
<span v-if="sb.icon" class="material-symbols-outlined" :style="getSubButtonIconStyle(sb)">
|
||||
{{ String.fromCodePoint(sb.icon) }}
|
||||
<span v-if="getSubButtonIcon(sb)" class="material-symbols-outlined" :style="getSubButtonIconStyle(sb)">
|
||||
{{ String.fromCodePoint(getSubButtonIcon(sb)) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@ -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;
|
||||
|
||||
@ -212,12 +212,19 @@
|
||||
<option v-for="s in store.config.screens" :key="s.id" :value="s.id">{{ s.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-[11px] text-muted">
|
||||
<label class="w-[50px]">Farben</label>
|
||||
<div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
|
||||
<label class="w-[50px]">Hintergr.</label>
|
||||
<span class="text-[10px]">An:</span>
|
||||
<input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.colorOn">
|
||||
<input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.bgColorOn">
|
||||
<span class="text-[10px]">Aus:</span>
|
||||
<input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.colorOff">
|
||||
<input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.bgColorOff">
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-[11px] text-muted">
|
||||
<label class="w-[50px]">Icon</label>
|
||||
<span class="text-[10px]">An:</span>
|
||||
<input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.iconColorOn">
|
||||
<span class="text-[10px]">Aus:</span>
|
||||
<input class="h-[22px] w-[28px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="sb.iconColorOff">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user