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