From a9abf028f74c91eb1dba8a60ac84ce96d4fcadee Mon Sep 17 00:00:00 2001 From: Kevin Koenig Date: Sat, 20 Nov 2021 06:54:24 -0500 Subject: [PATCH] Theme editor tweaks (#1066) * 1. fixed color of lists to match menu popup as per JimB 2. added more controls to PreviewWindow() of theme editor * 1. fixed issue with touch. in theme list now if you touch, you must long press to get menu. Enter works on short press 2. changed directory structure to new format, as per Issue #897 3. sorted theme list alphabetically and not by random order that the directory comes back in * 1. make background image work correctly for new theme folder structure 2. sort theme names * 1. fixed issues with touching previewwindow 2. fixed processing exit button on ThemeEditPage() * fix focus issue in previewwindow * layout enhancements for nv14 * more updates to layout * fix nv14 issues. Still havnt done performance optimization yet. Lets merge first. Co-authored-by: kkoenig1@optonline.com --- radio/src/gui/colorlcd/listbox.cpp | 10 +- radio/src/gui/colorlcd/preview_window.h | 97 +++++++--- radio/src/gui/colorlcd/radio_theme.cpp | 56 +++--- radio/src/gui/colorlcd/screen_setup.cpp | 19 +- radio/src/gui/colorlcd/theme.h | 8 + radio/src/gui/colorlcd/theme_manager.cpp | 175 ++++++++++-------- radio/src/gui/colorlcd/theme_manager.h | 16 +- radio/src/gui/colorlcd/themes/480_default.cpp | 9 + 8 files changed, 238 insertions(+), 152 deletions(-) diff --git a/radio/src/gui/colorlcd/listbox.cpp b/radio/src/gui/colorlcd/listbox.cpp index 899a36ed2..c1a65afac 100644 --- a/radio/src/gui/colorlcd/listbox.cpp +++ b/radio/src/gui/colorlcd/listbox.cpp @@ -80,17 +80,15 @@ void ListBase::drawLine(BitmapBuffer *dc, const rect_t &rect, uint32_t index, Lc void ListBase::paint(BitmapBuffer *dc) { dc->clear(COLOR_THEME_SECONDARY3); - uint32_t bgColor = 0; - bgColor = hasFocus() ? COLOR_THEME_FOCUS : COLOR_THEME_SECONDARY1; int curY = 0; for (int n = 0; n < (int)names.size(); n++) { - if (n == selected) { - dc->drawSolidFilledRect(1, curY, rect.w - 2, lineHeight, bgColor); - } + //if (n == selected) { + dc->drawSolidFilledRect(1, curY, rect.w - 2, lineHeight, n == selected ? COLOR_THEME_FOCUS : COLOR_THEME_PRIMARY2); + //} LcdFlags textColor = - n == selected ? COLOR_THEME_PRIMARY2 : COLOR_THEME_PRIMARY1; + n == selected ? COLOR_THEME_PRIMARY2 : COLOR_THEME_SECONDARY1; auto fontHeight = getFontHeight(FONT(STD)); drawLine(dc, { 8, curY + (lineHeight - fontHeight) / 2, rect.w, lineHeight}, n, textColor); diff --git a/radio/src/gui/colorlcd/preview_window.h b/radio/src/gui/colorlcd/preview_window.h index 6b172a4e0..3fd2cbb23 100644 --- a/radio/src/gui/colorlcd/preview_window.h +++ b/radio/src/gui/colorlcd/preview_window.h @@ -86,27 +86,43 @@ class ThemedTextEdit : public TextEdit class ThemedStaticText : public StaticText { public: - using StaticText::StaticText; + ThemedStaticText(FormGroup *window, const rect_t &rect, std::string text, LcdColorIndex colorIndex) : + StaticText(window, rect, text, 0, COLOR(colorIndex)), + _colorIndex(colorIndex) + { + } void paint(BitmapBuffer *dc) override { colorMaintainer.applyColorValues(); - setTextFlags(COLOR_THEME_PRIMARY1); + setTextFlags(COLOR(_colorIndex)); StaticText::paint(dc); colorMaintainer.restoreColorValues(); } + + protected: + LcdColorIndex _colorIndex; }; class ThemedCheckBox : public CheckBox { public: ThemedCheckBox(Window *parent, rect_t rect, bool checked) : - CheckBox( - parent, rect, [=]() { return checked; }, [](uint8_t value) {}), + CheckBox(parent, rect, [=]() { return checked; }, [](uint8_t value) {}, NO_FOCUS), checked(checked) { + enable(false); + setFocusHandler([] (bool focus) { + }); } +#if defined(HARDWARE_KEYS) + void onEvent(event_t event) override + { + return parent->onEvent(event); + } +#endif + void paint(BitmapBuffer *dc) override { colorMaintainer.applyColorValues(); @@ -120,14 +136,14 @@ class ThemedCheckBox : public CheckBox class ThemedMainViewHorizontalTrim : public MainViewHorizontalTrim { - public: - using MainViewHorizontalTrim::MainViewHorizontalTrim; - void paint(BitmapBuffer *dc) override - { - colorMaintainer.applyColorValues(); - MainViewHorizontalTrim::paint(dc); - colorMaintainer.restoreColorValues(); - } + public: + using MainViewHorizontalTrim::MainViewHorizontalTrim; + void paint(BitmapBuffer *dc) override + { + colorMaintainer.applyColorValues(); + MainViewHorizontalTrim::paint(dc); + colorMaintainer.restoreColorValues(); + } }; class ThemedMainViewHorizontalSlider : public MainViewHorizontalSlider @@ -142,6 +158,38 @@ class ThemedMainViewHorizontalSlider : public MainViewHorizontalSlider } }; +class ThemedButton : public TextButton +{ + public: + ThemedButton(FormGroup *window, const rect_t &rect, std::string text, bool isChecked, WindowFlags windowFlags, + LcdColorIndex colorIndex) : + TextButton(window, rect, text, nullptr, windowFlags | NO_FOCUS, COLOR(colorIndex)), + _colorIndex(colorIndex), + _isChecked(isChecked) + { + setPressHandler([=] () { return _isChecked; }); + } + +#if defined(HARDWARE_KEYS) + void onEvent(event_t event) override + { + parent->onEvent(event); + } +#endif + + void paint(BitmapBuffer *dc) override + { + colorMaintainer.applyColorValues(); + setTextFlags(COLOR(_colorIndex)); + TextButton::paint(dc); + colorMaintainer.restoreColorValues(); + } + protected: + LcdColorIndex _colorIndex; + bool _isChecked = true; +}; + + // display controls using the appropriate theme. class PreviewWindow : public FormGroup { @@ -150,14 +198,14 @@ class PreviewWindow : public FormGroup std::vector colorList) : FormGroup(window, rect, NO_FOCUS), _colorList(colorList) { - new ThemedStaticText(this, {5, 40, 100, LINE_HEIGHT}, "Checkbox", 0, - COLOR_THEME_PRIMARY1); + new ThemedStaticText(this, {5, 40, 100, LINE_HEIGHT}, "Checkbox", COLOR_THEME_PRIMARY1_INDEX); new ThemedCheckBox(this, {100 + 15, 40, 20, LINE_HEIGHT}, true); new ThemedCheckBox(this, {140 + 15, 40, 20, LINE_HEIGHT}, false); - new ThemedMainViewHorizontalTrim(this, - {5, 65, HORIZONTAL_SLIDERS_WIDTH, 20}, 0); - new ThemedMainViewHorizontalSlider( - this, {5, 87, HORIZONTAL_SLIDERS_WIDTH, 20}, 0); + new ThemedMainViewHorizontalTrim(this, {5, 65, HORIZONTAL_SLIDERS_WIDTH, 20}, 0); + new ThemedMainViewHorizontalSlider(this, {5, 87, HORIZONTAL_SLIDERS_WIDTH, 20}, 0); + new ThemedButton(this, {5, 109, 100, LINE_HEIGHT + 10}, "Checked", true, BUTTON_CHECKED, COLOR_THEME_PRIMARY1_INDEX); + new ThemedButton(this, {110, 109, 100, LINE_HEIGHT + 10}, "Regular", false, 0, COLOR_THEME_PRIMARY1_INDEX); + new ThemedStaticText(this, {5, 145, 100, LINE_HEIGHT}, "Warning Text", COLOR_THEME_WARNING_INDEX); ticks = getTicks(); } @@ -214,26 +262,23 @@ class PreviewWindow : public FormGroup dc->clear(COLOR_THEME_SECONDARY3); // top bar background - dc->drawSolidFilledRect(0, 0, rect.w, TOPBAR_HEIGHT, - COLOR_THEME_SECONDARY1); + dc->drawSolidFilledRect(0, 0, rect.w, TOPBAR_HEIGHT, COLOR_THEME_SECONDARY1); int width; int x = 5; // topbar icons - auto bm = getBitmap(mask_menu_radio, COLOR_THEME_SECONDARY1, - COLOR_THEME_PRIMARY2, &width); + auto bm = getBitmap(mask_menu_radio, COLOR_THEME_SECONDARY1, COLOR_THEME_PRIMARY2, &width); dc->drawBitmap(x, 5, bm); x += MENU_HEADER_BUTTON_WIDTH + 2; delete bm; - bm = getBitmap(mask_radio_tools, COLOR_THEME_SECONDARY1, - COLOR_THEME_PRIMARY2, &width); + dc->drawSolidFilledRect(x - 2, 0, MENU_HEADER_BUTTON_WIDTH + 2, TOPBAR_HEIGHT, COLOR_THEME_FOCUS); + bm = getBitmap(mask_radio_tools, COLOR_THEME_FOCUS, COLOR_THEME_PRIMARY2, &width); dc->drawBitmap(x, 5, bm); x += MENU_HEADER_BUTTON_WIDTH + 2; delete bm; - bm = getBitmap(mask_radio_setup, COLOR_THEME_SECONDARY1, - COLOR_THEME_PRIMARY2, &width); + bm = getBitmap(mask_radio_setup, COLOR_THEME_SECONDARY1, COLOR_THEME_PRIMARY2, &width); dc->drawBitmap(x, 5, bm); delete bm; diff --git a/radio/src/gui/colorlcd/radio_theme.cpp b/radio/src/gui/colorlcd/radio_theme.cpp index cd9138252..a728c2757 100644 --- a/radio/src/gui/colorlcd/radio_theme.cpp +++ b/radio/src/gui/colorlcd/radio_theme.cpp @@ -40,11 +40,15 @@ #define COLOR_PREVIEW_HEIGHT (LCD_H - TOPBAR_HEIGHT - 30) #define LEFT_LIST_WIDTH (LCD_W / 2) - COLOR_PREVIEW_WIDTH #define LEFT_LIST_HEIGHT (LCD_H - TOPBAR_HEIGHT - 37) + #define COLOR_LIST_WIDTH ((LCD_W * 3)/10) + #define COLOR_LIST_HEIGHT (LCD_H - TOPBAR_HEIGHT) #else - #define COLOR_PREVIEW_WIDTH 18 - #define COLOR_PREVIEW_HEIGHT (LCD_H - TOPBAR_HEIGHT - 30) + #define COLOR_PREVIEW_WIDTH LCD_W + #define COLOR_PREVIEW_HEIGHT 18 #define LEFT_LIST_WIDTH LCD_W #define LEFT_LIST_HEIGHT (LCD_H / 2 - 38) + #define COLOR_LIST_WIDTH LCD_W + #define COLOR_LIST_HEIGHT (LCD_H / 2 - 38) #endif #define MARGIN_WIDTH 5 @@ -60,8 +64,8 @@ constexpr int COLOR_BOX_LEFT = 3; constexpr int COLOR_BOX_WIDTH = 45; constexpr int COLOR_BOX_HEIGHT = 27; #else -constexpr int BUTTON_HEIGHT = 25; -constexpr int BUTTON_WIDTH = 50; +constexpr int BUTTON_HEIGHT = 30; +constexpr int BUTTON_WIDTH = 65; constexpr LcdFlags textFont = FONT(XS); constexpr rect_t detailsDialogRect = {5, 50, LCD_W - 10, 340}; constexpr int labelWidth = 120; @@ -74,8 +78,6 @@ constexpr int COLOR_BUTTON_WIDTH = COLOR_BOX_WIDTH; constexpr int COLOR_BUTTON_HEIGHT = 20; #endif -#define COLOR_LIST_WIDTH ((LCD_W * 3)/10) -#define COLOR_LIST_HEIGHT (LCD_H - TOPBAR_HEIGHT) class ThemeDetailsDialog: public Dialog @@ -227,7 +229,11 @@ protected: _hexBox = new StaticText(window, r, "", 0, COLOR_THEME_PRIMARY1 | FONT(L) | RIGHT); setHexStr(_hexBox, _theme.getColorEntryByIndex(_indexOfColor)->colorValue); - r = { COLOR_BOX_LEFT, COLOR_BOX_HEIGHT + 7, COLOR_LIST_WIDTH, window->height() - COLOR_BOX_HEIGHT - 7}; + if (LCD_W > LCD_H) + r = { COLOR_BOX_LEFT, COLOR_BOX_HEIGHT + 7, COLOR_LIST_WIDTH, window->height() - COLOR_BOX_HEIGHT - 7}; + else + r = { COLOR_BOX_LEFT, COLOR_BOX_HEIGHT + 7, COLOR_LIST_WIDTH, LCD_H / 2 - COLOR_BOX_HEIGHT - 20 }; + _colorEditor = new ColorEditor(window, r, _theme.getColorEntryByIndex(_indexOfColor)->colorValue, [=](uint32_t rgb) { _theme.setColor(_indexOfColor, rgb); @@ -320,6 +326,7 @@ class ThemeEditPage : public Page focus->getNextField()->setFocus(SET_FOCUS_FORWARD, focus); } } else if (event == EVT_KEY_FIRST(KEY_EXIT)) { + killEvents(event); deleteLater(); } else { Window::onEvent(event); @@ -479,18 +486,15 @@ void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp) new ThemeDetailsDialog(window, newTheme, [=] (ThemeFile theme) { if (strlen(theme.getName()) != 0) { - char path[NAME_LENGTH + 20]; - strncpy(path, theme.getName(), NAME_LENGTH + 19); - removeAllWhiteSpace(path); - strncat(path, ".yml", NAME_LENGTH + 19); - theme.setPath(path); - + char name[NAME_LENGTH + 20]; + strncpy(name, theme.getName(), NAME_LENGTH + 19); + removeAllWhiteSpace(name); // use the current themes color list to make the new theme auto curTheme = tp->getCurrentTheme(); for (auto color : curTheme->getColorList()) theme.setColor(color.colorNumber, color.colorValue); - theme.serialize(); + tp->createNewTheme(name, theme); tp->refresh(); listBox->setNames(tp->getNames()); listBox->setSelected(currentTheme); @@ -504,7 +508,7 @@ void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp) if (confirmationDialog("Delete Theme?", tp->getThemeByIndex(listBox->getSelected())->getName())) { tp->deleteThemeByIndex(listBox->getSelected()); listBox->setNames(tp->getNames()); - listBox->setSelected(currentTheme); + listBox->setSelected(min(currentTheme, tp->getNames().size() - 1)); } }); } @@ -530,7 +534,10 @@ void ThemeSetupPage::setupListbox(FormWindow *window, rect_t r, ThemePersistance listBox->setActiveIndex(tp->getThemeIndex()); listBox->setTitle(STR_THEME + std::string("s")); listBox->setLongPressHandler([=] (event_t event) { displayThemeMenu(window, tp); }); - listBox->setPressHandler([=] (event_t event) { displayThemeMenu(window, tp); }); + listBox->setPressHandler([=] (event_t event) { + if (event != 0) + displayThemeMenu(window, tp); + }); } void ThemeSetupPage::build(FormWindow *window) @@ -549,30 +556,37 @@ void ThemeSetupPage::build(FormWindow *window) rect_t r = { 2, 3, LEFT_LIST_WIDTH, LEFT_LIST_HEIGHT }; setupListbox(window, r, tp); + rect_t colorPreviewRect; if (LCD_W > LCD_H) { r.x = LEFT_LIST_WIDTH + MARGIN_WIDTH; r.w = LCD_W - r.x; + colorPreviewRect = {LEFT_LIST_WIDTH + 6, 0, COLOR_PREVIEW_WIDTH, COLOR_PREVIEW_HEIGHT}; } else { r.y += LEFT_LIST_HEIGHT + 4; r.x = 0; r.w = LCD_W; + colorPreviewRect = {0, LEFT_LIST_HEIGHT + 6, COLOR_PREVIEW_WIDTH, COLOR_PREVIEW_HEIGHT}; } // setup ThemeColorPreview() auto colorList = theme != nullptr ? theme->getColorList() : std::vector(); - rect_t colorPreviewRect = {LEFT_LIST_WIDTH + 6, 0, COLOR_PREVIEW_WIDTH, COLOR_PREVIEW_HEIGHT}; themeColorPreview = new ThemeColorPreview(window, colorPreviewRect, colorList); // setup FileCarosell() - r.h = 130; - r.w -= COLOR_PREVIEW_WIDTH; - r.x += COLOR_PREVIEW_WIDTH; + if (LCD_W > LCD_H) { + r.w -= COLOR_PREVIEW_WIDTH; + r.h = 130; + r.x += COLOR_PREVIEW_WIDTH; + } else { + r.y += COLOR_PREVIEW_HEIGHT; + r.h = LEFT_LIST_HEIGHT - COLOR_PREVIEW_HEIGHT - 50; + } auto fileNames = theme != nullptr ? theme->getThemeImageFileNames() : std::vector(); fileCarosell = new FileCarosell(window, r, fileNames, listBox); // author and name of theme on right side of screen r.x += 7; - r.y += 130; + r.y += 135; r.h = 20; nameText = new StaticText(window, r, theme != nullptr ? theme->getName() : "", 0, COLOR_THEME_PRIMARY1); diff --git a/radio/src/gui/colorlcd/screen_setup.cpp b/radio/src/gui/colorlcd/screen_setup.cpp index 7a0bb04b3..c72a3e463 100644 --- a/radio/src/gui/colorlcd/screen_setup.cpp +++ b/radio/src/gui/colorlcd/screen_setup.cpp @@ -402,26 +402,13 @@ void ScreenUserInterfacePage::build(FormWindow * window) build(window); }); - std::function launchEditor = [=]() { - auto theme = tp->getCurrentTheme(); - auto te = new ThemeEditorPage(window, theme); - te->setCloseHandler([=] () { - window->clear(); - build(window); - }); - - return 0; - }; - - // new TextButton(window, grid.getFieldSlot(3, 2), STR_EDIT, launchEditor, BUTTON_BACKGROUND | OPAQUE, COLOR_THEME_PRIMARY2); - bool bNarrowScreen = LCD_W < LCD_H; if (bNarrowScreen) grid.setLabelWidth(LCD_W); grid.nextLine(); auto theme = tp->getCurrentTheme(); - auto themeImage = theme->getThemeImageFileName(); + auto themeImage = theme->getThemeImageFileNames(); grid.spacer(8); @@ -440,10 +427,10 @@ void ScreenUserInterfacePage::build(FormWindow * window) new StaticText(window, r, info, 0, COLOR_THEME_PRIMARY1); rect_t previewRect = bNarrowScreen ? - rect_t {0, r.h, LCD_W- 12, window->height()} : + rect_t {0, r.h, LCD_W, 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()); + preview->setFile(themeImage.size() > 0 ? themeImage[0].c_str() : ""); window->setInnerHeight(grid.getWindowHeight()); } diff --git a/radio/src/gui/colorlcd/theme.h b/radio/src/gui/colorlcd/theme.h index d6e4e6ad3..4524e3a63 100644 --- a/radio/src/gui/colorlcd/theme.h +++ b/radio/src/gui/colorlcd/theme.h @@ -25,6 +25,7 @@ #include #include #include "zone.h" +#include "ffconf.h" #include "thirdparty/libopenui/src/theme.h" class BitmapBuffer; @@ -82,6 +83,12 @@ class OpenTxTheme: public Theme ZoneOptionValue * getOptionValue(unsigned int index) const; + virtual void setBackgroundImageFileName(const char *fileName) + { + strncpy(backgroundImageFileName, fileName, FF_MAX_LFN); + backgroundImageFileName[FF_MAX_LFN] = '\0'; // ensure string termination + } + virtual void load() const; virtual void drawBackground(BitmapBuffer * dc) const; @@ -112,6 +119,7 @@ class OpenTxTheme: public Theme const char * name; const ZoneOption * options; BitmapBuffer * thumb; + char backgroundImageFileName[FF_MAX_LFN + 1]; public: static const BitmapBuffer * error; diff --git a/radio/src/gui/colorlcd/theme_manager.cpp b/radio/src/gui/colorlcd/theme_manager.cpp index c57f2d4b1..cea93beb5 100644 --- a/radio/src/gui/colorlcd/theme_manager.cpp +++ b/radio/src/gui/colorlcd/theme_manager.cpp @@ -20,7 +20,7 @@ */ #include "theme_manager.h" -#define MAX_FILES 20 +#define MAX_FILES 9 ThemePersistance themePersistance; static const char *colorNames[COLOR_COUNT] = { @@ -33,6 +33,32 @@ static const char *colorNames[COLOR_COUNT] = { constexpr const char *RGBSTRING = "RGB("; constexpr const char* SELECTED_THEME_FILE = THEMES_PATH "/selectedtheme.txt"; +ThemeFile::ThemeFile(std::string themePath) : + path(themePath) +{ + if (path.size()) { + deSerialize(); + } + + FIL file; + auto found = path.rfind('/'); + if (found != std::string::npos) { + int n = 0; + while (n < MAX_FILES) { + auto baseFileName(path.substr(0, found + 1) + (n != 0 ? "screenshot" + std::to_string(n) : "logo") + ".png"); + FRESULT result = f_open(&file, baseFileName.c_str(), FA_OPEN_EXISTING); + if (result == FR_OK) { + _imageFileNames.emplace_back(baseFileName); + f_close(&file); + } else { + break; + } + + n++; + } + } +} + ThemeFile& ThemeFile::operator= (const ThemeFile& theme) { if (this == &theme) @@ -43,58 +69,20 @@ ThemeFile& ThemeFile::operator= (const ThemeFile& theme) strncpy(author, theme.author, AUTHOR_LENGTH); strncpy(info, theme.info, INFO_LENGTH); colorList.assign(theme.colorList.begin(), theme.colorList.end()); + _imageFileNames.assign(theme._imageFileNames.begin(), theme._imageFileNames.end()); + return *this; }; -std::string ThemeFile::getThemeImageFileName() -{ - char fullPath[FF_MAX_LFN + 1]; - strncpy(fullPath, THEMES_PATH "/", FF_MAX_LFN); - - auto found = path.rfind('.'); - if (found != std::string::npos) { - auto baseFileName(fullPath + path.substr(0, found) + ".png"); - return baseFileName; - } - - return ""; -} - std::vector ThemeFile::getThemeImageFileNames() { - std::vector fileNames; - - char fullPath[FF_MAX_LFN + 1]; - strncpy(fullPath, THEMES_PATH "/", FF_MAX_LFN); - auto found = path.rfind('.'); - FIL file; - if (found != std::string::npos) { - int n = 0; - while (n < MAX_FILES) { - auto baseFileName(fullPath + path.substr(0, found) + (n != 0 ? std::to_string(n) : "") + ".png"); - FRESULT result = f_open(&file, baseFileName.c_str(), FA_OPEN_EXISTING); - if (result == FR_OK) { - fileNames.emplace_back(baseFileName); - f_close(&file); - } else { - break; - } - - n++; - } - } - - return fileNames; + return _imageFileNames; } void ThemeFile::serialize() { - char fullPath[FF_MAX_LFN + 1]; - strncpy(fullPath, THEMES_PATH "/", FF_MAX_LFN); - strncat(fullPath, path.c_str(), FF_MAX_LFN); - FIL file; - FRESULT result = f_open(&file, fullPath, FA_CREATE_ALWAYS | FA_WRITE); + FRESULT result = f_open(&file, path.c_str(), FA_CREATE_ALWAYS | FA_WRITE); if (result == FR_OK) { f_printf(&file, "---\n"); f_printf(&file, "summary:\n"); @@ -121,14 +109,10 @@ void ThemeFile::serialize() void ThemeFile::deSerialize() { char line[256 + 1]; - char fullPath[FF_MAX_LFN + 1]; ScanState scanState = none; - strncpy(fullPath, THEMES_PATH "/", FF_MAX_LFN); - strncat(fullPath, path.c_str(), FF_MAX_LFN); - FIL file; - FRESULT result = f_open(&file, fullPath, FA_OPEN_EXISTING | FA_READ); + FRESULT result = f_open(&file, path.c_str(), FA_OPEN_EXISTING | FA_READ); if (result != FR_OK) return; int lineNo = 1; @@ -301,11 +285,31 @@ void ThemeFile::setColor(LcdColorIndex colorIndex, uint32_t color) } } -void ThemeFile::applyTheme() +void ThemeFile::applyColors() { for (auto color: colorList) { lcdColorTable[color.colorNumber] = color.colorValue; } +} + +void ThemeFile::applyBackground() +{ + auto instance = OpenTxTheme::instance(); + std::string backgroundImageFileName(getPath()); + auto pos = backgroundImageFileName.rfind('/'); + if (pos != std::string::npos) { + auto rootDir = backgroundImageFileName.substr(0, pos + 1); + rootDir = rootDir + "background_" + std::to_string(LCD_W) + "x" + std::to_string(LCD_H) + ".png"; + instance->setBackgroundImageFileName((char *)rootDir.c_str()); + } else { + instance->setBackgroundImageFileName(""); + } +} + +void ThemeFile::applyTheme() +{ + applyColors(); + applyBackground(); OpenTxTheme::instance()->update(false); } @@ -318,7 +322,16 @@ void ThemePersistance::clearThemes() themes.clear(); } -#define MAX_SD_FILE_LENGTH 128 +void ThemePersistance::scanThemeFolder(char *fullPath) +{ + FIL file; + strncat(fullPath, "/theme.yml", FF_MAX_LFN); + FRESULT result = f_open(&file, fullPath, FA_OPEN_EXISTING | FA_READ); + if (result != FR_OK) return; + + TRACE("scanForThemes: found file %s", fullPath); + themes.emplace_back(new ThemeFile(fullPath)); +} void ThemePersistance::scanForThemes() { @@ -345,20 +358,20 @@ void ThemePersistance::scanForThemes() 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 (strcasecmp(fname.substr(found).c_str(), YAML_EXT) != 0) continue; + if (fno.fattrib & AM_DIR) { + char themePath[FF_MAX_LFN + 1]; + strncpy(themePath, fullPath, FF_MAX_LFN); + strncat(themePath, "/", FF_MAX_LFN); + strncat(themePath, fno.fname, FF_MAX_LFN); + scanThemeFolder(themePath); } - else continue; - - themes.emplace_back(new ThemeFile(fno.fname)); } f_closedir(&dir); + std::sort(themes.begin(), themes.end(), + [](ThemeFile *a, ThemeFile *b) { + return strcmp(a->getName(), b->getName()) < 0; + }); } } @@ -402,16 +415,12 @@ bool ThemePersistance::deleteThemeByIndex(int index) if (index > 0 && index < (int) themes.size()) { ThemeFile* theme = themes[index]; - char curFile[FF_MAX_LFN + 1]; - strncpy(curFile, THEMES_PATH "/", FF_MAX_LFN); - strncat(curFile, theme->getPath().c_str(), FF_MAX_LFN); - char newFile[FF_MAX_LFN + 1]; - strncpy(newFile, curFile, FF_MAX_LFN); + strncpy(newFile, theme->getPath().c_str(), FF_MAX_LFN); strcat(newFile, ".deleted"); // for now we are just renaming the file so we don't find it - FRESULT status = f_rename(curFile, newFile); + FRESULT status = f_rename(theme->getPath().c_str(), newFile); refresh(); // make sure currentTheme stays in bounds @@ -423,6 +432,23 @@ bool ThemePersistance::deleteThemeByIndex(int index) return false; } +bool ThemePersistance::createNewTheme(std::string name, ThemeFile &theme) +{ + char fullPath[FF_MAX_LFN + 1]; + strncpy(fullPath, THEMES_PATH, FF_MAX_LFN); + fullPath[FF_MAX_LFN] = '\0'; + strncat(fullPath, "/", FF_MAX_LFN); + strncat(fullPath, name.c_str(), FF_MAX_LFN); + + FRESULT result = f_mkdir(fullPath); + if (result != FR_OK) return false; + strncat(fullPath, "/", FF_MAX_LFN); + strncat(fullPath, "theme.yml", FF_MAX_LFN); + theme.setPath(fullPath); + theme.serialize(); + return true; +} + void ThemePersistance::deleteDefaultTheme() { FIL file; @@ -467,20 +493,20 @@ class DefaultEdgeTxTheme : public ThemeFile colorList.emplace_back(ColorEntry { COLOR_THEME_DISABLED_INDEX, RGB(140, 140, 140) }); } - void applyTheme() override + void applyBackground() override { - ThemeFile::applyTheme(); - } - - std::string getThemeImageFileName() override - { - return "/THEMES/EdgeTX.png"; + auto instance = OpenTxTheme::instance(); + char fileName[FF_MAX_LFN + 1]; + fileName[FF_MAX_LFN] = '\0'; + strncpy(fileName, THEMES_PATH, FF_MAX_LFN); + strcat(fileName, "/EdgeTX/background.png"); + instance->setBackgroundImageFileName(fileName); } std::vector getThemeImageFileNames() override { std::vector fileNames; - fileNames.emplace_back("/THEMES/EdgeTX.png"); + fileNames.emplace_back("/THEMES/EdgeTX/EdgeTX.png"); return fileNames; } }; @@ -490,3 +516,4 @@ 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 index 6d21b7ed1..aeaea57e5 100644 --- a/radio/src/gui/colorlcd/theme_manager.h +++ b/radio/src/gui/colorlcd/theme_manager.h @@ -55,13 +55,7 @@ class ThemeFile info[0] = '\0'; } - ThemeFile(std::string themePath) : - path(themePath) - { - if (themePath.size()) { - deSerialize(); - } - } + ThemeFile(std::string themePath); ThemeFile(const ThemeFile &theme) { path = theme.path; @@ -117,9 +111,8 @@ class ThemeFile void setColor(LcdColorIndex colorIndex, uint32_t color); void setColorByIndex(int index, uint32_t color); - virtual std::string getThemeImageFileName(); virtual std::vector getThemeImageFileNames(); - virtual void applyTheme(); + void applyTheme(); protected: std::string path; @@ -127,6 +120,9 @@ class ThemeFile char author[AUTHOR_LENGTH + 1]; char info[INFO_LENGTH + 1]; std::vector colorList; + std::vector _imageFileNames; + void applyColors(); + virtual void applyBackground(); enum ScanState { @@ -158,6 +154,7 @@ class ThemePersistance void deleteDefaultTheme(); static char **getColorNames(); bool deleteThemeByIndex(int index); + bool createNewTheme(std::string name, ThemeFile &theme); std::vector getNames() { @@ -206,4 +203,5 @@ class ThemePersistance void scanForThemes(); void insertDefaultTheme(); void clearThemes(); + void scanThemeFolder(char *fullPath); }; diff --git a/radio/src/gui/colorlcd/themes/480_default.cpp b/radio/src/gui/colorlcd/themes/480_default.cpp index af28673d4..2ba11066c 100644 --- a/radio/src/gui/colorlcd/themes/480_default.cpp +++ b/radio/src/gui/colorlcd/themes/480_default.cpp @@ -186,6 +186,15 @@ class Theme480: public OpenTxTheme delete dot; } + void setBackgroundImageFileName(const char *fileName) override + { + // ensure you delete old bitmap + if (strcmp(backgroundImageFileName, fileName) != 0 && backgroundBitmap != nullptr) + delete backgroundBitmap; + OpenTxTheme::setBackgroundImageFileName(fileName); // set the filename + backgroundBitmap = BitmapBuffer::loadBitmap(backgroundImageFileName); + } + void loadThemeBitmaps() const { // Calibration screen