From 7b37f9dbdf92da0b6c68a64fd3ee3757c706b131 Mon Sep 17 00:00:00 2001 From: Rob Thomson Date: Tue, 12 May 2026 23:26:49 +0100 Subject: [PATCH] ethos 26 theme support --- src/dashx/app/modules/logs/logs_view.lua | 71 ++-- src/dashx/tools/logs.lua | 136 ++++++- src/dashx/widgets/dashboard/dashboard.lua | 15 +- src/dashx/widgets/dashboard/lib/loaders.lua | 29 +- src/dashx/widgets/dashboard/lib/toolbar.lua | 27 +- src/dashx/widgets/dashboard/lib/utils.lua | 363 +++++++++++++++--- .../dashboard/themes/@rt-rc/inflight.lua | 32 +- .../dashboard/themes/@rt-rc/postflight.lua | 32 +- .../dashboard/themes/@rt-rc/preflight.lua | 32 +- .../dashboard/themes/default/inflight.lua | 32 +- .../dashboard/themes/default/postflight.lua | 32 +- .../dashboard/themes/default/preflight.lua | 32 +- 12 files changed, 518 insertions(+), 315 deletions(-) diff --git a/src/dashx/app/modules/logs/logs_view.lua b/src/dashx/app/modules/logs/logs_view.lua index 9dad8d1..d77f4e2 100644 --- a/src/dashx/app/modules/logs/logs_view.lua +++ b/src/dashx/app/modules/logs/logs_view.lua @@ -6,6 +6,44 @@ local dashx = require("dashx") local utils = assert(loadfile("SCRIPTS:/" .. dashx.config.baseDir .. "/app/modules/logs/lib/utils.lua"))() + +local dashboardUtils = nil + +local function getDashboardUtils() + if dashboardUtils ~= nil then + return dashboardUtils or nil + end + + dashboardUtils = dashx.widgets and dashx.widgets.dashboard and dashx.widgets.dashboard.utils + if dashboardUtils then + return dashboardUtils + end + + local loader = loadfile("SCRIPTS:/" .. dashx.config.baseDir .. "/widgets/dashboard/lib/utils.lua") + if loader then + dashboardUtils = loader() + return dashboardUtils + end + + dashboardUtils = false + return nil +end + +local function getThemeState() + local du = getDashboardUtils() + if du and du.getThemeState then + return du.getThemeState() + end + + local dark = lcd.darkMode and lcd.darkMode() or false + return { + darkMode = dark, + usesThemeColors = false, + primaryColor = dark and lcd.RGB(255, 255, 255) or lcd.RGB(0, 0, 0), + secondaryColor = lcd.RGB(160, 160, 160), + secondaryBgColor = dark and lcd.RGB(40, 40, 40) or lcd.RGB(240, 240, 240) + } +end local res = system.getVersion() local LCD_W = res.lcdWidth local LCD_H = res.lcdHeight @@ -266,11 +304,7 @@ local function drawKey(name, keyunit, keyminmax, keyfloor, color, minimum, maxim lcd.drawText(x + 5, textY, name, LEFT) lcd.font(dashx.app.radio.logKeyFontSmall) - if lcd.darkMode() then - lcd.color(COLOR_WHITE) - else - lcd.color(COLOR_BLACK) - end + lcd.color(getThemeState().primaryColor or COLOR_WHITE) local min_trunc if keyunit == "rpm" and (minimum >= 100000 or maximum >= 1000000) then @@ -337,13 +371,10 @@ local function drawCurrentIndex(points, position, totalPoints, keyindex, keyunit if position > 50 then boxPos = boxPos - tw - (boxpadding * 2) end lcd.color(color) + local themeState = getThemeState() lcd.drawFilledRectangle(boxPos, boxY, tw + (boxpadding * 2), boxHeight) - if lcd.darkMode() then - lcd.color(COLOR_BLACK) - else - lcd.color(COLOR_WHITE) - end + lcd.color(themeState.primaryColor or COLOR_WHITE) lcd.drawText(idxPos, textY, value, textAlign) if laneNumber == 1 then @@ -365,21 +396,13 @@ local function drawCurrentIndex(points, position, totalPoints, keyindex, keyunit lcd.font(dashx.app.radio.logKeyFont) local ty = graphPos['height'] + graphPos['menu_offset'] - 10 - lcd.color(COLOR_WHITE) + lcd.color(themeState.primaryColor or COLOR_WHITE) lcd.drawText(idxPos, ty, full_label, textAlign) - if lcd.darkMode() then - lcd.color(COLOR_WHITE) - else - lcd.color(COLOR_BLACK) - end + lcd.color(themeState.primaryColor or COLOR_WHITE) lcd.drawLine(linePos, graphPos['menu_offset'] - 5, linePos, graphPos['menu_offset'] + graphPos['height']) - if lcd.darkMode() then - lcd.color(lcd.RGB(40, 40, 40)) - else - lcd.color(lcd.RGB(240, 240, 240)) - end + lcd.color(themeState.secondaryBgColor or lcd.RGB(40, 40, 40)) local z_x = (LCD_W - 25) local z_y = graphPos['slider_y'] @@ -392,11 +415,7 @@ local function drawCurrentIndex(points, position, totalPoints, keyindex, keyunit lcd.drawFilledRectangle(z_x, z_y, z_w, z_h) if zoomCount > 1 then - if lcd.darkMode() then - lcd.color(COLOR_WHITE) - else - lcd.color(COLOR_BLACK) - end + lcd.color(themeState.primaryColor or COLOR_WHITE) else lcd.color(COLOR_GREY) end diff --git a/src/dashx/tools/logs.lua b/src/dashx/tools/logs.lua index 0aec8d3..b39b65e 100644 --- a/src/dashx/tools/logs.lua +++ b/src/dashx/tools/logs.lua @@ -137,6 +137,106 @@ local tool = { progressDialog = nil } +local FALLBACK_THEME_STATE = { + dark = { + darkMode = true, + usesThemeColors = false, + primaryColor = COLOR_WHITE_SAFE, + primaryBgColor = COLOR_BLACK_SAFE, + secondaryColor = COLOR_GREY_SAFE, + secondaryBgColor = lcd.RGB(40, 40, 40), + focusBgColor = lcd.RGB(40, 40, 40), + focusColor = COLOR_WHITE_SAFE, + highlightInvertColor = COLOR_BLACK_SAFE, + warningColor = lcd.RGB(220, 92, 92), + activeColor = lcd.RGB(102, 214, 129), + buttonBorderActiveColor = COLOR_WHITE_SAFE, + buttonBorderColor = lcd.RGB(86, 96, 106), + pageBgColor = lcd.RGB(10, 14, 18) + }, + light = { + darkMode = false, + usesThemeColors = false, + primaryColor = COLOR_BLACK_SAFE, + primaryBgColor = COLOR_WHITE_SAFE, + secondaryColor = lcd.RGB(108, 116, 124), + secondaryBgColor = lcd.RGB(240, 240, 240), + focusBgColor = lcd.RGB(240, 240, 240), + focusColor = COLOR_BLACK_SAFE, + highlightInvertColor = COLOR_WHITE_SAFE, + warningColor = lcd.RGB(200, 0, 0), + activeColor = lcd.RGB(0, 140, 0), + buttonBorderActiveColor = COLOR_BLACK_SAFE, + buttonBorderColor = lcd.RGB(196, 202, 208), + pageBgColor = lcd.RGB(246, 247, 249) + } +} +local FALLBACK_CHROME_THEME = { + dark = { + background = lcd.RGB(10, 14, 18), + panel = lcd.RGB(22, 28, 34), + panelAlt = lcd.RGB(17, 22, 27), + text = COLOR_WHITE_SAFE, + muted = lcd.RGB(166, 174, 182), + border = lcd.RGB(86, 96, 106) + }, + light = { + background = lcd.RGB(246, 247, 249), + panel = COLOR_WHITE_SAFE, + panelAlt = lcd.RGB(240, 242, 245), + text = COLOR_BLACK_SAFE, + muted = lcd.RGB(108, 116, 124), + border = lcd.RGB(196, 202, 208) + } +} +local dashboardUtils = nil + +local function getDashboardUtils() + if dashboardUtils ~= nil then + return dashboardUtils or nil + end + + dashboardUtils = dashx.widgets and dashx.widgets.dashboard and dashx.widgets.dashboard.utils + if dashboardUtils then + return dashboardUtils + end + + local loader = loadfile("SCRIPTS:/" .. dashx.config.baseDir .. "/widgets/dashboard/lib/utils.lua") + if loader then + dashboardUtils = loader() + return dashboardUtils + end + + dashboardUtils = false + return nil +end + +local function getThemeState() + local utils = getDashboardUtils() + if utils and utils.getThemeState then + return utils.getThemeState() + end + + return (lcd.darkMode and lcd.darkMode() or false) and FALLBACK_THEME_STATE.dark or FALLBACK_THEME_STATE.light +end + +local function getChromeTheme() + local utils = getDashboardUtils() + if utils and utils.getChromeTheme then + return utils.getChromeTheme() + end + + return getThemeState().darkMode and FALLBACK_CHROME_THEME.dark or FALLBACK_CHROME_THEME.light +end + +local function getGraphValueTextColor(themeState) + if themeState.usesThemeColors then + return themeState.focusColor or themeState.highlightInvertColor or COLOR_WHITE_SAFE + end + + return themeState.darkMode and COLOR_BLACK_SAFE or COLOR_WHITE_SAFE +end + local function openProgressDialog(title, message) if form.openWaitDialog and dashx.utils.ethosVersionAtLeast({26, 1, 0}) then return form.openWaitDialog({title = title, message = message, progress = true}) @@ -152,7 +252,19 @@ local function closeProgressDialog() end local function buildColorTable() - if lcd.darkMode() then + local themeState = getThemeState() + + if themeState.usesThemeColors then + return { + voltage = themeState.warningColor or lcd.RGB(220, 92, 92), + current = themeState.focusBgColor or themeState.buttonBorderActiveColor or lcd.RGB(255, 168, 58), + rpm = themeState.activeColor or lcd.RGB(102, 214, 129), + temp_esc = themeState.focusColor or themeState.secondaryColor or lcd.RGB(90, 180, 255), + throttle_percent = themeState.buttonBorderActiveColor or themeState.primaryColor or lcd.RGB(248, 215, 90) + } + end + + if themeState.darkMode then return { voltage = lcd.RGB(220, 92, 92), current = lcd.RGB(255, 168, 58), @@ -1088,7 +1200,7 @@ local function drawKey(name, keyunit, keyminmax, keyfloor, color, minimum, maxim lcd.drawText(x + 5, textY, name, LEFT) lcd.font(radio.logKeyFontSmall) - lcd.color(lcd.darkMode() and COLOR_WHITE_SAFE or COLOR_BLACK_SAFE) + lcd.color(getThemeState().primaryColor or COLOR_WHITE_SAFE) if keyunit == "rpm" and ((minimum >= 10000) or (maximum >= 10000)) then minText = string_format("%.1fK", minimum / 10000) @@ -1143,10 +1255,11 @@ local function drawCurrentIndex(points, position, totalPoints, keyunit, keyfloor boxX = linePos + boxPadding end + local themeState = getThemeState() lcd.color(color) lcd.drawFilledRectangle(boxX, boxY, textW + (boxPadding * 2), boxHeight) - lcd.color(lcd.darkMode() and COLOR_BLACK_SAFE or COLOR_WHITE_SAFE) + lcd.color(getGraphValueTextColor(themeState)) lcd.drawText(textX, textY, valueLabel, textAlign) if laneNumber == 1 then @@ -1167,13 +1280,13 @@ local function drawCurrentIndex(points, position, totalPoints, keyunit, keyfloor local timeY = graphPos.height + graphPos.menu_offset - 10 lcd.font(radio.logKeyFont) - lcd.color(COLOR_WHITE_SAFE) + lcd.color(themeState.primaryColor or COLOR_WHITE_SAFE) lcd.drawText(textX, timeY, fullLabel, textAlign) - lcd.color(lcd.darkMode() and COLOR_WHITE_SAFE or COLOR_BLACK_SAFE) + lcd.color(themeState.primaryColor or COLOR_WHITE_SAFE) lcd.drawLine(linePos, graphPos.menu_offset - 5, linePos, graphPos.menu_offset + graphPos.height) - lcd.color(lcd.darkMode() and lcd.RGB(40, 40, 40) or lcd.RGB(240, 240, 240)) + lcd.color(themeState.secondaryBgColor or lcd.RGB(40, 40, 40)) local zoomX = graphPos.lcdWidth - 25 local zoomY = graphPos.slider_y local zoomW = 20 @@ -1182,26 +1295,27 @@ local function drawCurrentIndex(points, position, totalPoints, keyunit, keyfloor local lineOffsetY = (tool.state.zoomCount - tool.state.zoomLevel) * zoomLineH lcd.drawFilledRectangle(zoomX, zoomY, zoomW, zoomH) - lcd.color(tool.state.zoomCount > 1 and (lcd.darkMode() and COLOR_WHITE_SAFE or COLOR_BLACK_SAFE) or COLOR_GREY_SAFE) + lcd.color(tool.state.zoomCount > 1 and (themeState.primaryColor or COLOR_WHITE_SAFE) or COLOR_GREY_SAFE) lcd.drawFilledRectangle(zoomX, zoomY + lineOffsetY, zoomW, zoomLineH) end end local function paintLoadingMessage(message, detail) local _, width, height = getRadio() + local theme = getChromeTheme() - lcd.color(lcd.darkMode() and lcd.RGB(22, 28, 34) or lcd.RGB(255, 255, 255)) + lcd.color(theme.panel) lcd.drawFilledRectangle(20, math_floor(height * 0.35), width - 40, 70) - lcd.color(lcd.darkMode() and lcd.RGB(86, 96, 106) or lcd.RGB(196, 202, 208)) + lcd.color(theme.border) lcd.drawRectangle(20, math_floor(height * 0.35), width - 40, 70, 1) lcd.font(FONT_STD) - lcd.color(lcd.darkMode() and COLOR_WHITE_SAFE or COLOR_BLACK_SAFE) + lcd.color(theme.text) lcd.drawText(math_floor(width / 2), math_floor(height * 0.35) + 14, message, CENTERED) if detail then lcd.font(FONT_XXS) - lcd.color(COLOR_GREY_SAFE) + lcd.color(theme.muted or COLOR_GREY_SAFE) lcd.drawText(math_floor(width / 2), math_floor(height * 0.35) + 38, tostring(detail), CENTERED) end end diff --git a/src/dashx/widgets/dashboard/dashboard.lua b/src/dashx/widgets/dashboard/dashboard.lua index cbd905e..f6a2b41 100644 --- a/src/dashx/widgets/dashboard/dashboard.lua +++ b/src/dashx/widgets/dashboard/dashboard.lua @@ -20,7 +20,9 @@ local themesUserPath = "SCRIPTS:/" .. dashx.config.preferences .. "/dashboard/" local currentState = nil local loadedStates = {} local lastSizeKey = nil -local darkModeState = lcd.darkMode() +local themeStateSignature = nil +local nextThemeStateCheck = 0 +local themeStateCheckInterval = 0.25 local unsupportedResolution = false local forceFullRepaint = true local lastInvalidateAt = 0 @@ -224,6 +226,8 @@ local function loadObjects(module) end local function reloadTheme() + themeStateSignature = dashboard.utils and dashboard.utils.getThemeSignature and dashboard.utils.getThemeSignature() or themeStateSignature + nextThemeStateCheck = os.clock() + themeStateCheckInterval loadedStates = { preflight = loadStateScript(getThemeForState("preflight"), "preflight"), inflight = loadStateScript(getThemeForState("inflight"), "inflight"), @@ -612,9 +616,14 @@ function dashboard.wakeup(widget) return end - if runtimeState.model_changed or lcd.darkMode() ~= darkModeState then - darkModeState = lcd.darkMode() + if runtimeState.model_changed then reloadTheme() + elseif now >= nextThemeStateCheck then + nextThemeStateCheck = now + themeStateCheckInterval + local currentThemeSignature = dashboard.utils.getThemeSignature() + if currentThemeSignature ~= themeStateSignature then + reloadTheme() + end end if dashx.session and dashx.session.dashboardThemeReloadPending then diff --git a/src/dashx/widgets/dashboard/lib/loaders.lua b/src/dashx/widgets/dashboard/lib/loaders.lua index 1169763..5e6db7e 100644 --- a/src/dashx/widgets/dashboard/lib/loaders.lua +++ b/src/dashx/widgets/dashboard/lib/loaders.lua @@ -6,6 +6,7 @@ local dashx = require("dashx") local loaders = {} +local utils = dashx.widgets.dashboard.utils local function drawLogoImage(cx, cy, w, h) local imageName = "SCRIPTS:/" .. dashx.config.baseDir .. "/widgets/dashboard/gfx/logo.png" @@ -87,13 +88,17 @@ function loaders.staticLoader(dashboard, x, y, w, h) local cx, cy = x + w / 2, y + h / 2 local radius = math.min(w, h) * (dashboard.loaderScale or 0.3) local thickness = math.max(6, radius * 0.15) + local themeState = utils.getThemeState() + local ringColor = themeState.primaryColor or lcd.RGB(255, 255, 255, 1.0) + local innerColor = themeState.primaryBgColor or themeState.pageBgColor or lcd.RGB(0, 0, 0, 1.0) + local activeDotColor = themeState.primaryColor or lcd.RGB(255, 255, 255) + local inactiveDotColor = themeState.disableColor or themeState.secondaryColor or lcd.RGB(80, 80, 80) - local r, g, b = lcd.darkMode() and 255 or 0, lcd.darkMode() and 255 or 0, lcd.darkMode() and 255 or 0 - lcd.color(lcd.RGB(r, g, b, 1.0)) + lcd.color(ringColor) if lcd.drawFilledCircle then lcd.drawFilledCircle(cx, cy, radius) - lcd.color(lcd.darkMode() and lcd.RGB(0, 0, 0, 1.0) or lcd.RGB(0, 0, 0, 1.0)) + lcd.color(innerColor) lcd.drawFilledCircle(cx, cy, radius - thickness) end @@ -113,9 +118,9 @@ function loaders.staticLoader(dashboard, x, y, w, h) for i = 1, 3 do if i == dashboard._dots_index then - lcd.color(lcd.darkMode() and lcd.RGB(255, 255, 255) or lcd.RGB(0, 0, 0)) + lcd.color(activeDotColor) else - lcd.color(lcd.darkMode() and lcd.RGB(80, 80, 80) or lcd.RGB(180, 180, 180)) + lcd.color(inactiveDotColor) end lcd.drawFilledCircle(startX + (i - 1) * spacing, yPos, dotRadius) end @@ -134,8 +139,9 @@ function loaders.staticOverlayMessage(dashboard, x, y, w, h, txt) if dashboard._overlay_cycles <= 0 then return end dashboard._overlay_cycles = dashboard._overlay_cycles - 1 - local fg = lcd.darkMode() and lcd.RGB(255, 255, 255) or lcd.RGB(255, 255, 255) - local bg = lcd.darkMode() and lcd.RGB(0, 0, 0, 1.0) or lcd.RGB(255, 255, 255, 1.0) + local themeState = utils.getThemeState() + local fg = themeState.primaryColor or lcd.RGB(255, 255, 255) + local bg = themeState.primaryBgColor or themeState.pageBgColor or lcd.RGB(0, 0, 0, 1.0) local cx, cy = x + w / 2, y + h / 2 local radius = math.min(w, h) * (dashboard.overlayScale or 0.35) @@ -144,11 +150,10 @@ function loaders.staticOverlayMessage(dashboard, x, y, w, h, txt) drawOverlayBackground(cx, cy, innerR, bg) - local r, g, b = lcd.darkMode() and 255 or 0, lcd.darkMode() and 255 or 0, lcd.darkMode() and 255 or 0 - lcd.color(lcd.RGB(r, g, b, 1.0)) + lcd.color(themeState.primaryColor or lcd.RGB(255, 255, 255, 1.0)) if lcd.drawFilledCircle then lcd.drawFilledCircle(cx, cy, radius) - lcd.color(lcd.darkMode() and lcd.RGB(0, 0, 0, 1.0) or lcd.RGB(0, 0, 0, 1.0)) + lcd.color(bg) lcd.drawFilledCircle(cx, cy, radius - thickness) end @@ -166,9 +171,9 @@ function loaders.staticOverlayMessage(dashboard, x, y, w, h, txt) for i = 1, 3 do if i == dashboard._dots_index then - lcd.color(lcd.darkMode() and lcd.RGB(255, 255, 255) or lcd.RGB(0, 0, 0)) + lcd.color(themeState.primaryColor or lcd.RGB(255, 255, 255)) else - lcd.color(lcd.darkMode() and lcd.RGB(80, 80, 80) or lcd.RGB(180, 180, 180)) + lcd.color(themeState.disableColor or themeState.secondaryColor or lcd.RGB(80, 80, 80)) end lcd.drawFilledCircle(startX + (i - 1) * spacing, yPos, dotRadius) end diff --git a/src/dashx/widgets/dashboard/lib/toolbar.lua b/src/dashx/widgets/dashboard/lib/toolbar.lua index f1ca289..fe9a8e1 100644 --- a/src/dashx/widgets/dashboard/lib/toolbar.lua +++ b/src/dashx/widgets/dashboard/lib/toolbar.lua @@ -3,28 +3,13 @@ GPLv3 — https://www.gnu.org/licenses/gpl-3.0.en.html ]] -- +local dashx = require("dashx") + local toolbar = {} +local utils = dashx.widgets.dashboard.utils local function getThemeColors() - if lcd.darkMode() then - return { - background = lcd.RGB(18, 22, 26), - panel = lcd.RGB(28, 34, 40), - accent = lcd.RGB(231, 116, 58), - text = lcd.RGB(245, 246, 247), - muted = lcd.RGB(160, 168, 176), - border = lcd.RGB(72, 82, 90) - } - end - - return { - background = lcd.RGB(245, 246, 248), - panel = lcd.RGB(255, 255, 255), - accent = lcd.RGB(215, 98, 38), - text = lcd.RGB(32, 38, 44), - muted = lcd.RGB(108, 116, 124), - border = lcd.RGB(196, 202, 208) - } + return utils.getToolbarTheme() end local function getToolbarItems(dashboard) @@ -84,11 +69,11 @@ local function drawToolbar(dashboard) lcd.color(colors.border) lcd.drawRectangle(itemX, itemY, itemW, itemH, 2) - lcd.color(selected and colors.panel or colors.text) + lcd.color(selected and (colors.accentText or colors.text) or colors.text) lcd.drawText(itemX + math.floor(itemW / 2), itemY + 14, item.name or "@i18n(widgets.dashboard.reset_flight)@", CENTERED) lcd.font(FONT_XXS) - lcd.color(selected and colors.panel or colors.muted) + lcd.color(selected and (colors.accentText or colors.text) or colors.muted) lcd.drawText(itemX + math.floor(itemW / 2), itemY + itemH - 18, item.subtitle or "", CENTERED) lcd.font(FONT_XS) end diff --git a/src/dashx/widgets/dashboard/lib/utils.lua b/src/dashx/widgets/dashboard/lib/utils.lua index 3585369..90a9115 100644 --- a/src/dashx/widgets/dashboard/lib/utils.lua +++ b/src/dashx/widgets/dashboard/lib/utils.lua @@ -10,6 +10,143 @@ local utils = {} local imageCache = {} local fontCache +local THEME_SIGNATURE_MOD = 2147483647 +local THEME_STATE_KEYS = { + {key = "focusBgColor", constant = "THEME_FOCUS_BGCOLOR"}, + {key = "focusColor", constant = "THEME_FOCUS_COLOR"}, + {key = "primaryColor", constant = "THEME_PRIMARY_COLOR"}, + {key = "primaryBgColor", constant = "THEME_PRIMARY_BGCOLOR"}, + {key = "secondaryColor", constant = "THEME_SECONDARY_COLOR"}, + {key = "secondaryBgColor", constant = "THEME_SECONDARY_BGCOLOR"}, + {key = "highlightColor", constant = "THEME_HIGHLIGHT_COLOR"}, + {key = "highlightInvertColor", constant = "THEME_HIGHLIGHT_INVERT_COLOR"}, + {key = "disableColor", constant = "THEME_DISABLE_COLOR"}, + {key = "warningColor", constant = "THEME_WARNING_COLOR"}, + {key = "activeColor", constant = "THEME_ACTIVE_COLOR"}, + {key = "inactiveColor", constant = "THEME_INACTIVE_COLOR"}, + {key = "buttonBorderActiveColor", constant = "THEME_BUTTON_BORDER_ACTIVE_COLOR"}, + {key = "buttonBorderColor", constant = "THEME_BUTTON_BORDER_COLOR"}, + {key = "mixerOutputColor", constant = "THEME_MIXER_OUTPUT_COLOR"}, + {key = "pageBgColor", constant = "THEME_PAGE_BGCOLOR"} +} +local LEGACY_THEME_STATE = { + dark = { + primaryColor = lcd.RGB(255, 255, 255), + primaryBgColor = lcd.RGB(0, 0, 0), + secondaryColor = lcd.RGB(185, 185, 185), + secondaryBgColor = lcd.RGB(40, 40, 40), + focusBgColor = lcd.RGB(40, 40, 40), + focusColor = lcd.RGB(255, 255, 255), + highlightColor = lcd.RGB(255, 255, 255), + highlightInvertColor = lcd.RGB(0, 0, 0), + disableColor = lcd.RGB(90, 90, 90), + warningColor = lcd.RGB(255, 0, 0), + activeColor = lcd.RGB(0, 188, 4), + inactiveColor = lcd.RGB(255, 0, 0), + buttonBorderActiveColor = lcd.RGB(255, 255, 255), + buttonBorderColor = lcd.RGB(90, 90, 90), + mixerOutputColor = lcd.RGB(0, 188, 4), + pageBgColor = lcd.RGB(16, 16, 16) + }, + light = { + primaryColor = lcd.RGB(0, 0, 0), + primaryBgColor = lcd.RGB(255, 255, 255), + secondaryColor = lcd.RGB(90, 90, 90), + secondaryBgColor = lcd.RGB(211, 211, 211), + focusBgColor = lcd.RGB(211, 211, 211), + focusColor = lcd.RGB(0, 0, 0), + highlightColor = lcd.RGB(90, 90, 90), + highlightInvertColor = lcd.RGB(255, 255, 255), + disableColor = lcd.RGB(185, 185, 185), + warningColor = lcd.RGB(255, 0, 0), + activeColor = lcd.RGB(0, 188, 4), + inactiveColor = lcd.RGB(255, 0, 0), + buttonBorderActiveColor = lcd.RGB(90, 90, 90), + buttonBorderColor = lcd.RGB(185, 185, 185), + mixerOutputColor = lcd.RGB(0, 188, 4), + pageBgColor = lcd.RGB(209, 208, 208) + } +} +local LEGACY_CHROME_THEME = { + dark = { + background = lcd.RGB(10, 14, 18), + panel = lcd.RGB(22, 28, 34), + panelAlt = lcd.RGB(17, 22, 27), + button = lcd.RGB(36, 36, 39), + buttonDisabled = lcd.RGB(24, 24, 27), + text = lcd.RGB(245, 246, 247), + muted = lcd.RGB(166, 174, 182), + accent = lcd.RGB(231, 116, 58), + accentSoft = lcd.RGB(72, 44, 30), + accentText = lcd.RGB(245, 246, 247), + accentBorder = lcd.RGB(231, 116, 58), + border = lcd.RGB(86, 96, 106) + }, + light = { + background = lcd.RGB(246, 247, 249), + panel = lcd.RGB(255, 255, 255), + panelAlt = lcd.RGB(240, 242, 245), + button = lcd.RGB(232, 234, 237), + buttonDisabled = lcd.RGB(242, 244, 246), + text = lcd.RGB(28, 34, 40), + muted = lcd.RGB(108, 116, 124), + accent = lcd.RGB(215, 98, 38), + accentSoft = lcd.RGB(255, 226, 210), + accentText = lcd.RGB(28, 34, 40), + accentBorder = lcd.RGB(215, 98, 38), + border = lcd.RGB(196, 202, 208) + } +} +local LEGACY_TOOLBAR_THEME = { + dark = { + background = lcd.RGB(18, 22, 26), + panel = lcd.RGB(28, 34, 40), + accent = lcd.RGB(231, 116, 58), + accentText = lcd.RGB(28, 34, 40), + text = lcd.RGB(245, 246, 247), + muted = lcd.RGB(160, 168, 176), + border = lcd.RGB(72, 82, 90) + }, + light = { + background = lcd.RGB(245, 246, 248), + panel = lcd.RGB(255, 255, 255), + accent = lcd.RGB(215, 98, 38), + accentText = lcd.RGB(255, 255, 255), + text = lcd.RGB(32, 38, 44), + muted = lcd.RGB(108, 116, 124), + border = lcd.RGB(196, 202, 208) + } +} +local cachedThemeSignature +local cachedThemeState +local cachedDashboardTheme +local cachedChromeTheme +local cachedToolbarTheme + +local function isLegacyDarkMode() + return lcd.darkMode and lcd.darkMode() or false +end + +local function resolveSystemThemeColor(constantName) + local themeColor = lcd.themeColor + if type(themeColor) ~= "function" then + return nil + end + + local constant = _G[constantName] + if constant == nil then + return nil + end + + return themeColor(constant) +end + +local function copyThemeMap(target, source) + for key, value in pairs(source) do + target[key] = value + end +end + function utils.standardHeaderLayout(headeropts) return {height = headeropts.height, cols = 7, rows = 1} end function utils.standardHeaderBoxes(i18n, colorMode, headeropts, txbatt_type) @@ -145,48 +282,6 @@ function utils.getTxBatteryVoltageRange() return 7.2, 8.4 end -function utils.themeColors() - local colorMode = { - dark = { - textcolor = "white", - titlecolor = "white", - bgcolor = "black", - fillcolor = "green", - fillwarncolor = "orange", - fillcritcolor = "red", - fillbgcolor = "grey", - accentcolor = "white", - rssifillcolor = "green", - rssifillbgcolor = "darkgrey", - txaccentcolor = "grey", - txfillcolor = "green", - txbgfillcolor = "darkgrey", - tbbgcolor = "headergrey", - cntextcolor = "white", - tbtextcolor = "white" - }, - light = { - textcolor = "lmgrey", - titlecolor = "lmgrey", - bgcolor = "white", - fillcolor = "lightgreen", - fillwarncolor = "lightorange", - fillcritcolor = "lightred", - fillbgcolor = "lightgrey", - accentcolor = "darkgrey", - rssifillcolor = "lightgreen", - rssifillbgcolor = "grey", - txaccentcolor = "white", - txfillcolor = "lightgreen", - txbgfillcolor = "grey", - tbbgcolor = "darkgrey", - cntextcolor = "white", - tbtextcolor = "white" - } - } - return lcd.darkMode() and colorMode.dark or colorMode.light -end - function utils.isFullScreen(w, h) if (w == 800 and (h == 458 or h == 480)) then return true end @@ -211,6 +306,138 @@ function utils.supportedResolution(W, H, supportedResolutions) return false end +function utils.getThemeSignature() + if type(lcd.themeColor) ~= "function" then + return isLegacyDarkMode() and 1 or 0 + end + + local signature = 97 + local hasThemeColors = false + + for index = 1, #THEME_STATE_KEYS do + local color = resolveSystemThemeColor(THEME_STATE_KEYS[index].constant) + if color ~= nil then + hasThemeColors = true + signature = (signature * 131 + (tonumber(color) or 0)) % THEME_SIGNATURE_MOD + else + signature = (signature * 131 + index) % THEME_SIGNATURE_MOD + end + end + + if not hasThemeColors then + return isLegacyDarkMode() and 1 or 0 + end + + return signature + 2 +end + +local function ensureThemeCache() + local signature = utils.getThemeSignature() + if cachedThemeSignature == signature and cachedThemeState and cachedDashboardTheme and cachedChromeTheme and cachedToolbarTheme then + return + end + + local darkMode = isLegacyDarkMode() + local baseState = darkMode and LEGACY_THEME_STATE.dark or LEGACY_THEME_STATE.light + local themeState = cachedThemeState or {} + local dashboardTheme = cachedDashboardTheme or {} + local chromeTheme = cachedChromeTheme or {} + local toolbarTheme = cachedToolbarTheme or {} + local focusFill + local focusText + + copyThemeMap(themeState, baseState) + themeState.darkMode = darkMode + themeState.usesThemeColors = false + + for index = 1, #THEME_STATE_KEYS do + local entry = THEME_STATE_KEYS[index] + local color = resolveSystemThemeColor(entry.constant) + if color ~= nil then + themeState[entry.key] = color + themeState.usesThemeColors = true + end + end + + themeState.signature = signature + + dashboardTheme.textcolor = themeState.primaryColor + dashboardTheme.titlecolor = themeState.primaryColor + dashboardTheme.bgcolor = themeState.primaryBgColor + dashboardTheme.fillcolor = themeState.activeColor + dashboardTheme.fillbgcolor = themeState.secondaryBgColor + dashboardTheme.framecolor = themeState.buttonBorderColor or themeState.secondaryColor + dashboardTheme.accentcolor = themeState.buttonBorderActiveColor or themeState.primaryColor + dashboardTheme.rssifillcolor = themeState.activeColor + dashboardTheme.rssifillbgcolor = themeState.secondaryBgColor + dashboardTheme.txaccentcolor = themeState.secondaryColor + dashboardTheme.txfillcolor = themeState.activeColor + dashboardTheme.txbgfillcolor = themeState.secondaryBgColor + dashboardTheme.bgcolortop = themeState.pageBgColor + dashboardTheme.pagebgcolor = themeState.pageBgColor + dashboardTheme.fillwarncolor = themeState.warningColor + dashboardTheme.fillcritcolor = themeState.inactiveColor or themeState.warningColor + dashboardTheme.tbbgcolor = themeState.pageBgColor or themeState.primaryBgColor + dashboardTheme.cntextcolor = themeState.primaryColor + dashboardTheme.tbtextcolor = themeState.primaryColor + dashboardTheme.rssitextcolor = themeState.primaryColor + + if themeState.usesThemeColors then + focusFill = themeState.focusBgColor or themeState.highlightColor or themeState.buttonBorderActiveColor or themeState.activeColor + focusText = themeState.focusColor or themeState.highlightInvertColor or themeState.primaryColor + + chromeTheme.background = themeState.pageBgColor or themeState.primaryBgColor + chromeTheme.panel = themeState.primaryBgColor + chromeTheme.panelAlt = themeState.secondaryBgColor + chromeTheme.button = themeState.secondaryBgColor + chromeTheme.buttonDisabled = themeState.primaryBgColor + chromeTheme.text = themeState.primaryColor + chromeTheme.muted = themeState.secondaryColor + chromeTheme.accent = focusFill + chromeTheme.accentSoft = focusFill + chromeTheme.accentText = focusText + chromeTheme.accentBorder = themeState.buttonBorderActiveColor or focusText + chromeTheme.border = themeState.buttonBorderColor or themeState.secondaryColor + + toolbarTheme.background = themeState.pageBgColor or themeState.primaryBgColor + toolbarTheme.panel = themeState.secondaryBgColor + toolbarTheme.accent = focusFill + toolbarTheme.accentText = focusText + toolbarTheme.text = themeState.primaryColor + toolbarTheme.muted = themeState.secondaryColor + toolbarTheme.border = themeState.buttonBorderColor or themeState.secondaryColor + else + copyThemeMap(chromeTheme, darkMode and LEGACY_CHROME_THEME.dark or LEGACY_CHROME_THEME.light) + copyThemeMap(toolbarTheme, darkMode and LEGACY_TOOLBAR_THEME.dark or LEGACY_TOOLBAR_THEME.light) + end + + cachedThemeSignature = signature + cachedThemeState = themeState + cachedDashboardTheme = dashboardTheme + cachedChromeTheme = chromeTheme + cachedToolbarTheme = toolbarTheme +end + +function utils.themeColors() + ensureThemeCache() + return cachedDashboardTheme +end + +function utils.getThemeState() + ensureThemeCache() + return cachedThemeState +end + +function utils.getChromeTheme() + ensureThemeCache() + return cachedChromeTheme +end + +function utils.getToolbarTheme() + ensureThemeCache() + return cachedToolbarTheme +end + function utils.drawBarNeedle(cx, cy, length, thickness, angleDeg, color) local angleRad = math.rad(angleDeg) local step = 1 @@ -338,7 +565,7 @@ function utils.screenError(msg, border, pct, padX, padY) if not padY then padY = 4 end local w, h = lcd.getWindowSize() - local isDarkMode = lcd.darkMode() + local themeState = utils.getThemeState() local fonts = {FONT_XXS, FONT_XS, FONT_S, FONT_M, FONT_L, FONT_XL, FONT_XXL, FONT_XXXXL} @@ -358,7 +585,7 @@ function utils.screenError(msg, border, pct, padX, padY) lcd.font(bestFont) - local textColor = isDarkMode and lcd.RGB(255, 255, 255, 1) or lcd.RGB(90, 90, 90) + local textColor = themeState.primaryColor or lcd.RGB(255, 255, 255, 1) lcd.color(textColor) local x = (w - bestW) / 2 @@ -466,21 +693,48 @@ function utils.resolveThemeColor(colorkey, value) if resolved then return resolved end end + local themeColors = utils.themeColors() if colorkey == "fillcolor" then - return lcd.darkMode() and lcd.RGB(40, 40, 40) or lcd.RGB(240, 240, 240) + return themeColors.fillcolor elseif colorkey == "fillbgcolor" then - return lcd.darkMode() and lcd.RGB(40, 40, 40) or lcd.RGB(240, 240, 240) + return themeColors.fillbgcolor elseif colorkey == "framecolor" then - return lcd.darkMode() and lcd.RGB(40, 40, 40) or lcd.RGB(240, 240, 240) + return themeColors.framecolor elseif colorkey == "textcolor" then - return lcd.RGB(255, 255, 255) + return themeColors.textcolor elseif colorkey == "titlecolor" then - return lcd.RGB(255, 255, 255) + return themeColors.titlecolor elseif colorkey == "accentcolor" then - return lcd.RGB(255, 255, 255) + return themeColors.accentcolor + elseif colorkey == "bgcolor" then + return themeColors.bgcolor + elseif colorkey == "bgcolortop" then + return themeColors.bgcolortop + elseif colorkey == "fillwarncolor" then + return themeColors.fillwarncolor + elseif colorkey == "fillcritcolor" then + return themeColors.fillcritcolor + elseif colorkey == "tbbgcolor" then + return themeColors.tbbgcolor + elseif colorkey == "cntextcolor" then + return themeColors.cntextcolor + elseif colorkey == "tbtextcolor" then + return themeColors.tbtextcolor + elseif colorkey == "rssitextcolor" then + return themeColors.rssitextcolor + elseif colorkey == "rssifillcolor" then + return themeColors.rssifillcolor + elseif colorkey == "rssifillbgcolor" then + return themeColors.rssifillbgcolor + elseif colorkey == "txaccentcolor" then + return themeColors.txaccentcolor + elseif colorkey == "txfillcolor" then + return themeColors.txfillcolor + elseif colorkey == "txbgfillcolor" then + return themeColors.txbgfillcolor end - return lcd.darkMode() and lcd.RGB(40, 40, 40) or lcd.RGB(240, 240, 240) + return themeColors.bgcolor end function utils.resolveThemeColorArray(colorkey, arr) @@ -702,11 +956,8 @@ end function utils.setBackgroundColourBasedOnTheme() local w, h = lcd.getWindowSize() - if lcd.darkMode() then - lcd.color(lcd.RGB(16, 16, 16)) - else - lcd.color(lcd.RGB(209, 208, 208)) - end + local themeState = utils.getThemeState() + lcd.color(themeState.pageBgColor or themeState.primaryBgColor or lcd.RGB(16, 16, 16)) lcd.drawFilledRectangle(0, 0, w, h) end diff --git a/src/dashx/widgets/dashboard/themes/@rt-rc/inflight.lua b/src/dashx/widgets/dashboard/themes/@rt-rc/inflight.lua index f865289..88f01d7 100644 --- a/src/dashx/widgets/dashboard/themes/@rt-rc/inflight.lua +++ b/src/dashx/widgets/dashboard/themes/@rt-rc/inflight.lua @@ -10,36 +10,6 @@ local boxes_cache = nil local themeconfig = nil local lastScreenW = nil -local darkMode = { - textcolor = "white", - titlecolor = "white", - bgcolor = "black", - fillcolor = "green", - fillbgcolor = "darkgrey", - accentcolor = "white", - rssifillcolor = "green", - rssifillbgcolor = "darkgrey", - txaccentcolor = "grey", - txfillcolor = "green", - txbgfillcolor = "darkgrey", - bgcolortop = lcd.RGB(10, 10, 10) -} - -local lightMode = { - textcolor = "black", - titlecolor = "black", - bgcolor = "white", - fillcolor = "green", - fillbgcolor = "lightgrey", - accentcolor = "darkgrey", - rssifillcolor = "green", - rssifillbgcolor = "grey", - txaccentcolor = "darkgrey", - txfillcolor = "green", - txbgfillcolor = "grey", - bgcolortop = "grey" -} - local function getUserVoltageOverride(which) local prefs = dashx.session and dashx.session.modelPreferences if prefs and prefs["system/@default"] then @@ -51,7 +21,7 @@ local function getUserVoltageOverride(which) return nil end -local colorMode = lcd.darkMode() and darkMode or lightMode +local colorMode = utils.themeColors() local theme_section = "system/@default" diff --git a/src/dashx/widgets/dashboard/themes/@rt-rc/postflight.lua b/src/dashx/widgets/dashboard/themes/@rt-rc/postflight.lua index 5ebc858..8c0c7ba 100644 --- a/src/dashx/widgets/dashboard/themes/@rt-rc/postflight.lua +++ b/src/dashx/widgets/dashboard/themes/@rt-rc/postflight.lua @@ -10,36 +10,6 @@ local boxes_cache = nil local themeconfig = nil local lastScreenW = nil -local darkMode = { - textcolor = "white", - titlecolor = "white", - bgcolor = "black", - fillcolor = "green", - fillbgcolor = "darkgrey", - accentcolor = "white", - rssifillcolor = "green", - rssifillbgcolor = "darkgrey", - txaccentcolor = "grey", - txfillcolor = "green", - txbgfillcolor = "darkgrey", - bgcolortop = lcd.RGB(10, 10, 10) -} - -local lightMode = { - textcolor = "black", - titlecolor = "black", - bgcolor = "white", - fillcolor = "green", - fillbgcolor = "lightgrey", - accentcolor = "darkgrey", - rssifillcolor = "green", - rssifillbgcolor = "grey", - txaccentcolor = "darkgrey", - txfillcolor = "green", - txbgfillcolor = "grey", - bgcolortop = "grey" -} - local function getUserVoltageOverride(which) local prefs = dashx.session and dashx.session.modelPreferences if prefs and prefs["system/@default"] then @@ -51,7 +21,7 @@ local function getUserVoltageOverride(which) return nil end -local colorMode = lcd.darkMode() and darkMode or lightMode +local colorMode = utils.themeColors() local theme_section = "system/@default" diff --git a/src/dashx/widgets/dashboard/themes/@rt-rc/preflight.lua b/src/dashx/widgets/dashboard/themes/@rt-rc/preflight.lua index 13cce70..5553b0b 100644 --- a/src/dashx/widgets/dashboard/themes/@rt-rc/preflight.lua +++ b/src/dashx/widgets/dashboard/themes/@rt-rc/preflight.lua @@ -10,36 +10,6 @@ local boxes_cache = nil local themeconfig = nil local lastScreenW = nil -local darkMode = { - textcolor = "white", - titlecolor = "white", - bgcolor = "black", - fillcolor = "green", - fillbgcolor = "darkgrey", - accentcolor = "white", - rssifillcolor = "green", - rssifillbgcolor = "darkgrey", - txaccentcolor = "grey", - txfillcolor = "green", - txbgfillcolor = "darkgrey", - bgcolortop = lcd.RGB(10, 10, 10) -} - -local lightMode = { - textcolor = "black", - titlecolor = "black", - bgcolor = "white", - fillcolor = "green", - fillbgcolor = "lightgrey", - accentcolor = "darkgrey", - rssifillcolor = "green", - rssifillbgcolor = "grey", - txaccentcolor = "darkgrey", - txfillcolor = "green", - txbgfillcolor = "grey", - bgcolortop = "grey" -} - local function getUserVoltageOverride(which) local prefs = dashx.session and dashx.session.modelPreferences if prefs and prefs["system/@default"] then @@ -51,7 +21,7 @@ local function getUserVoltageOverride(which) return nil end -local colorMode = lcd.darkMode() and darkMode or lightMode +local colorMode = utils.themeColors() local theme_section = "system/@default" diff --git a/src/dashx/widgets/dashboard/themes/default/inflight.lua b/src/dashx/widgets/dashboard/themes/default/inflight.lua index c7dcf4e..36c67d1 100644 --- a/src/dashx/widgets/dashboard/themes/default/inflight.lua +++ b/src/dashx/widgets/dashboard/themes/default/inflight.lua @@ -10,36 +10,6 @@ local boxes_cache = nil local themeconfig = nil local lastScreenW = nil -local darkMode = { - textcolor = "white", - titlecolor = "white", - bgcolor = "black", - fillcolor = "green", - fillbgcolor = "darkgrey", - accentcolor = "white", - rssifillcolor = "green", - rssifillbgcolor = "darkgrey", - txaccentcolor = "grey", - txfillcolor = "green", - txbgfillcolor = "darkgrey", - bgcolortop = lcd.RGB(10, 10, 10) -} - -local lightMode = { - textcolor = "black", - titlecolor = "black", - bgcolor = "white", - fillcolor = "green", - fillbgcolor = "lightgrey", - accentcolor = "darkgrey", - rssifillcolor = "green", - rssifillbgcolor = "grey", - txaccentcolor = "darkgrey", - txfillcolor = "green", - txbgfillcolor = "grey", - bgcolortop = "grey" -} - local function getUserVoltageOverride(which) local prefs = dashx.session and dashx.session.modelPreferences if prefs and prefs["system/@default"] then @@ -51,7 +21,7 @@ local function getUserVoltageOverride(which) return nil end -local colorMode = lcd.darkMode() and darkMode or lightMode +local colorMode = utils.themeColors() local theme_section = "system/@default" diff --git a/src/dashx/widgets/dashboard/themes/default/postflight.lua b/src/dashx/widgets/dashboard/themes/default/postflight.lua index 2958697..445e05d 100644 --- a/src/dashx/widgets/dashboard/themes/default/postflight.lua +++ b/src/dashx/widgets/dashboard/themes/default/postflight.lua @@ -10,36 +10,6 @@ local boxes_cache = nil local themeconfig = nil local lastScreenW = nil -local darkMode = { - textcolor = "white", - titlecolor = "white", - bgcolor = "black", - fillcolor = "green", - fillbgcolor = "darkgrey", - accentcolor = "white", - rssifillcolor = "green", - rssifillbgcolor = "darkgrey", - txaccentcolor = "grey", - txfillcolor = "green", - txbgfillcolor = "darkgrey", - bgcolortop = lcd.RGB(10, 10, 10) -} - -local lightMode = { - textcolor = "black", - titlecolor = "black", - bgcolor = "white", - fillcolor = "green", - fillbgcolor = "lightgrey", - accentcolor = "darkgrey", - rssifillcolor = "green", - rssifillbgcolor = "grey", - txaccentcolor = "darkgrey", - txfillcolor = "green", - txbgfillcolor = "grey", - bgcolortop = "grey" -} - local function getUserVoltageOverride(which) local prefs = dashx.session and dashx.session.modelPreferences if prefs and prefs["system/@default"] then @@ -51,7 +21,7 @@ local function getUserVoltageOverride(which) return nil end -local colorMode = lcd.darkMode() and darkMode or lightMode +local colorMode = utils.themeColors() local theme_section = "system/@default" diff --git a/src/dashx/widgets/dashboard/themes/default/preflight.lua b/src/dashx/widgets/dashboard/themes/default/preflight.lua index afbbf89..22655b7 100644 --- a/src/dashx/widgets/dashboard/themes/default/preflight.lua +++ b/src/dashx/widgets/dashboard/themes/default/preflight.lua @@ -10,36 +10,6 @@ local boxes_cache = nil local themeconfig = nil local lastScreenW = nil -local darkMode = { - textcolor = "white", - titlecolor = "white", - bgcolor = "black", - fillcolor = "green", - fillbgcolor = "darkgrey", - accentcolor = "white", - rssifillcolor = "green", - rssifillbgcolor = "darkgrey", - txaccentcolor = "grey", - txfillcolor = "green", - txbgfillcolor = "darkgrey", - bgcolortop = lcd.RGB(10, 10, 10) -} - -local lightMode = { - textcolor = "black", - titlecolor = "black", - bgcolor = "white", - fillcolor = "green", - fillbgcolor = "lightgrey", - accentcolor = "darkgrey", - rssifillcolor = "green", - rssifillbgcolor = "grey", - txaccentcolor = "darkgrey", - txfillcolor = "green", - txbgfillcolor = "grey", - bgcolortop = "grey" -} - local function getUserVoltageOverride(which) local prefs = dashx.session and dashx.session.modelPreferences if prefs and prefs["system/@default"] then @@ -51,7 +21,7 @@ local function getUserVoltageOverride(which) return nil end -local colorMode = lcd.darkMode() and darkMode or lightMode +local colorMode = utils.themeColors() local theme_section = "system/@default"