#pragma once #include "configuration.h" #include namespace graphics { struct TFTColorRegion { int16_t x; int16_t y; int16_t width; int16_t height; uint16_t onColorBe; uint16_t offColorBe; // Required by ST7789 driver: it scans until the first disabled entry. bool enabled = false; }; static constexpr size_t MAX_TFT_COLOR_REGIONS = 48; extern TFTColorRegion colorRegions[MAX_TFT_COLOR_REGIONS]; enum class TFTColorRole : uint8_t { HeaderBackground = 0, HeaderTitle, HeaderStatus, SignalBars, ConnectionIcon, UtilizationFill, FavoriteNode, ActionMenuBorder, ActionMenuBody, ActionMenuTitle, FrameMono, BootSplash, FavoriteNodeBGHighlight, NavigationBar, NavigationArrow, Count }; #if HAS_TFT || defined(HAS_SPI_TFT) #define GRAPHICS_TFT_COLORING_ENABLED 1 #else #define GRAPHICS_TFT_COLORING_ENABLED 0 #endif static constexpr bool kTFTColoringEnabled = GRAPHICS_TFT_COLORING_ENABLED != 0; constexpr bool isTFTColoringEnabled() { return kTFTColoringEnabled; } void setTFTColorRole(TFTColorRole role, uint16_t onColor, uint16_t offColor); void registerTFTColorRegion(TFTColorRole role, int16_t x, int16_t y, int16_t width, int16_t height); // Convenience helper for the common "set role then register one region" flow. void setAndRegisterTFTColorRole(TFTColorRole role, uint16_t onColor, uint16_t offColor, int16_t x, int16_t y, int16_t width, int16_t height); // Register a region using explicit colors (no role lookup). Use when the // color comes from a theme field rather than a role (e.g. battery fill). void registerTFTColorRegionDirect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t onColor, uint16_t offColor); void registerTFTActionMenuRegions(int16_t boxLeft, int16_t boxTop, int16_t boxWidth, int16_t boxHeight); uint32_t getTFTColorFrameSignature(); uint8_t getTFTColorRegionCount(); void clearTFTColorRegions(); uint16_t resolveTFTColorPixel(int16_t x, int16_t y, bool isset, uint16_t defaultOnColor, uint16_t defaultOffColor); // Resolve effective region-mapped OFF color at a coordinate in native-endian RGB565. uint16_t resolveTFTOffColorAt(int16_t x, int16_t y, uint16_t defaultOffColor); // -- Per-row fast path for the hot pixel loops in TFTDisplay::display() -------- // resolveTFTColorPixel() is O(regions) per pixel; for a full 800x480 repaint that // dominates redraw time. Regions are vertically localized, so cull to the regions // overlapping the current row once per row, then resolve each pixel against only // those (inlined here, so no per-pixel cross-TU call). extern uint8_t tftColorRowRegions[MAX_TFT_COLOR_REGIONS]; // indices into colorRegions[], ascending extern uint8_t tftColorRowCount; // Build tftColorRowRegions for row y. Call once per row before resolveTFTColorPixelRow(). void beginTFTColorRow(int16_t y); // Resolve one pixel against the current row's active regions (set by beginTFTColorRow()). // Highest-index region wins, matching resolveTFTColorPixel()'s precedence. inline uint16_t resolveTFTColorPixelRow(int16_t x, bool isset, uint16_t defaultOnColor, uint16_t defaultOffColor) { for (int j = static_cast(tftColorRowCount) - 1; j >= 0; j--) { const TFTColorRegion &r = colorRegions[tftColorRowRegions[j]]; if (x >= r.x && x < r.x + r.width) return isset ? r.onColorBe : r.offColorBe; } return isset ? defaultOnColor : defaultOffColor; } // -- Theme engine ------------------------------------------------------ // Each theme has four fields that work together: // // id - ThemeID:: constant, used for in-code references. // name - human-readable label shown in the theme picker. // uniqueIdentifier - the stable numeric value persisted to // uiconfig.screen_rgb_color and restored at boot. // This is a CONTRACT with saved configs on disk - once // assigned, never reuse or renumber, even if the theme is // deleted or the kThemes[] array is reordered. // visible - controls whether a theme appears in the picker menu. // Hidden themes can still be restored and applied if their // uniqueIdentifier is persisted. // // Display order in the menu is controlled by kThemes[] array order among // themes where visible == true, NOT by any numeric value above. // // To add a new theme: // 1. Add a unique constant in ThemeID below (next unused value). // 2. Add a kThemes[] entry at the desired menu position, with a unique // uniqueIdentifier that has never been used by any prior theme. // 3. Set visible=true if it should appear in the picker. // // To retire a theme without breaking saved configs: // - Preferred: keep the entry and set visible=false so existing saved // uniqueIdentifier values still resolve to the same theme. // - If you remove the entry, resolveThemeIndex() falls back to DefaultDark // when the persisted uniqueIdentifier no longer matches any theme. // - Do NOT reuse a retired uniqueIdentifier for a future theme. namespace ThemeID { constexpr uint32_t DefaultDark = 0; constexpr uint32_t DefaultLight = 1; constexpr uint32_t Christmas = 2; constexpr uint32_t Pink = 3; constexpr uint32_t Blue = 4; constexpr uint32_t Creamsicle = 5; constexpr uint32_t MeshtasticGreen = 6; constexpr uint32_t ClassicRed = 7; constexpr uint32_t MonochromeWhite = 8; } // namespace ThemeID // Per-role color pair stored in native (little-endian) RGB565 format. struct TFTThemeRoleColor { uint16_t onColor; uint16_t offColor; }; // Complete theme definition. struct TFTThemeDef { uint32_t id; // ThemeID constant - in-code identifier for this theme. const char *name; // Human-readable label shown in the theme picker. uint32_t uniqueIdentifier; // Stable persisted value copied into uiconfig.screen_rgb_color. // Never reuse or renumber - see file-level notes above. TFTThemeRoleColor roles[static_cast(TFTColorRole::Count)]; uint16_t batteryFillGood; uint16_t batteryFillMedium; uint16_t batteryFillBad; bool fullFrameInvert; // Apply full-frame FrameMono inversion (ST7789 light themes) bool visible; // Show in the theme picker menu. Hidden themes still apply // correctly if their uniqueIdentifier is persisted (dev/legacy themes). }; // Count of themes whose .visible flag is true. Use this when building menus. size_t getVisibleThemeCount(); // Access the Nth visible theme (0 .. getVisibleThemeCount()-1). Hidden themes // are skipped, preserving kThemes[] order among the visible entries. const TFTThemeDef &getVisibleThemeByIndex(size_t visibleIndex); // Return the theme that matches uiconfig.screen_rgb_color (falls back to Dark). const TFTThemeDef &getActiveTheme(); // Return the visible-theme index for the currently active theme, or SIZE_MAX // if the active theme is hidden (so menus can show "no selection"). size_t getActiveVisibleThemeIndex(); // Convenience accessors - safe to call even when coloring is compiled out. uint16_t getThemeHeaderBg(); uint16_t getThemeHeaderText(); uint16_t getThemeHeaderStatus(); uint16_t getThemeBodyBg(); uint16_t getThemeBodyFg(); bool isThemeFullFrameInvert(); uint16_t getThemeBatteryFillColor(int batteryPercent); // Reinitialise default roleColors from the active theme. Call after a // theme change so that any role registered without a prior setTFTColorRole() // picks up theme-appropriate defaults. void loadThemeDefaults(); } // namespace graphics