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 <kkoenig1@optonline.com> Co-authored-by: Peter Feerick <peter.feerick@gmail.com>
This commit is contained in:
co-authored by
kkoenig1@optonline.com <kkoenig1@optonline.com>
Peter Feerick
parent
ba03d02b7a
commit
9cb6d73947
@@ -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
|
||||
|
||||
@@ -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<coord_t>(h, bitmap->height());
|
||||
coord_t bitmapWidth = min<coord_t>(width(), bitmap->width());
|
||||
dc->drawScaledBitmap(bitmap, (width() - bitmapWidth) / 2, y + (h - bitmapHeight) / 2, bitmapWidth,
|
||||
bitmapHeight);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
BitmapBuffer *bitmap = nullptr;
|
||||
};
|
||||
@@ -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<coord_t>(h, bitmap->height());
|
||||
coord_t bitmapWidth = min<coord_t>(width(), bitmap->width());
|
||||
dc->drawScaledBitmap(bitmap, (width() - bitmapWidth) / 2, y + (h - bitmapHeight) / 2, bitmapWidth,
|
||||
bitmapHeight);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
BitmapBuffer *bitmap = nullptr;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class FlashDialog: public FullScreenDialog
|
||||
|
||||
@@ -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<std::string> 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):
|
||||
|
||||
@@ -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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#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<std::string> Explode(std::string str, char *splitChars)
|
||||
{
|
||||
std::vector<std::string> 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<std::string> 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();
|
||||
}
|
||||
@@ -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 <wctype.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
extern char *trim(char *str);
|
||||
extern std::string trim_start(std::string str);
|
||||
extern std::string wrap(std::string str, uint32_t width);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 <stdlib.h>
|
||||
#include <list>
|
||||
#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<ColorEntry> 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<ColorEntry> 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<std::string> getNames()
|
||||
{
|
||||
std::vector<std::string> 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<ThemeFile *> themes;
|
||||
int currentTheme = 0;
|
||||
void scanForThemes();
|
||||
void insertDefaultTheme();
|
||||
};
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user