Fixes
This commit is contained in:
parent
6c67516a4c
commit
4a2e2dcf63
@ -164,6 +164,9 @@ void WidgetConfig::serialize(uint8_t* buf) const {
|
|||||||
buf[pos++] = arcValueColor.g;
|
buf[pos++] = arcValueColor.g;
|
||||||
buf[pos++] = arcValueColor.b;
|
buf[pos++] = arcValueColor.b;
|
||||||
buf[pos++] = arcValueFontSize;
|
buf[pos++] = arcValueFontSize;
|
||||||
|
|
||||||
|
// Theme
|
||||||
|
buf[pos++] = themeFixed ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WidgetConfig::deserialize(const uint8_t* buf) {
|
void WidgetConfig::deserialize(const uint8_t* buf) {
|
||||||
@ -376,6 +379,13 @@ void WidgetConfig::deserialize(const uint8_t* buf) {
|
|||||||
arcValueColor = textColor;
|
arcValueColor = textColor;
|
||||||
arcValueFontSize = fontSize;
|
arcValueFontSize = fontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Theme
|
||||||
|
if (pos + 1 <= SERIALIZED_SIZE) {
|
||||||
|
themeFixed = buf[pos++] != 0;
|
||||||
|
} else {
|
||||||
|
themeFixed = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WidgetConfig WidgetConfig::createLabel(uint8_t id, int16_t x, int16_t y, const char* labelText) {
|
WidgetConfig WidgetConfig::createLabel(uint8_t id, int16_t x, int16_t y, const char* labelText) {
|
||||||
@ -408,6 +418,7 @@ WidgetConfig WidgetConfig::createLabel(uint8_t id, int16_t x, int16_t y, const c
|
|||||||
cfg.arcScaleColor = cfg.textColor;
|
cfg.arcScaleColor = cfg.textColor;
|
||||||
cfg.arcValueColor = cfg.textColor;
|
cfg.arcValueColor = cfg.textColor;
|
||||||
cfg.arcValueFontSize = cfg.fontSize;
|
cfg.arcValueFontSize = cfg.fontSize;
|
||||||
|
cfg.themeFixed = false;
|
||||||
cfg.shadow.enabled = false;
|
cfg.shadow.enabled = false;
|
||||||
// Icon defaults
|
// Icon defaults
|
||||||
cfg.iconCodepoint = 0;
|
cfg.iconCodepoint = 0;
|
||||||
@ -469,6 +480,7 @@ WidgetConfig WidgetConfig::createButton(uint8_t id, int16_t x, int16_t y,
|
|||||||
cfg.arcScaleColor = cfg.textColor;
|
cfg.arcScaleColor = cfg.textColor;
|
||||||
cfg.arcValueColor = cfg.textColor;
|
cfg.arcValueColor = cfg.textColor;
|
||||||
cfg.arcValueFontSize = cfg.fontSize;
|
cfg.arcValueFontSize = cfg.fontSize;
|
||||||
|
cfg.themeFixed = false;
|
||||||
cfg.shadow.enabled = true;
|
cfg.shadow.enabled = true;
|
||||||
cfg.shadow.offsetX = 2;
|
cfg.shadow.offsetX = 2;
|
||||||
cfg.shadow.offsetY = 2;
|
cfg.shadow.offsetY = 2;
|
||||||
@ -507,6 +519,7 @@ void ScreenConfig::clear(uint8_t newId, const char* newName) {
|
|||||||
id = newId;
|
id = newId;
|
||||||
mode = ScreenMode::FULLSCREEN;
|
mode = ScreenMode::FULLSCREEN;
|
||||||
backgroundColor = {26, 26, 46}; // Dark blue background
|
backgroundColor = {26, 26, 46}; // Dark blue background
|
||||||
|
nightBackgroundColor = {14, 18, 23}; // Darker night background
|
||||||
bgImagePath[0] = '\0'; // No background image
|
bgImagePath[0] = '\0'; // No background image
|
||||||
bgImageMode = BgImageMode::STRETCH;
|
bgImageMode = BgImageMode::STRETCH;
|
||||||
widgetCount = 0;
|
widgetCount = 0;
|
||||||
|
|||||||
@ -107,6 +107,49 @@ struct Color {
|
|||||||
.b = static_cast<uint8_t>(hex & 0xFF)
|
.b = static_cast<uint8_t>(hex & 0xFF)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invert lightness via HSL: light colors become dark, dark become light
|
||||||
|
Color invertLightness() const {
|
||||||
|
float rf = r / 255.0f, gf = g / 255.0f, bf = b / 255.0f;
|
||||||
|
float maxC = rf > gf ? (rf > bf ? rf : bf) : (gf > bf ? gf : bf);
|
||||||
|
float minC = rf < gf ? (rf < bf ? rf : bf) : (gf < bf ? gf : bf);
|
||||||
|
float delta = maxC - minC;
|
||||||
|
float L = (maxC + minC) * 0.5f;
|
||||||
|
float S = 0.0f, H = 0.0f;
|
||||||
|
if (delta > 0.0001f) {
|
||||||
|
S = L < 0.5f ? delta / (maxC + minC) : delta / (2.0f - maxC - minC);
|
||||||
|
if (maxC == rf) H = (gf - bf) / delta + (gf < bf ? 6.0f : 0.0f);
|
||||||
|
else if (maxC == gf) H = (bf - rf) / delta + 2.0f;
|
||||||
|
else H = (rf - gf) / delta + 4.0f;
|
||||||
|
H /= 6.0f;
|
||||||
|
}
|
||||||
|
// Invert lightness
|
||||||
|
L = 1.0f - L;
|
||||||
|
// HSL to RGB
|
||||||
|
auto hue2rgb = [](float p, float q, float t) -> float {
|
||||||
|
if (t < 0.0f) t += 1.0f;
|
||||||
|
if (t > 1.0f) t -= 1.0f;
|
||||||
|
if (t < 1.0f / 6.0f) return p + (q - p) * 6.0f * t;
|
||||||
|
if (t < 0.5f) return q;
|
||||||
|
if (t < 2.0f / 3.0f) return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
float ro, go, bo;
|
||||||
|
if (S < 0.0001f) {
|
||||||
|
ro = go = bo = L;
|
||||||
|
} else {
|
||||||
|
float q = L < 0.5f ? L * (1.0f + S) : L + S - L * S;
|
||||||
|
float p = 2.0f * L - q;
|
||||||
|
ro = hue2rgb(p, q, H + 1.0f / 3.0f);
|
||||||
|
go = hue2rgb(p, q, H);
|
||||||
|
bo = hue2rgb(p, q, H - 1.0f / 3.0f);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
.r = static_cast<uint8_t>(ro * 255.0f + 0.5f),
|
||||||
|
.g = static_cast<uint8_t>(go * 255.0f + 0.5f),
|
||||||
|
.b = static_cast<uint8_t>(bo * 255.0f + 0.5f)
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Condition operator for conditional styling
|
// Condition operator for conditional styling
|
||||||
@ -298,9 +341,11 @@ struct WidgetConfig {
|
|||||||
Color arcValueColor; // Center value text color
|
Color arcValueColor; // Center value text color
|
||||||
uint8_t arcValueFontSize; // Center value font size index
|
uint8_t arcValueFontSize; // Center value font size index
|
||||||
|
|
||||||
|
// Theme
|
||||||
|
bool themeFixed; // true = colors stay fixed in night mode
|
||||||
|
|
||||||
// Serialization size (fixed for NVS storage)
|
// Serialization size (fixed for NVS storage)
|
||||||
// 369 + 36 (6 subbuttons * 6 bytes for icon colors) = 405
|
static constexpr size_t SERIALIZED_SIZE = 406;
|
||||||
static constexpr size_t SERIALIZED_SIZE = 405;
|
|
||||||
|
|
||||||
void serialize(uint8_t* buf) const;
|
void serialize(uint8_t* buf) const;
|
||||||
void deserialize(const uint8_t* buf);
|
void deserialize(const uint8_t* buf);
|
||||||
@ -323,6 +368,7 @@ struct ScreenConfig {
|
|||||||
char name[MAX_SCREEN_NAME_LEN];
|
char name[MAX_SCREEN_NAME_LEN];
|
||||||
ScreenMode mode;
|
ScreenMode mode;
|
||||||
Color backgroundColor;
|
Color backgroundColor;
|
||||||
|
Color nightBackgroundColor; // Background color for night mode
|
||||||
char bgImagePath[MAX_BG_IMAGE_PATH_LEN]; // Background image path (e.g., "/images/bg.png")
|
char bgImagePath[MAX_BG_IMAGE_PATH_LEN]; // Background image path (e.g., "/images/bg.png")
|
||||||
BgImageMode bgImageMode; // 0=none, 1=stretch, 2=center, 3=tile
|
BgImageMode bgImageMode; // 0=none, 1=stretch, 2=center, 3=tile
|
||||||
uint8_t widgetCount;
|
uint8_t widgetCount;
|
||||||
|
|||||||
@ -1137,6 +1137,22 @@ void WidgetManager::refreshChartWidgets() {
|
|||||||
refreshChartWidgetsLocked();
|
refreshChartWidgetsLocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WidgetManager::applyNightMode(bool night) {
|
||||||
|
// Update screen background
|
||||||
|
const ScreenConfig* screen = activeScreen();
|
||||||
|
if (screen && screen_) {
|
||||||
|
Color bg = night ? screen->nightBackgroundColor : screen->backgroundColor;
|
||||||
|
lv_obj_set_style_bg_color(screen_, lv_color_make(bg.r, bg.g, bg.b), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-apply style to all widgets (they check nightMode_ internally)
|
||||||
|
for (auto& widget : widgets_) {
|
||||||
|
if (widget) {
|
||||||
|
widget->applyStyle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void WidgetManager::updateSystemTimeWidgets() {
|
void WidgetManager::updateSystemTimeWidgets() {
|
||||||
int64_t nowUs = esp_timer_get_time();
|
int64_t nowUs = esp_timer_get_time();
|
||||||
if (nowUs - lastSystemTimeUpdateUs_ < 500000) { // Update every 500ms
|
if (nowUs - lastSystemTimeUpdateUs_ < 500000) { // Update every 500ms
|
||||||
@ -1222,7 +1238,10 @@ void WidgetManager::applyKnxSwitch(uint16_t groupAddr, bool value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config_->knxNightModeAddress != 0 && groupAddr == config_->knxNightModeAddress) {
|
if (config_->knxNightModeAddress != 0 && groupAddr == config_->knxNightModeAddress) {
|
||||||
|
if (nightMode_ != value) {
|
||||||
nightMode_ = value;
|
nightMode_ = value;
|
||||||
|
applyNightMode(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1545,6 +1564,11 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
|
|||||||
screen.backgroundColor.r, screen.backgroundColor.g, screen.backgroundColor.b);
|
screen.backgroundColor.r, screen.backgroundColor.g, screen.backgroundColor.b);
|
||||||
cJSON_AddStringToObject(screenJson, "bgColor", bgColorStr);
|
cJSON_AddStringToObject(screenJson, "bgColor", bgColorStr);
|
||||||
|
|
||||||
|
char nightBgColorStr[8];
|
||||||
|
snprintf(nightBgColorStr, sizeof(nightBgColorStr), "#%02X%02X%02X",
|
||||||
|
screen.nightBackgroundColor.r, screen.nightBackgroundColor.g, screen.nightBackgroundColor.b);
|
||||||
|
cJSON_AddStringToObject(screenJson, "nightBgColor", nightBgColorStr);
|
||||||
|
|
||||||
// Background image
|
// Background image
|
||||||
if (screen.bgImagePath[0] != '\0') {
|
if (screen.bgImagePath[0] != '\0') {
|
||||||
cJSON_AddStringToObject(screenJson, "bgImage", screen.bgImagePath);
|
cJSON_AddStringToObject(screenJson, "bgImage", screen.bgImagePath);
|
||||||
@ -1638,6 +1662,8 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
|
|||||||
cJSON_AddStringToObject(widget, "arcValueColor", arcValueColorStr);
|
cJSON_AddStringToObject(widget, "arcValueColor", arcValueColorStr);
|
||||||
cJSON_AddNumberToObject(widget, "arcValueFontSize", w.arcValueFontSize);
|
cJSON_AddNumberToObject(widget, "arcValueFontSize", w.arcValueFontSize);
|
||||||
|
|
||||||
|
cJSON_AddBoolToObject(widget, "themeFixed", w.themeFixed);
|
||||||
|
|
||||||
cJSON_AddNumberToObject(widget, "parentId", w.parentId);
|
cJSON_AddNumberToObject(widget, "parentId", w.parentId);
|
||||||
|
|
||||||
// Secondary KNX address (left value)
|
// Secondary KNX address (left value)
|
||||||
@ -2004,6 +2030,9 @@ bool WidgetManager::updateConfigFromJson(const char* json) {
|
|||||||
cJSON* arcValueFontSize = cJSON_GetObjectItem(widget, "arcValueFontSize");
|
cJSON* arcValueFontSize = cJSON_GetObjectItem(widget, "arcValueFontSize");
|
||||||
if (cJSON_IsNumber(arcValueFontSize)) w.arcValueFontSize = arcValueFontSize->valueint;
|
if (cJSON_IsNumber(arcValueFontSize)) w.arcValueFontSize = arcValueFontSize->valueint;
|
||||||
|
|
||||||
|
cJSON* themeFixed = cJSON_GetObjectItem(widget, "themeFixed");
|
||||||
|
if (cJSON_IsBool(themeFixed)) w.themeFixed = cJSON_IsTrue(themeFixed);
|
||||||
|
|
||||||
cJSON* parentId = cJSON_GetObjectItem(widget, "parentId");
|
cJSON* parentId = cJSON_GetObjectItem(widget, "parentId");
|
||||||
if (cJSON_IsNumber(parentId)) {
|
if (cJSON_IsNumber(parentId)) {
|
||||||
w.parentId = static_cast<int8_t>(parentId->valueint);
|
w.parentId = static_cast<int8_t>(parentId->valueint);
|
||||||
@ -2362,6 +2391,11 @@ bool WidgetManager::updateConfigFromJson(const char* json) {
|
|||||||
screen.backgroundColor = Color::fromHex(parseHexColor(bgColor->valuestring));
|
screen.backgroundColor = Color::fromHex(parseHexColor(bgColor->valuestring));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cJSON* nightBgColor = cJSON_GetObjectItem(screenJson, "nightBgColor");
|
||||||
|
if (cJSON_IsString(nightBgColor)) {
|
||||||
|
screen.nightBackgroundColor = Color::fromHex(parseHexColor(nightBgColor->valuestring));
|
||||||
|
}
|
||||||
|
|
||||||
// Parse background image
|
// Parse background image
|
||||||
cJSON* bgImage = cJSON_GetObjectItem(screenJson, "bgImage");
|
cJSON* bgImage = cJSON_GetObjectItem(screenJson, "bgImage");
|
||||||
if (cJSON_IsString(bgImage)) {
|
if (cJSON_IsString(bgImage)) {
|
||||||
|
|||||||
@ -66,6 +66,9 @@ public:
|
|||||||
// KNX write (for RoomCard sub-buttons etc.)
|
// KNX write (for RoomCard sub-buttons etc.)
|
||||||
void sendKnxSwitch(uint16_t groupAddr, bool value);
|
void sendKnxSwitch(uint16_t groupAddr, bool value);
|
||||||
|
|
||||||
|
// Night mode
|
||||||
|
bool isNightMode() const { return nightMode_; }
|
||||||
|
|
||||||
// Direct config access
|
// Direct config access
|
||||||
GuiConfig& getConfig() { return *config_; }
|
GuiConfig& getConfig() { return *config_; }
|
||||||
const GuiConfig& getConfig() const { return *config_; }
|
const GuiConfig& getConfig() const { return *config_; }
|
||||||
@ -148,6 +151,7 @@ private:
|
|||||||
static bool isNumericTextSource(TextSource source);
|
static bool isNumericTextSource(TextSource source);
|
||||||
void refreshChartWidgetsLocked();
|
void refreshChartWidgetsLocked();
|
||||||
void refreshChartWidgets();
|
void refreshChartWidgets();
|
||||||
|
void applyNightMode(bool night);
|
||||||
|
|
||||||
void createDefaultConfig();
|
void createDefaultConfig();
|
||||||
void applyScreen(uint8_t screenId);
|
void applyScreen(uint8_t screenId);
|
||||||
|
|||||||
@ -82,11 +82,12 @@ void ArcWidget::applyStyle() {
|
|||||||
if (arcWidth < 2) arcWidth = 2;
|
if (arcWidth < 2) arcWidth = 2;
|
||||||
if (arcWidth > 48) arcWidth = 48;
|
if (arcWidth > 48) arcWidth = 48;
|
||||||
|
|
||||||
lv_color_t trackColor = lv_color_make(config_.bgColor.r, config_.bgColor.g, config_.bgColor.b);
|
Color trackCol = themeColor(config_.bgColor);
|
||||||
|
lv_color_t trackColor = lv_color_make(trackCol.r, trackCol.g, trackCol.b);
|
||||||
uint8_t trackOpa = config_.bgOpacity > 0 ? config_.bgOpacity : static_cast<uint8_t>(LV_OPA_40);
|
uint8_t trackOpa = config_.bgOpacity > 0 ? config_.bgOpacity : static_cast<uint8_t>(LV_OPA_40);
|
||||||
|
|
||||||
lv_color_t indicatorColor = lv_color_make(
|
Color indicatorCol = themeColor(config_.textColor);
|
||||||
config_.textColor.r, config_.textColor.g, config_.textColor.b);
|
lv_color_t indicatorColor = lv_color_make(indicatorCol.r, indicatorCol.g, indicatorCol.b);
|
||||||
|
|
||||||
lv_obj_set_style_arc_width(obj_, arcWidth, LV_PART_MAIN);
|
lv_obj_set_style_arc_width(obj_, arcWidth, LV_PART_MAIN);
|
||||||
lv_obj_set_style_arc_color(obj_, trackColor, LV_PART_MAIN);
|
lv_obj_set_style_arc_color(obj_, trackColor, LV_PART_MAIN);
|
||||||
@ -135,8 +136,8 @@ void ArcWidget::applyStyle() {
|
|||||||
lv_obj_set_style_arc_width(scale_, 1, LV_PART_MAIN);
|
lv_obj_set_style_arc_width(scale_, 1, LV_PART_MAIN);
|
||||||
lv_obj_set_style_arc_opa(scale_, LV_OPA_TRANSP, LV_PART_MAIN);
|
lv_obj_set_style_arc_opa(scale_, LV_OPA_TRANSP, LV_PART_MAIN);
|
||||||
|
|
||||||
lv_color_t scaleColor = lv_color_make(
|
Color scaleCol = themeColor(config_.arcScaleColor);
|
||||||
config_.arcScaleColor.r, config_.arcScaleColor.g, config_.arcScaleColor.b);
|
lv_color_t scaleColor = lv_color_make(scaleCol.r, scaleCol.g, scaleCol.b);
|
||||||
lv_obj_set_style_line_color(scale_, scaleColor, LV_PART_INDICATOR);
|
lv_obj_set_style_line_color(scale_, scaleColor, LV_PART_INDICATOR);
|
||||||
lv_obj_set_style_line_color(scale_, scaleColor, LV_PART_ITEMS);
|
lv_obj_set_style_line_color(scale_, scaleColor, LV_PART_ITEMS);
|
||||||
lv_obj_set_style_line_width(scale_, 2, LV_PART_INDICATOR);
|
lv_obj_set_style_line_width(scale_, 2, LV_PART_INDICATOR);
|
||||||
@ -155,8 +156,8 @@ void ArcWidget::applyStyle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (valueLabel_) {
|
if (valueLabel_) {
|
||||||
lv_color_t valueColor = lv_color_make(
|
Color valueCol = themeColor(config_.arcValueColor);
|
||||||
config_.arcValueColor.r, config_.arcValueColor.g, config_.arcValueColor.b);
|
lv_color_t valueColor = lv_color_make(valueCol.r, valueCol.g, valueCol.b);
|
||||||
lv_obj_set_style_text_color(valueLabel_, valueColor, 0);
|
lv_obj_set_style_text_color(valueLabel_, valueColor, 0);
|
||||||
lv_obj_set_style_text_font(valueLabel_, getFontBySize(config_.arcValueFontSize), 0);
|
lv_obj_set_style_text_font(valueLabel_, getFontBySize(config_.arcValueFontSize), 0);
|
||||||
lv_obj_center(valueLabel_);
|
lv_obj_center(valueLabel_);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#include "Widget.hpp"
|
#include "Widget.hpp"
|
||||||
#include "../Fonts.hpp"
|
#include "../Fonts.hpp"
|
||||||
|
#include "../WidgetManager.hpp"
|
||||||
#include "lvgl.h"
|
#include "lvgl.h"
|
||||||
|
|
||||||
Widget::Widget(const WidgetConfig& config)
|
Widget::Widget(const WidgetConfig& config)
|
||||||
@ -161,20 +162,30 @@ void Widget::applyConditionStyle(const ConditionStyle& style) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Widget::shouldTransformColors() const {
|
||||||
|
return WidgetManager::instance().isNightMode() && !config_.themeFixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color Widget::themeColor(const Color& c) const {
|
||||||
|
return shouldTransformColors() ? c.invertLightness() : c;
|
||||||
|
}
|
||||||
|
|
||||||
void Widget::applyCommonStyle() {
|
void Widget::applyCommonStyle() {
|
||||||
if (obj_ == nullptr) return;
|
if (obj_ == nullptr) return;
|
||||||
|
|
||||||
|
Color textCol = themeColor(config_.textColor);
|
||||||
|
Color bgCol = themeColor(config_.bgColor);
|
||||||
|
Color borderCol = themeColor(config_.borderColor);
|
||||||
|
|
||||||
// Text color
|
// Text color
|
||||||
lv_obj_set_style_text_color(obj_, lv_color_make(
|
lv_obj_set_style_text_color(obj_, lv_color_make(textCol.r, textCol.g, textCol.b), 0);
|
||||||
config_.textColor.r, config_.textColor.g, config_.textColor.b), 0);
|
|
||||||
|
|
||||||
// Font
|
// Font
|
||||||
lv_obj_set_style_text_font(obj_, getFontBySize(config_.fontSize), 0);
|
lv_obj_set_style_text_font(obj_, getFontBySize(config_.fontSize), 0);
|
||||||
|
|
||||||
// Background (for buttons and labels with bg)
|
// Background (for buttons and labels with bg)
|
||||||
if (config_.bgOpacity > 0) {
|
if (config_.bgOpacity > 0) {
|
||||||
lv_obj_set_style_bg_color(obj_, lv_color_make(
|
lv_obj_set_style_bg_color(obj_, lv_color_make(bgCol.r, bgCol.g, bgCol.b), 0);
|
||||||
config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0);
|
|
||||||
lv_obj_set_style_bg_opa(obj_, config_.bgOpacity, 0);
|
lv_obj_set_style_bg_opa(obj_, config_.bgOpacity, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,8 +197,7 @@ void Widget::applyCommonStyle() {
|
|||||||
// Border
|
// Border
|
||||||
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
|
||||||
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
|
||||||
lv_obj_set_style_border_color(obj_, lv_color_make(
|
lv_obj_set_style_border_color(obj_, lv_color_make(borderCol.r, borderCol.g, borderCol.b), 0);
|
||||||
config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0);
|
|
||||||
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
|
||||||
} else {
|
} else {
|
||||||
lv_obj_set_style_border_width(obj_, 0, 0);
|
lv_obj_set_style_border_width(obj_, 0, 0);
|
||||||
@ -213,8 +223,8 @@ void Widget::applyShadowStyle() {
|
|||||||
if (spread < 0) spread = 0;
|
if (spread < 0) spread = 0;
|
||||||
if (blur == 0) return;
|
if (blur == 0) return;
|
||||||
|
|
||||||
lv_color_t shadowColor = lv_color_make(
|
Color sc = themeColor(config_.shadow.color);
|
||||||
config_.shadow.color.r, config_.shadow.color.g, config_.shadow.color.b);
|
lv_color_t shadowColor = lv_color_make(sc.r, sc.g, sc.b);
|
||||||
|
|
||||||
// Default state shadow
|
// Default state shadow
|
||||||
lv_obj_set_style_shadow_color(obj_, shadowColor, 0);
|
lv_obj_set_style_shadow_color(obj_, shadowColor, 0);
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
#include "lvgl.h"
|
#include "lvgl.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
||||||
|
class WidgetManager;
|
||||||
|
|
||||||
class Widget {
|
class Widget {
|
||||||
public:
|
public:
|
||||||
explicit Widget(const WidgetConfig& config);
|
explicit Widget(const WidgetConfig& config);
|
||||||
@ -70,6 +72,10 @@ protected:
|
|||||||
void applyConditionStyle(const ConditionStyle& style);
|
void applyConditionStyle(const ConditionStyle& style);
|
||||||
static const lv_font_t* getFontBySize(uint8_t sizeIndex);
|
static const lv_font_t* getFontBySize(uint8_t sizeIndex);
|
||||||
|
|
||||||
|
// Night mode color transformation
|
||||||
|
bool shouldTransformColors() const;
|
||||||
|
Color themeColor(const Color& c) const;
|
||||||
|
|
||||||
const WidgetConfig& config_;
|
const WidgetConfig& config_;
|
||||||
lv_obj_t* obj_ = nullptr;
|
lv_obj_t* obj_ = nullptr;
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,14 @@
|
|||||||
<span class="text-[11px] text-muted">{{ store.config.screens.length }}</span>
|
<span class="text-[11px] text-muted">{{ store.config.screens.length }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
class="border px-2.5 py-1.5 rounded-[10px] text-[12px] font-semibold transition hover:-translate-y-0.5 active:translate-y-0.5"
|
||||||
|
:class="store.nightPreview ? 'bg-[#2f3b4a] text-white border-[#4a5568]' : 'border-border bg-panel-2 text-text hover:bg-[#e4ebf2]'"
|
||||||
|
@click="store.nightPreview = !store.nightPreview"
|
||||||
|
title="Nachtmodus-Vorschau"
|
||||||
|
>
|
||||||
|
<span class="material-symbols-outlined text-[16px] align-middle">{{ store.nightPreview ? 'dark_mode' : 'light_mode' }}</span>
|
||||||
|
</button>
|
||||||
<button class="border border-border bg-panel-2 text-text px-2.5 py-1.5 rounded-[10px] text-[12px] font-semibold transition hover:-translate-y-0.5 hover:bg-[#e4ebf2] active:translate-y-0.5" @click="emit('open-screen-settings')">Einstellungen</button>
|
<button class="border border-border bg-panel-2 text-text px-2.5 py-1.5 rounded-[10px] text-[12px] font-semibold transition hover:-translate-y-0.5 hover:bg-[#e4ebf2] active:translate-y-0.5" @click="emit('open-screen-settings')">Einstellungen</button>
|
||||||
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:bg-[#e4ebf2] hover:border-[#b8c4d2]" @click="store.addScreen">+</button>
|
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:bg-[#e4ebf2] hover:border-[#b8c4d2]" @click="store.addScreen">+</button>
|
||||||
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:bg-[#e4ebf2] hover:border-[#b8c4d2]" @click="screensOpen = !screensOpen">
|
<button class="w-7 h-7 rounded-lg border border-border bg-panel-2 text-text grid place-items-center cursor-pointer hover:bg-[#e4ebf2] hover:border-[#b8c4d2]" @click="screensOpen = !screensOpen">
|
||||||
@ -94,7 +102,9 @@ const canvasStyle = computed(() => {
|
|||||||
const style = {
|
const style = {
|
||||||
width: `${canvasW.value * store.canvasScale}px`,
|
width: `${canvasW.value * store.canvasScale}px`,
|
||||||
height: `${canvasH.value * store.canvasScale}px`,
|
height: `${canvasH.value * store.canvasScale}px`,
|
||||||
backgroundColor: store.activeScreen?.bgColor || '#1A1A2E'
|
backgroundColor: store.nightPreview
|
||||||
|
? (store.activeScreen?.nightBgColor || '#0E1217')
|
||||||
|
: (store.activeScreen?.bgColor || '#1A1A2E')
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add background image if set
|
// Add background image if set
|
||||||
|
|||||||
@ -16,6 +16,10 @@
|
|||||||
<label class="text-[12px] text-muted">Hintergrund</label>
|
<label class="text-[12px] text-muted">Hintergrund</label>
|
||||||
<input class="h-[30px] w-[44px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="screen.bgColor">
|
<input class="h-[30px] w-[44px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="screen.bgColor">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center justify-between gap-2.5">
|
||||||
|
<label class="text-[12px] text-muted">Nacht-Hintergrund</label>
|
||||||
|
<input class="h-[30px] w-[44px] cursor-pointer border-0 bg-transparent p-0" type="color" v-model="screen.nightBgColor">
|
||||||
|
</div>
|
||||||
<div class="flex items-center justify-between gap-2.5">
|
<div class="flex items-center justify-between gap-2.5">
|
||||||
<label class="text-[12px] text-muted">Modus</label>
|
<label class="text-[12px] text-muted">Modus</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="screen.mode">
|
<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="screen.mode">
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" v-model.number="w.w"></div>
|
<div :class="rowClass"><label :class="labelClass">Breite</label><input :class="inputClass" type="number" v-model.number="w.w"></div>
|
||||||
<div :class="rowClass"><label :class="labelClass">Hoehe</label><input :class="inputClass" type="number" v-model.number="w.h"></div>
|
<div :class="rowClass"><label :class="labelClass">Hoehe</label><input :class="inputClass" type="number" v-model.number="w.h"></div>
|
||||||
<div :class="rowClass"><label :class="labelClass">Sichtbar</label><input class="accent-[var(--accent)]" type="checkbox" v-model="w.visible"></div>
|
<div :class="rowClass"><label :class="labelClass">Sichtbar</label><input class="accent-[var(--accent)]" type="checkbox" v-model="w.visible"></div>
|
||||||
|
<div :class="rowClass"><label :class="labelClass">Farbe fixieren</label><input class="accent-[var(--accent)]" type="checkbox" v-model="w.themeFixed"></div>
|
||||||
|
|
||||||
<!-- Widget-specific settings -->
|
<!-- Widget-specific settings -->
|
||||||
<component
|
<component
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<component
|
<component
|
||||||
:is="widgetComponent"
|
:is="widgetComponent"
|
||||||
:widget="widget"
|
:widget="themedWidget"
|
||||||
:scale="scale"
|
:scale="scale"
|
||||||
:selected="selected"
|
:selected="selected"
|
||||||
@select="$emit('select')"
|
@select="$emit('select')"
|
||||||
@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, markRaw } from 'vue';
|
import { computed, markRaw } from 'vue';
|
||||||
|
import { useEditorStore } from '../stores/editor';
|
||||||
|
import { invertLightness } from '../utils';
|
||||||
import { WIDGET_TYPES } from '../constants';
|
import { WIDGET_TYPES } from '../constants';
|
||||||
|
|
||||||
// Import all widget element components
|
// Import all widget element components
|
||||||
@ -56,7 +58,26 @@ const componentMap = {
|
|||||||
[WIDGET_TYPES.BUTTONMATRIX]: markRaw(ButtonMatrixElement)
|
[WIDGET_TYPES.BUTTONMATRIX]: markRaw(ButtonMatrixElement)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const store = useEditorStore();
|
||||||
|
|
||||||
const widgetComponent = computed(() => {
|
const widgetComponent = computed(() => {
|
||||||
return componentMap[props.widget.type] || LabelElement;
|
return componentMap[props.widget.type] || LabelElement;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const colorKeys = ['textColor', 'bgColor', 'borderColor', 'arcScaleColor', 'arcValueColor'];
|
||||||
|
|
||||||
|
const themedWidget = computed(() => {
|
||||||
|
const w = props.widget;
|
||||||
|
if (!store.nightPreview || w.themeFixed) return w;
|
||||||
|
const copy = { ...w };
|
||||||
|
for (const key of colorKeys) {
|
||||||
|
if (copy[key] && typeof copy[key] === 'string' && copy[key].startsWith('#')) {
|
||||||
|
copy[key] = invertLightness(copy[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (copy.shadow && copy.shadow.enabled && copy.shadow.color) {
|
||||||
|
copy.shadow = { ...copy.shadow, color: invertLightness(copy.shadow.color) };
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -208,7 +208,8 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconCodepoint: 0,
|
iconCodepoint: 0,
|
||||||
iconPosition: 0,
|
iconPosition: 0,
|
||||||
iconSize: 1,
|
iconSize: 1,
|
||||||
iconGap: 8
|
iconGap: 8,
|
||||||
|
themeFixed: false
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
w: 130,
|
w: 130,
|
||||||
@ -232,6 +233,7 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconPosition: 0,
|
iconPosition: 0,
|
||||||
iconSize: 1,
|
iconSize: 1,
|
||||||
iconGap: 8,
|
iconGap: 8,
|
||||||
|
themeFixed: false,
|
||||||
conditions: []
|
conditions: []
|
||||||
},
|
},
|
||||||
led: {
|
led: {
|
||||||
@ -254,7 +256,8 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconCodepoint: 0,
|
iconCodepoint: 0,
|
||||||
iconPosition: 0,
|
iconPosition: 0,
|
||||||
iconSize: 1,
|
iconSize: 1,
|
||||||
iconGap: 8
|
iconGap: 8,
|
||||||
|
themeFixed: false
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
w: 48,
|
w: 48,
|
||||||
@ -276,7 +279,8 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconCodepoint: 0xe88a,
|
iconCodepoint: 0xe88a,
|
||||||
iconPosition: 0,
|
iconPosition: 0,
|
||||||
iconSize: 3,
|
iconSize: 3,
|
||||||
iconGap: 8
|
iconGap: 8,
|
||||||
|
themeFixed: false
|
||||||
},
|
},
|
||||||
tabview: {
|
tabview: {
|
||||||
w: 400,
|
w: 400,
|
||||||
@ -298,7 +302,8 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconCodepoint: 0,
|
iconCodepoint: 0,
|
||||||
iconPosition: 0, // 0=Top, 1=Bottom, 2=Left, 3=Right
|
iconPosition: 0, // 0=Top, 1=Bottom, 2=Left, 3=Right
|
||||||
iconSize: 5, // Used for Tab Height (x10) -> 50px
|
iconSize: 5, // Used for Tab Height (x10) -> 50px
|
||||||
iconGap: 0
|
iconGap: 0,
|
||||||
|
themeFixed: false
|
||||||
},
|
},
|
||||||
tabpage: {
|
tabpage: {
|
||||||
w: 0, // Ignored
|
w: 0, // Ignored
|
||||||
@ -320,7 +325,8 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconCodepoint: 0,
|
iconCodepoint: 0,
|
||||||
iconPosition: 0,
|
iconPosition: 0,
|
||||||
iconSize: 1,
|
iconSize: 1,
|
||||||
iconGap: 0
|
iconGap: 0,
|
||||||
|
themeFixed: false
|
||||||
},
|
},
|
||||||
powerflow: {
|
powerflow: {
|
||||||
w: 720,
|
w: 720,
|
||||||
@ -342,7 +348,8 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconCodepoint: 0,
|
iconCodepoint: 0,
|
||||||
iconPosition: 0,
|
iconPosition: 0,
|
||||||
iconSize: 1,
|
iconSize: 1,
|
||||||
iconGap: 0
|
iconGap: 0,
|
||||||
|
themeFixed: false
|
||||||
},
|
},
|
||||||
powernode: {
|
powernode: {
|
||||||
w: 120,
|
w: 120,
|
||||||
@ -374,6 +381,7 @@ export const WIDGET_DEFAULTS = {
|
|||||||
text3: '',
|
text3: '',
|
||||||
knxAddr3: 0,
|
knxAddr3: 0,
|
||||||
// Conditions
|
// Conditions
|
||||||
|
themeFixed: false,
|
||||||
conditions: []
|
conditions: []
|
||||||
},
|
},
|
||||||
powerlink: {
|
powerlink: {
|
||||||
@ -396,7 +404,8 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconCodepoint: 0,
|
iconCodepoint: 0,
|
||||||
iconPosition: 0,
|
iconPosition: 0,
|
||||||
iconSize: 0,
|
iconSize: 0,
|
||||||
iconGap: 6
|
iconGap: 6,
|
||||||
|
themeFixed: false
|
||||||
},
|
},
|
||||||
chart: {
|
chart: {
|
||||||
w: 360,
|
w: 360,
|
||||||
@ -419,6 +428,7 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconPosition: 0,
|
iconPosition: 0,
|
||||||
iconSize: 1,
|
iconSize: 1,
|
||||||
iconGap: 0,
|
iconGap: 0,
|
||||||
|
themeFixed: false,
|
||||||
chart: {
|
chart: {
|
||||||
period: 0,
|
period: 0,
|
||||||
series: [
|
series: [
|
||||||
@ -446,7 +456,8 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconCodepoint: 0,
|
iconCodepoint: 0,
|
||||||
iconPosition: 0,
|
iconPosition: 0,
|
||||||
iconSize: 1,
|
iconSize: 1,
|
||||||
iconGap: 0
|
iconGap: 0,
|
||||||
|
themeFixed: false
|
||||||
},
|
},
|
||||||
roomcard: {
|
roomcard: {
|
||||||
w: 200,
|
w: 200,
|
||||||
@ -474,7 +485,8 @@ export const WIDGET_DEFAULTS = {
|
|||||||
subButtonOpacity: 255, // Sub-button opacity (0-255)
|
subButtonOpacity: 255, // Sub-button opacity (0-255)
|
||||||
cardStyle: 0, // 0=Bubble (round), 1=Tile (rectangular)
|
cardStyle: 0, // 0=Bubble (round), 1=Tile (rectangular)
|
||||||
subButtons: [],
|
subButtons: [],
|
||||||
textLines: [] // Variable text lines with icon, text, textSrc, knxAddr, fontSize
|
textLines: [], // Variable text lines with icon, text, textSrc, knxAddr, fontSize
|
||||||
|
themeFixed: false
|
||||||
},
|
},
|
||||||
rectangle: {
|
rectangle: {
|
||||||
w: 220,
|
w: 220,
|
||||||
@ -501,7 +513,8 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconSize: 1,
|
iconSize: 1,
|
||||||
iconGap: 0,
|
iconGap: 0,
|
||||||
iconPositionX: 0,
|
iconPositionX: 0,
|
||||||
iconPositionY: 0
|
iconPositionY: 0,
|
||||||
|
themeFixed: false
|
||||||
},
|
},
|
||||||
arc: {
|
arc: {
|
||||||
w: 180,
|
w: 180,
|
||||||
@ -536,7 +549,8 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconSize: 1,
|
iconSize: 1,
|
||||||
iconGap: 0,
|
iconGap: 0,
|
||||||
iconPositionX: 0,
|
iconPositionX: 0,
|
||||||
iconPositionY: 0
|
iconPositionY: 0,
|
||||||
|
themeFixed: false
|
||||||
},
|
},
|
||||||
buttonmatrix: {
|
buttonmatrix: {
|
||||||
w: 240,
|
w: 240,
|
||||||
@ -563,6 +577,7 @@ export const WIDGET_DEFAULTS = {
|
|||||||
iconSize: 1,
|
iconSize: 1,
|
||||||
iconGap: 0,
|
iconGap: 0,
|
||||||
iconPositionX: 0,
|
iconPositionX: 0,
|
||||||
iconPositionY: 0
|
iconPositionY: 0,
|
||||||
|
themeFixed: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,6 +19,7 @@ export const useEditorStore = defineStore('editor', () => {
|
|||||||
const showGrid = ref(true);
|
const showGrid = ref(true);
|
||||||
const snapToGrid = ref(true);
|
const snapToGrid = ref(true);
|
||||||
const gridSize = ref(20);
|
const gridSize = ref(20);
|
||||||
|
const nightPreview = ref(false);
|
||||||
const powerLinkMode = reactive({ active: false, powerflowId: null, fromNodeId: null });
|
const powerLinkMode = reactive({ active: false, powerflowId: null, fromNodeId: null });
|
||||||
|
|
||||||
const nextScreenId = ref(0);
|
const nextScreenId = ref(0);
|
||||||
@ -273,6 +274,7 @@ export const useEditorStore = defineStore('editor', () => {
|
|||||||
name: `Screen ${id}`,
|
name: `Screen ${id}`,
|
||||||
mode: 0,
|
mode: 0,
|
||||||
bgColor: '#1A1A2E',
|
bgColor: '#1A1A2E',
|
||||||
|
nightBgColor: '#0E1217',
|
||||||
bgImage: '',
|
bgImage: '',
|
||||||
bgImageMode: 1,
|
bgImageMode: 1,
|
||||||
widgets: []
|
widgets: []
|
||||||
@ -406,7 +408,8 @@ export const useEditorStore = defineStore('editor', () => {
|
|||||||
iconSize: defaults.iconSize || 1,
|
iconSize: defaults.iconSize || 1,
|
||||||
iconGap: defaults.iconGap || 8,
|
iconGap: defaults.iconGap || 8,
|
||||||
iconPositionX: defaults.iconPositionX || 8,
|
iconPositionX: defaults.iconPositionX || 8,
|
||||||
iconPositionY: defaults.iconPositionY || 8
|
iconPositionY: defaults.iconPositionY || 8,
|
||||||
|
themeFixed: defaults.themeFixed ?? false
|
||||||
};
|
};
|
||||||
|
|
||||||
if (defaults.conditions !== undefined) {
|
if (defaults.conditions !== undefined) {
|
||||||
@ -656,6 +659,7 @@ export const useEditorStore = defineStore('editor', () => {
|
|||||||
knxAddresses,
|
knxAddresses,
|
||||||
selectedWidgetId,
|
selectedWidgetId,
|
||||||
activeScreenId,
|
activeScreenId,
|
||||||
|
nightPreview,
|
||||||
canvasScale,
|
canvasScale,
|
||||||
showGrid,
|
showGrid,
|
||||||
snapToGrid,
|
snapToGrid,
|
||||||
|
|||||||
@ -4,6 +4,44 @@ export function typeKeyFor(type) {
|
|||||||
return TYPE_KEYS[type] || 'label';
|
return TYPE_KEYS[type] || 'label';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function invertLightness(hex) {
|
||||||
|
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
||||||
|
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
||||||
|
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
||||||
|
const maxC = Math.max(r, g, b);
|
||||||
|
const minC = Math.min(r, g, b);
|
||||||
|
const delta = maxC - minC;
|
||||||
|
let L = (maxC + minC) * 0.5;
|
||||||
|
let S = 0, H = 0;
|
||||||
|
if (delta > 0.0001) {
|
||||||
|
S = L < 0.5 ? delta / (maxC + minC) : delta / (2 - maxC - minC);
|
||||||
|
if (maxC === r) H = ((g - b) / delta + (g < b ? 6 : 0)) / 6;
|
||||||
|
else if (maxC === g) H = ((b - r) / delta + 2) / 6;
|
||||||
|
else H = ((r - g) / delta + 4) / 6;
|
||||||
|
}
|
||||||
|
L = 1 - L;
|
||||||
|
const hue2rgb = (p, q, t) => {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1/6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1/2) return q;
|
||||||
|
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
let ro, go, bo;
|
||||||
|
if (S < 0.0001) {
|
||||||
|
ro = go = bo = L;
|
||||||
|
} else {
|
||||||
|
const q = L < 0.5 ? L * (1 + S) : L + S - L * S;
|
||||||
|
const p = 2 * L - q;
|
||||||
|
ro = hue2rgb(p, q, H + 1/3);
|
||||||
|
go = hue2rgb(p, q, H);
|
||||||
|
bo = hue2rgb(p, q, H - 1/3);
|
||||||
|
}
|
||||||
|
const toHex = v => Math.round(Math.max(0, Math.min(1, v)) * 255).toString(16).padStart(2, '0');
|
||||||
|
return `#${toHex(ro)}${toHex(go)}${toHex(bo)}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function clamp(value, min, max) {
|
export function clamp(value, min, max) {
|
||||||
if (Number.isNaN(value)) return min;
|
if (Number.isNaN(value)) return min;
|
||||||
return Math.max(min, Math.min(max, value));
|
return Math.max(min, Math.min(max, value));
|
||||||
@ -171,6 +209,7 @@ export function normalizeScreen(screen, nextScreenIdRef, nextWidgetIdRef) {
|
|||||||
if (!screen.name) screen.name = `Screen ${screen.id}`;
|
if (!screen.name) screen.name = `Screen ${screen.id}`;
|
||||||
if (screen.mode === undefined || screen.mode === null) screen.mode = 0;
|
if (screen.mode === undefined || screen.mode === null) screen.mode = 0;
|
||||||
if (!screen.bgColor) screen.bgColor = '#1A1A2E';
|
if (!screen.bgColor) screen.bgColor = '#1A1A2E';
|
||||||
|
if (!screen.nightBgColor) screen.nightBgColor = '#0E1217';
|
||||||
if (screen.bgImage === undefined) screen.bgImage = '';
|
if (screen.bgImage === undefined) screen.bgImage = '';
|
||||||
if (screen.bgImageMode === undefined) screen.bgImageMode = 1;
|
if (screen.bgImageMode === undefined) screen.bgImageMode = 1;
|
||||||
if (!Array.isArray(screen.widgets)) screen.widgets = [];
|
if (!Array.isArray(screen.widgets)) screen.widgets = [];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user