From 9cb6d7394745ef5185d28d8e778632fb8ee5f043 Mon Sep 17 00:00:00 2001 From: Kevin Koenig Date: Mon, 4 Oct 2021 08:27:08 -0400 Subject: [PATCH] Implementation of Theme Switching and Persistence (#839) * initial support for color theming using yml files in the THEME directory * initial theming support using yml files (without yml support) * fixed issues with themes actually working on radio. * fix another issue with drive access * more theme changes. Added author, and info to display. Also display an image of the theme if it is present. * theme files now support both RGB() and 0x syntax * quick fix for possible variable overflow. * more tweaks after JimB tested * fixed layout issues with NV14 and also added word wrap correctly. * added edgetx headers to new string functions. * Whitespace / unused code cleanup Co-authored-by: kkoenig1@optonline.com Co-authored-by: Peter Feerick --- radio/src/gui/colorlcd/CMakeLists.txt | 2 + radio/src/gui/colorlcd/file_preview.h | 66 +++ radio/src/gui/colorlcd/radio_sdmanager.cpp | 43 +- radio/src/gui/colorlcd/screen_setup.cpp | 65 ++- radio/src/gui/colorlcd/str_functions.cpp | 132 ++++++ radio/src/gui/colorlcd/str_functions.h | 29 ++ radio/src/gui/colorlcd/theme_manager.cpp | 383 ++++++++++++++++++ radio/src/gui/colorlcd/theme_manager.h | 134 ++++++ radio/src/gui/colorlcd/themes/480_default.cpp | 2 + 9 files changed, 806 insertions(+), 50 deletions(-) create mode 100644 radio/src/gui/colorlcd/file_preview.h create mode 100644 radio/src/gui/colorlcd/str_functions.cpp create mode 100644 radio/src/gui/colorlcd/str_functions.h create mode 100644 radio/src/gui/colorlcd/theme_manager.cpp create mode 100644 radio/src/gui/colorlcd/theme_manager.h diff --git a/radio/src/gui/colorlcd/CMakeLists.txt b/radio/src/gui/colorlcd/CMakeLists.txt index 9e065f5a6..7844a268e 100644 --- a/radio/src/gui/colorlcd/CMakeLists.txt +++ b/radio/src/gui/colorlcd/CMakeLists.txt @@ -11,6 +11,7 @@ file(GLOB WIDGETS_SRC RELATIVE ${RADIO_SRC_DIR}/gui/colorlcd ${RADIO_SRC_DIR}/gu set(GUI_SRC ${GUI_SRC} + str_functions.cpp colors.cpp lcd.cpp splash.cpp @@ -18,6 +19,7 @@ set(GUI_SRC curves.cpp bitmaps.cpp theme.cpp + theme_manager.cpp tabsgroup.cpp page.cpp fab_button.cpp diff --git a/radio/src/gui/colorlcd/file_preview.h b/radio/src/gui/colorlcd/file_preview.h new file mode 100644 index 000000000..04202b755 --- /dev/null +++ b/radio/src/gui/colorlcd/file_preview.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once +#include "libopenui.h" + +class FilePreview : public Window +{ + public: + FilePreview(Window *parent, const rect_t &rect) : + Window(parent, rect, NO_SCROLLBAR) + { + } + +#if defined(DEBUG_WINDOWS) + std::string getName() const override + { + return "FilePreview"; + } +#endif + + void setFile(const char *filename) + { + delete bitmap; + const char *ext = getFileExtension(filename); + if (ext && isExtensionMatching(ext, BITMAPS_EXT)) { + bitmap = BitmapBuffer::loadBitmap(filename); + } else { + bitmap = nullptr; + } + invalidate(); + } + + void paint(BitmapBuffer *dc) override + { + coord_t y = parent->getScrollPositionY() + 2; + coord_t h = MENU_BODY_HEIGHT - 4; + if (bitmap) { + coord_t bitmapHeight = min(h, bitmap->height()); + coord_t bitmapWidth = min(width(), bitmap->width()); + dc->drawScaledBitmap(bitmap, (width() - bitmapWidth) / 2, y + (h - bitmapHeight) / 2, bitmapWidth, + bitmapHeight); + } + } + + protected: + BitmapBuffer *bitmap = nullptr; +}; diff --git a/radio/src/gui/colorlcd/radio_sdmanager.cpp b/radio/src/gui/colorlcd/radio_sdmanager.cpp index e7e06c631..e2000328d 100644 --- a/radio/src/gui/colorlcd/radio_sdmanager.cpp +++ b/radio/src/gui/colorlcd/radio_sdmanager.cpp @@ -28,6 +28,7 @@ #include "standalone_lua.h" #include "sdcard.h" #include "view_text.h" +#include "file_preview.h" class FileNameEditWindow : public Page { @@ -128,48 +129,6 @@ char * getCurrentPath() return path; } -class FilePreview : public Window -{ - public: - FilePreview(Window *parent, const rect_t &rect) : - Window(parent, rect, NO_SCROLLBAR) - { - } - -#if defined(DEBUG_WINDOWS) - std::string getName() const override - { - return "FilePreview"; - } -#endif - - void setFile(const char *filename) - { - delete bitmap; - const char *ext = getFileExtension(filename); - if (ext && isExtensionMatching(ext, BITMAPS_EXT)) { - bitmap = BitmapBuffer::loadBitmap(filename); - } else { - bitmap = nullptr; - } - invalidate(); - } - - void paint(BitmapBuffer *dc) override - { - coord_t y = parent->getScrollPositionY() + 2; - coord_t h = MENU_BODY_HEIGHT - 4; - if (bitmap) { - coord_t bitmapHeight = min(h, bitmap->height()); - coord_t bitmapWidth = min(width(), bitmap->width()); - dc->drawScaledBitmap(bitmap, (width() - bitmapWidth) / 2, y + (h - bitmapHeight) / 2, bitmapWidth, - bitmapHeight); - } - } - - protected: - BitmapBuffer *bitmap = nullptr; -}; template class FlashDialog: public FullScreenDialog diff --git a/radio/src/gui/colorlcd/screen_setup.cpp b/radio/src/gui/colorlcd/screen_setup.cpp index 66fb686ab..97d98fef6 100644 --- a/radio/src/gui/colorlcd/screen_setup.cpp +++ b/radio/src/gui/colorlcd/screen_setup.cpp @@ -27,6 +27,8 @@ #include "topbar.h" #include "libopenui.h" #include "layouts/layout_factory_impl.h" +#include "theme_manager.h" +#include "file_preview.h" #define SET_DIRTY() storageDirty(EE_MODEL) @@ -265,14 +267,7 @@ ScreenUserInterfacePage::ScreenUserInterfacePage(ScreenMenu* menu): void ScreenUserInterfacePage::build(FormWindow * window) { - FormGridLayout grid; - - // Theme choice - new StaticText(window, grid.getLabelSlot(), STR_THEME, 0, COLOR_THEME_PRIMARY1); - // TODO: Theme picklist - grid.nextLine(); - - // Theme options ? + FormGridLayout grid(LCD_W, 10); // Top Bar new StaticText(window, grid.getLabelSlot(), STR_TOP_BAR, 0, COLOR_THEME_PRIMARY1); @@ -283,6 +278,60 @@ void ScreenUserInterfacePage::build(FormWindow * window) new SetupTopBarWidgetsPage(menu); return 0; }); + + grid.nextLine(); + grid.spacer(8); + + // Theme choice + new StaticText(window, grid.getLabelSlot(), STR_THEME, 0, COLOR_THEME_PRIMARY1); + auto tp = ThemePersistance::instance(); + tp->refresh(); + std::vector names = tp->getNames(); + new Choice(window, grid.getFieldSlot(), names, 0, names.size() - 1, + [=] () { + return tp->getThemeIndex(); + }, + [=] (int value) { + tp->setThemeIndex(value); + tp->applyTheme(value); + tp->setDefaultTheme(value); + + window->clear(); + build(window); + }); + + bool bNarrowScreen = LCD_W < LCD_H; + + if (bNarrowScreen) + grid.setLabelWidth(LCD_W); + + grid.nextLine(); + auto theme = tp->getCurrentTheme(); + auto themeImage = theme->getThemeImageFileName(); + + grid.spacer(8); + + new StaticText(window, grid.getLabelSlot(), "Author", 0, COLOR_THEME_PRIMARY1 | FONT(BOLD)); + grid.nextLine(); + new StaticText(window, grid.getLabelSlot(), theme->getAuthor(), 0, COLOR_THEME_PRIMARY1); + grid.nextLine(); + new StaticText(window, grid.getLabelSlot(), "Description", 0, COLOR_THEME_PRIMARY1 | FONT(BOLD)); + grid.nextLine(); + + int charBreak = bNarrowScreen ? 40 : 30; + auto info = wrap(theme->getInfo(), charBreak); + + rect_t r = grid.getLabelSlot(); + r.h += 50; + new StaticText(window, r, info, 0, COLOR_THEME_PRIMARY1); + + rect_t previewRect = bNarrowScreen ? + rect_t {0, r.x + r.h, LCD_W- 12, window->height()} : + rect_t {LCD_W / 2 + 6, 30, LCD_W / 2 - 12, window->height()}; + auto preview = new FilePreview(window, previewRect); + preview->setFile(themeImage.c_str()); + + window->setInnerHeight(grid.getWindowHeight()); } ScreenAddPage::ScreenAddPage(ScreenMenu * menu, uint8_t pageIndex): diff --git a/radio/src/gui/colorlcd/str_functions.cpp b/radio/src/gui/colorlcd/str_functions.cpp new file mode 100644 index 000000000..078127d42 --- /dev/null +++ b/radio/src/gui/colorlcd/str_functions.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include "str_functions.h" + +static char splitChars[] = { ' ', '-', '\t' }; + +char *trim(char *str) +{ + char *end; + + // Trim leading space + while(isspace((unsigned char)*str)) str++; + + if(*str == 0) // All spaces? + return str; + + // Trim trailing space + end = str + strlen(str) - 1; + while(end > str && isspace((unsigned char)*end)) end--; + + // Write new null terminator character + end[1] = '\0'; + + return str; +} + +std::string trim_start(std::string str) +{ + uint32_t i; + for (i = 0; i < str.length(); i++) + if (!iswspace(str[i])) + break; + + return str.substr(i); +} + +std::vector Explode(std::string str, char *splitChars) +{ + std::vector parts; + int startIndex = 0; + while (true) + { + size_t index = str.find_first_of(splitChars, startIndex); + + if (index == std::string::npos) + { + parts.emplace_back(str.substr(startIndex)); + return parts; + } + + std::string word = str.substr(startIndex, index - startIndex); + char nextChar = str.substr(index, 1)[0]; + + // Dashes and the likes should stick to the word occuring before it. Whitespace doesn't have to. + if (iswspace(nextChar)) + { + parts.emplace_back(word); + parts.emplace_back(std::string(&nextChar, 1)); + } + else + { + parts.emplace_back(word + nextChar); + } + + startIndex = index + 1; + } +} + +std::string wrap(std::string str, uint32_t width) +{ + std::vector words = Explode(str, splitChars); + + uint32_t curLineLength = 0; + std::ostringstream strBuilder; + + for(uint32_t i = 0; i < words.size(); i++) + { + std::string word = words[i]; + // If adding the new word to the current line would be too long, + // then put it on a new line (and split it up if it's too long). + if (curLineLength + word.length() > width) + { + // Only move down to a new line if we have text on the current line. + // Avoids situation where wrapped whitespace causes emptylines in text. + if (curLineLength > 0) + { + strBuilder << "\n"; + curLineLength = 0; + } + + // If the current word is too long to fit on a line even on it's own then + // split the word up. + while (word.length() > width) + { + strBuilder << word.substr(0, width - 1) + "-"; + word = word.substr(width - 1); + + strBuilder << "\n"; + } + + // Remove leading whitespace from the word so the new line starts flush to the left. + word = trim_start(word); + } + + strBuilder << word; + curLineLength += word.length(); + } + + return strBuilder.str(); +} diff --git a/radio/src/gui/colorlcd/str_functions.h b/radio/src/gui/colorlcd/str_functions.h new file mode 100644 index 000000000..b96afc1e9 --- /dev/null +++ b/radio/src/gui/colorlcd/str_functions.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#pragma once +#include +#include +#include +#include + +extern char *trim(char *str); +extern std::string trim_start(std::string str); +extern std::string wrap(std::string str, uint32_t width); diff --git a/radio/src/gui/colorlcd/theme_manager.cpp b/radio/src/gui/colorlcd/theme_manager.cpp new file mode 100644 index 000000000..b4909f8e6 --- /dev/null +++ b/radio/src/gui/colorlcd/theme_manager.cpp @@ -0,0 +1,383 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "theme_manager.h" + +ThemePersistance themePersistance; + +#define COLOR_COUNT 12 +static const char *conversionArray[COLOR_COUNT] = { + "DEFAULT", "PRIMARY1", "PRIMARY2", "PRIMARY3", + "SECONDARY1", "SECONDARY2", "SECONDARY3", "FOCUS", + "EDIT", "ACTIVE", "WARNING", "DISABLED", +}; + +constexpr const char *RGBSTRING = "RGB("; +constexpr const char *THEMES = "THEMES"; + +char *getWorkingDirectory() +{ + static char path[FF_MAX_LFN + 1]; // TODO optimize that! + f_getcwd((TCHAR *)path, FF_MAX_LFN); + if (path[strlen(path) - 1] != '/') strncat(path, "/", FF_MAX_LFN); + + strncat(path, THEMES, FF_MAX_LFN); + return path; +} + +std::string ThemeFile::getThemeImageFileName() +{ + char fullPath[FF_MAX_LFN + 1]; + strncpy(fullPath, getWorkingDirectory(), FF_MAX_LFN); + if (fullPath[strlen(fullPath) - 1] != '/') + strncat(fullPath, "/", FF_MAX_LFN); + + auto found = path.find('.'); + if (found != std::string::npos) { + auto baseFileName(fullPath + path.substr(0, found) + ".png"); + return baseFileName; + } + + return ""; +} + +void ThemeFile::scanFile() +{ + char line[256 + 1]; + char fullPath[FF_MAX_LFN + 1]; + ScanState scanState = none; + + strncpy(fullPath, getWorkingDirectory(), FF_MAX_LFN); + if (fullPath[strlen(fullPath) - 1] != '/') strncat(fullPath, "/", FF_MAX_LFN); + strncat(fullPath, path.c_str(), FF_MAX_LFN); + FRESULT result = f_open(&file, fullPath, FA_OPEN_EXISTING | FA_READ); + if (result != FR_OK) return; + + int lineNo = 1; + while (readNextLine(line, 256)) { + int len = strlen(line); + if (len == 0) continue; + + if (lineNo == 1 && len != 3 && strcmp(line, "---") != 0) { + TRACE("invalid yml file at line %d", lineNo); + return; + } else if (lineNo != 1) { + if (line[0] != ' ' && line[0] != '\t') { + char *pline = trim(line); + if (line[strlen(line) - 1] != ':') { + TRACE("invalid yml file at line %d", lineNo); + return; + } + + if (strcmp(pline, "colors:") == 0) { + scanState = colors; + continue; + } else if (strcmp(pline, "summary:") == 0) { + scanState = summary; + continue; + } + } + + const char *ptr = strchr(line, ':'); + if (!ptr) continue; + + char lvalue[64]; + char rvalue[64]; + char *plvalue; + char *prvalue; + + strncpy(lvalue, line, min((int)(ptr - line), 63)); + lvalue[ptr - line] = '\0'; + strncpy(rvalue, ptr + 1, 63); + plvalue = trim(lvalue); + prvalue = trim(rvalue); + + switch (scanState) { + case colors: { + int colorIndex = findColorIndex(plvalue); + if (colorIndex >= 0) { + uint32_t color; + if (convertRGB(prvalue, color)) + colorList.emplace_back(ColorEntry{(LcdColorIndex)colorIndex, color}); + else + TRACE("Theme: Could not convert color value"); + } + } break; + + case summary: { + if (strcmp(plvalue, "name") == 0) + name = prvalue; + else if (strcmp(plvalue, "author") == 0) + author = prvalue; + else if (strcmp(plvalue, "info") == 0) + info = prvalue; + } break; + } + } + + lineNo++; + } + + f_close(&file); +} + +#define HEX_COLOR_VALUE_LEN 8 + +bool ThemeFile::convertRGB(char *pColorRGB, uint32_t &color) +{ + if (strlen(pColorRGB) < strlen(RGBSTRING)) return false; + + if (strncmp(pColorRGB, RGBSTRING, strlen(RGBSTRING)) == 0) { + if (pColorRGB[strlen(pColorRGB) - 1] != ')') return false; + + pColorRGB[strlen(pColorRGB) - 1] = '\0'; + pColorRGB += strlen(RGBSTRING); + if (strlen(pColorRGB)) { + char *token = nullptr; + int numTokens = 0; + uint32_t tokens[3]; + + token = strtok(pColorRGB, ","); + while (token != nullptr) { + if (numTokens < 3) { + tokens[numTokens] = strtol(token, nullptr, 0); + numTokens++; + } else + break; + + token = strtok(nullptr, ","); + } + + if (numTokens != 3) return false; + + color = RGB(tokens[0], tokens[1], tokens[2]); + return true; + } + } else if (pColorRGB[0] == '0' && (pColorRGB[1] == 'x' || pColorRGB[1] == 'X')) { + if (strlen(pColorRGB) != HEX_COLOR_VALUE_LEN) return false; + pColorRGB += 2; + uint32_t tokens[3]; + char hexVal[3]; + for (int i = 0; i < 3; i++) { + strncpy(hexVal, pColorRGB, 2); + hexVal[2] = '\0'; + tokens[i] = strtol(hexVal, nullptr, 16); + pColorRGB += 2; + } + + color = RGB(tokens[0], tokens[1], tokens[2]); + return true; + } + + TRACE("Theme: Invalid color value"); + return false; +} + +LcdColorIndex ThemeFile::findColorIndex(const char *name) +{ + int i; + for (i = 0; i < COLOR_COUNT; i++) { + if (strcmp(name, conversionArray[i]) == 0) break; + } + + if (i >= COLOR_COUNT) return DEFAULT_COLOR_INDEX; + + return (LcdColorIndex)i; +} + +bool ThemeFile::readNextLine(char *line, int maxlen) +{ + if (f_gets(line, maxlen, &file) != NULL) { + int curlen = strlen(line) - 1; + if (line[curlen] == + '\n') { // remove unwanted chars if file was edited using windows + if (line[curlen - 1] == '\r') { + line[curlen - 1] = 0; + } else { + line[curlen] = 0; + } + } + + return true; + } + + return false; +} + +void ThemeFile::setColor(LcdColorIndex colorIndex, uint32_t color) +{ + if (colorIndex >= DEFAULT_COLOR_INDEX && colorIndex < LCD_COLOR_COUNT) { + colorList.emplace_back(ColorEntry {colorIndex, color}); + } +} + +void ThemeFile::applyTheme() +{ + for (auto color: colorList) { + lcdColorTable[color.colorNumber] = color.colorValue; + } + OpenTxTheme::instance()->update(false); +} + +void ThemePersistance::scanForThemes() +{ + TRACE("in scanForThemes"); + themes.clear(); + + DIR dir; + FILINFO fno; + + char fullPath[FF_MAX_LFN + 1]; + strcpy(fullPath, "./"); + strcat(fullPath, THEMES); + + TRACE("opening directory: %s", fullPath); + FRESULT res = f_opendir(&dir, fullPath); // Open the directory + if (res == FR_OK) { + TRACE("scanForThemes: open successful"); + // read all entries + bool firstTime = true; + for (;;) { + res = sdReadDir(&dir, &fno, firstTime); + + if (res != FR_OK || fno.fname[0] == 0) + break; // Break on error or end of dir + + if (strlen((const char *)fno.fname) > SD_SCREEN_FILE_LENGTH) continue; + if (fno.fattrib & AM_DIR) continue; + + TRACE("scanForThemes: found file %s", fno.fname); + std::string fname(fno.fname); + auto found = fname.find('.'); + if (found != std::string::npos) { + if (fname.substr(found) != ".yml") continue; + } + + themes.emplace_back(new ThemeFile(fno.fname)); + } + + f_closedir(&dir); + } +} + +void ThemePersistance::loadDefaultTheme() +{ + FIL file; + char fullPath[128]; + strcpy(fullPath, getWorkingDirectory()); + if (fullPath[strlen(fullPath) - 1] != '/') + strcat(fullPath, "/"); + strcat(fullPath, "selectedtheme.txt"); + + FRESULT status = f_open(&file, fullPath, FA_READ); + if (status != FR_OK) return; + + char line[256]; + uint len; + status = f_read(&file, line, 256, &len); + if (status == FR_OK) { + refresh(); + + line[len] = '\0'; + + int index = 0; + for (auto theme : themes) { + if (theme->getPath() == std::string(line)) { + applyTheme(index); + setThemeIndex(index); + } + + index++; + } + } + f_close(&file); +} + +void ThemePersistance::deleteDefaultTheme() +{ + char fullPath[128]; + strcpy(fullPath, getWorkingDirectory()); + if (fullPath[strlen(fullPath) - 1] != '/') + strcat(fullPath, "/"); + strcat(fullPath, "selectedtheme.txt"); + FIL file; + + FRESULT status = f_open(&file, fullPath, FA_CREATE_ALWAYS | FA_WRITE); + if (status == FR_OK) f_close(&file); +} + +void ThemePersistance::setDefaultTheme(int index) +{ + char fullPath[128]; + strcpy(fullPath, getWorkingDirectory()); + if (fullPath[strlen(fullPath) - 1] != '/') + strcat(fullPath, "/"); + strcat(fullPath, "selectedtheme.txt"); + FIL file; + + auto theme = themes[index]; + FRESULT status = f_open(&file, fullPath, FA_CREATE_ALWAYS | FA_WRITE); + if (status != FR_OK) return; + + f_printf(&file, theme->getPath().c_str()); + f_close(&file); +} + +class DefaultEdgeTxTheme : public ThemeFile +{ + public: + DefaultEdgeTxTheme() : ThemeFile("") + { + setName("EdgeTX Default"); + setAuthor("EdgeTX Team"); + setInfo("Default EdgeTX Color Scheme"); + } + + void applyTheme() override + { + lcdColorTable[DEFAULT_COLOR_INDEX] = RGB(18, 94, 153); + + lcdColorTable[COLOR_THEME_PRIMARY1_INDEX] = RGB(0, 0, 0); + lcdColorTable[COLOR_THEME_PRIMARY2_INDEX] = RGB(255, 255, 255); + lcdColorTable[COLOR_THEME_PRIMARY3_INDEX] = RGB(12, 63, 102); + lcdColorTable[COLOR_THEME_SECONDARY1_INDEX] = RGB(18, 94, 153); + lcdColorTable[COLOR_THEME_SECONDARY2_INDEX] = RGB(182, 224, 242); + lcdColorTable[COLOR_THEME_SECONDARY3_INDEX] = RGB(228, 238, 242); + lcdColorTable[COLOR_THEME_FOCUS_INDEX] = RGB(20, 161, 229); + lcdColorTable[COLOR_THEME_EDIT_INDEX] = RGB(0, 153, 9); + lcdColorTable[COLOR_THEME_ACTIVE_INDEX] = RGB(255, 222, 0); + lcdColorTable[COLOR_THEME_WARNING_INDEX] = RGB(224, 0, 0); + lcdColorTable[COLOR_THEME_DISABLED_INDEX] = RGB(140, 140, 140); + lcdColorTable[CUSTOM_COLOR_INDEX] = RGB(170, 85, 0); + + OpenTxTheme::instance()->update(false); + } + + std::string getThemeImageFileName() override + { + return "/THEMES/EdgeTX.png"; + } +}; + +void ThemePersistance::insertDefaultTheme() +{ + auto themeFile = new DefaultEdgeTxTheme(); + themes.insert(themes.begin(), themeFile); +} diff --git a/radio/src/gui/colorlcd/theme_manager.h b/radio/src/gui/colorlcd/theme_manager.h new file mode 100644 index 000000000..d0dc64bb7 --- /dev/null +++ b/radio/src/gui/colorlcd/theme_manager.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#pragma once +#include +#include +#include "sdcard.h" +#include "colors.h" +#include "str_functions.h" + +class ThemePersistance; +extern char * getWorkingDirectory(); +extern ThemePersistance themePersistance; + +struct ColorEntry +{ + LcdColorIndex colorNumber; + uint32_t colorValue; +}; + +class ThemeFile +{ + public: + ThemeFile(std::string themePath) : + path(themePath) + { + if (themePath.size()) { + scanFile(); + } + } + + std::string getPath() { return path; } + std::string getName() { return name; } + std::string getAuthor() { return author; } + std::string getInfo() { return info; } + + void setName(std::string name) { this->name = name; } + void setAuthor(std::string author) { this->author = author; } + void setInfo(std::string info) { this->info = info; } + + std::vector getColorList() { return colorList; } + void setColor(LcdColorIndex colorIndex, uint32_t color); + + virtual std::string getThemeImageFileName(); + virtual void applyTheme(); + + protected: + FIL file; + std::string path; + std::string name; + std::string author; + std::string info; + std::vector colorList; + + + enum ScanState + { + none, + summary, + colors + }; + + void scanFile(); + bool convertRGB(char *pColorRGB, uint32_t &color); + LcdColorIndex findColorIndex(const char *name); + bool readNextLine(char * line, int maxlen); +}; + + +class ThemePersistance +{ + public: + ThemePersistance() + { + } + + static ThemePersistance *instance() { + return &themePersistance; + } + + void loadDefaultTheme(); + void setDefaultTheme(int index); + void deleteDefaultTheme(); + + std::vector getNames() + { + std::vector names; + for (auto theme:themes) { + names.emplace_back(theme->getName()); + } + + return names; + } + + void applyTheme(int index) + { + auto theme = themes[index]; + theme->applyTheme(); + } + + inline int getThemeIndex() {return currentTheme;} + inline void setThemeIndex(int index) { currentTheme = index;} + + inline ThemeFile* getCurrentTheme() { return themes[currentTheme]; } + + void refresh() + { + scanForThemes(); + insertDefaultTheme(); + } + + protected: + std::vector themes; + int currentTheme = 0; + void scanForThemes(); + void insertDefaultTheme(); +}; diff --git a/radio/src/gui/colorlcd/themes/480_default.cpp b/radio/src/gui/colorlcd/themes/480_default.cpp index 18cf45b48..550270c37 100644 --- a/radio/src/gui/colorlcd/themes/480_default.cpp +++ b/radio/src/gui/colorlcd/themes/480_default.cpp @@ -22,6 +22,7 @@ #include "opentx.h" #include "tabsgroup.h" #include "480_bitmaps.h" +#include "theme_manager.h" const ZoneOption OPTIONS_THEME_DEFAULT[] = { { STR_BACKGROUND_COLOR, ZoneOption::Color, OPTION_VALUE_UNSIGNED(WHITE) }, @@ -281,6 +282,7 @@ class Theme480: public OpenTxTheme void load() const override { loadColors(); + ThemePersistance::instance()->loadDefaultTheme(); OpenTxTheme::load(); if (!backgroundBitmap) { backgroundBitmap = BitmapBuffer::loadBitmap(getFilePath("background.png"));