Theme editor (#971)

* Add theme editor
- added theme editing support to radio
- color editing controls
- split screen into browser and editor
- removed dead code from coloreditor,
- new radio theme editing icon

* Resolve compile error & merge cleanup

* 1. fix exit key press in ThemeEditor.  It was not exiting screen
2. added ability to touch PageButton() on ColorEditPage()

Co-authored-by: kkoenig1@optonline.com <kkoenig1@optonline.com>
Co-authored-by: Peter Feerick <peter.feerick@gmail.com>
This commit is contained in:
Kevin Koenig
2021-11-15 12:36:56 +11:00
committed by GitHub
co-authored by kkoenig1@optonline.com <kkoenig1@optonline.com> Peter Feerick
parent 30780d82ec
commit 7298f0ff00
35 changed files with 2997 additions and 99 deletions
+1
View File
@@ -10,3 +10,4 @@ cmake-build-*/
/*.vscode
radio/src/tests/googletest*
radio/src/tests/gtest*.tar.gz
.editorconfig
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+7
View File
@@ -20,6 +20,12 @@ set(GUI_SRC
bitmaps.cpp
theme.cpp
theme_manager.cpp
color_editor_popup.cpp
color_editor.cpp
color_list.cpp
preview_window.cpp
file_carosell.cpp
listbox.cpp
tabsgroup.cpp
page.cpp
fab_button.cpp
@@ -56,6 +62,7 @@ set(GUI_SRC
radio_hardware.cpp
radio_diagkeys.cpp
radio_diaganas.cpp
radio_theme.cpp
radio_calibration.cpp
view_about.cpp
view_main.cpp
+318
View File
@@ -0,0 +1,318 @@
/*
* 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 "color_editor.h"
constexpr int BAR_MARGIN = 5;
constexpr int BAR_TOP_MARGIN = 5;
constexpr int BAR_HEIGHT_OFFSET = BAR_TOP_MARGIN + 25;
const char *RGBChars[MAX_BARS] = { "R", "G", "B" };
const char *HSVChars[MAX_BARS] = { "H", "S", "V" };
// ColorTypes()
// A ColorType implements a three bar color editor. Currently we support HSV and RGB
enum HSV_BAR_TYPE
{
HUE = 0,
SATURATION = 1,
BRIGHTNESS = 2
};
HSVColorType::HSVColorType(FormGroup *window, uint32_t color) :
ColorType(window->height() - BAR_HEIGHT_OFFSET)
{
auto r = GET_RED(color), g = GET_GREEN(color), b = GET_BLUE(color);
float values[MAX_BARS];
RGBtoHSV(r, g, b, values[0], values[1], values[2]);
values[1] *= MAX_SATURATION; // convert the proper base
values[2] *= MAX_BRIGHTNESS;
auto spacePerBar = (window->width() / MAX_BARS);
int leftPos = 0;
for (auto i = 0; i < MAX_BARS; i++) {
barInfo[i].leftPos = leftPos + (i != 0 ? BAR_MARGIN : 0);
barInfo[i].barWidth = spacePerBar - BAR_MARGIN - 5;
barInfo[i].maxValue = i == 0 ? MAX_HUE : MAX_BRIGHTNESS;
barInfo[i].invert = i != 0;
barInfo[i].sliding = false;
barInfo[i].value = values[i];
leftPos += spacePerBar;
}
}
uint32_t HSVColorType::getRGB()
{
return HSVtoRGB(barInfo[0].value, barInfo[1].value, barInfo[2].value);
}
void HSVColorType::drawBarValue(BitmapBuffer *dc, int bar)
{
dc->drawText(barInfo[bar].leftPos, BAR_TOP_MARGIN + screenHeight + 9, HSVChars[bar], COLOR_THEME_PRIMARY1 | FONT(XXS));
dc->drawText(barInfo[bar].leftPos + 10, BAR_TOP_MARGIN + screenHeight + 3, std::to_string(barInfo[bar].value).c_str(), COLOR_THEME_PRIMARY1);
}
void HSVColorType::drawBar(BitmapBuffer* dc, int bar, getRGBFromPos getRGB)
{
int maxRange = screenHeight;
for (auto i = 0; i < maxRange; i++) {
auto rgb = getRGB(bar, i);
auto r = GET_RED(rgb);
auto g = GET_GREEN(rgb);
auto b = GET_BLUE(rgb);
dc->drawSolidHorizontalLine(barInfo[bar].leftPos, i + BAR_TOP_MARGIN, barInfo[bar].barWidth, COLOR2FLAGS(RGB(r, g, b)));
}
dc->drawSolidRect(barInfo[bar].leftPos, BAR_TOP_MARGIN, barInfo[bar].barWidth, screenHeight, 1, COLOR2FLAGS(BLACK));
dc->drawFilledCircle(barInfo[bar].leftPos + (barInfo[bar].barWidth / 2), BAR_TOP_MARGIN + valueToScreen(bar, barInfo[bar].value), 5, COLOR2FLAGS(WHITE));\
drawBarValue(dc, bar);
}
void HSVColorType::paint(BitmapBuffer *dc)
{
drawBar(dc, HUE, [=] (int bar, int pos) {
int hue = screenToValue(0, pos);
auto rgb = HSVtoRGB(hue, barInfo[1].value, barInfo[2].value);
return rgb;
});
drawBar(dc, SATURATION, [=] (int bar, int pos) {
int saturation = screenToValue(1, pos);
auto rgb = HSVtoRGB(barInfo[0].value, saturation, barInfo[2].value);
return rgb;
});
drawBar(dc, BRIGHTNESS, [=] (int bar, int pos) {
int brightness = screenToValue(2, pos);
auto rgb = HSVtoRGB(barInfo[0].value, barInfo[1].value, brightness);
return rgb;
});
}
RGBColorType::RGBColorType(FormGroup *window, uint32_t color) :
ColorType(window->height() - BAR_HEIGHT_OFFSET)
{
screenHeight = screenHeight;
auto r = GET_RED(color), g = GET_GREEN(color), b = GET_BLUE(color);
float values[MAX_BARS];
values[0] = r; values[1] = g; values[2] = b;
auto spacePerBar = (window->width() / MAX_BARS);
int leftPos = 0;
for (auto i = 0; i < MAX_BARS; i++) {
barInfo[i].leftPos = leftPos + (i != 0 ? BAR_MARGIN : 0);
barInfo[i].barWidth = spacePerBar - BAR_MARGIN - 5;
barInfo[i].maxValue = 255;
barInfo[i].sliding = false;
barInfo[i].value = values[i];
barInfo[i].invert = true;
leftPos += spacePerBar;
}
}
uint32_t RGBColorType::getRGB()
{
return RGB(barInfo[0].value, barInfo[1].value, barInfo[2].value);
}
void RGBColorType::drawBarValue(BitmapBuffer *dc, int bar)
{
dc->drawText(barInfo[bar].leftPos, BAR_TOP_MARGIN + screenHeight + 9, RGBChars[bar], COLOR_THEME_PRIMARY1 | FONT(XXS));
dc->drawText(barInfo[bar].leftPos + 10, BAR_TOP_MARGIN + screenHeight + 3, std::to_string(barInfo[bar].value).c_str(), COLOR_THEME_PRIMARY1);
}
void RGBColorType::paint(BitmapBuffer *dc)
{
for (int bar = 0; bar < MAX_BARS; bar++) {
for (uint32_t pos = 0; pos < screenHeight; pos++) {
uint32_t value = screenToValue(bar, pos);
uint32_t color;
if (bar == 0)
color = RGB(value, 0, 0);
else if (bar == 1)
color = RGB(0, value, 0);
else
color = RGB(0, 0, value);
dc->drawSolidHorizontalLine(barInfo[bar].leftPos, pos + BAR_TOP_MARGIN, barInfo[bar].barWidth,
COLOR2FLAGS(color));
dc->drawSolidRect(barInfo[bar].leftPos, BAR_TOP_MARGIN, barInfo[bar].barWidth, screenHeight, 1,
COLOR2FLAGS(BLACK));
}
dc->drawFilledCircle(barInfo[bar].leftPos + (barInfo[bar].barWidth / 2), BAR_TOP_MARGIN + valueToScreen(bar, barInfo[bar].value), 5,
COLOR2FLAGS(WHITE));
drawBarValue(dc, bar);
}
}
/////////////////////////////////////////////////////////////////////////
////// ColorEditor Base class
/////////////////////////////////////////////////////////////////////////
ColorEditor::ColorEditor(FormGroup *window, const rect_t rect, uint32_t color,
std::function<void (uint32_t rgb)> setValue) :
FormGroup(window, rect),
_setValue(std::move(setValue)),
_color(color)
{
_colorType = new HSVColorType(this, color);
setFocusHandler([=](bool focus) {
_focused = focus;
});
}
void ColorEditor::setNextFocusBar()
{
_focusBar = (_focusBar + 1) % MAX_BARS;
setFocus();
invalidate();
}
void ColorEditor::setColorEditorType(COLOR_EDITOR_TYPE colorType)
{
if (_colorType != nullptr) {
delete _colorType;
}
if (colorType == RGB_COLOR_EDITOR)
_colorType = new RGBColorType(this, _color);
else
_colorType = new HSVColorType(this, _color);
invalidate();
}
void ColorEditor::setRGB()
{
_color = _colorType->getRGB();
invalidate();
if (_setValue != nullptr)
_setValue(_color);
}
#if defined(HARDWARE_TOUCH)
bool ColorEditor::onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY)
{
if (touchState.event == TE_SLIDE_END) {
onTouchEnd(0,0);
return true;
}
for (auto i = 0; i < MAX_BARS; i++) {
if (_colorType->barInfo[i].sliding) {
y -= BAR_TOP_MARGIN;
auto value = _colorType->screenToValue(i, y);
if (value != _colorType->barInfo[i].value) {
slidingWindow = this;
_colorType->barInfo[i].value = value;
setRGB();
}
}
}
return true;
}
bool ColorEditor::onTouchEnd(coord_t x, coord_t y)
{
bool bSliding = false;
for (auto i = 0; i < MAX_BARS; i++) {
if (_colorType->barInfo[i].sliding) {
bSliding = true;
}
_colorType->barInfo[i].sliding = false;
}
if (bSliding) {
invalidate();
}
return FormGroup::onTouchEnd(x,y);
}
bool ColorEditor::onTouchStart(coord_t x, coord_t y)
{
bool bFound = false;
for (auto i = 0; i < MAX_BARS; i++) {
if (y >= BAR_TOP_MARGIN && y < (coord_t)(BAR_TOP_MARGIN + _colorType->screenHeight) &&
x >= _colorType->barInfo[i].leftPos && x < _colorType->barInfo[i].leftPos + _colorType->barInfo[i].barWidth) {
_colorType->barInfo[i].sliding = true;
y -= BAR_TOP_MARGIN;
int value = _colorType->screenToValue(i, y);
_colorType->barInfo[i].value = value;
bFound = true;
_focusBar = i;
setFocus();
} else {
_colorType->barInfo[i].sliding = false;
}
}
if (bFound) {
setRGB();
return true;
} else {
return FormGroup::onTouchStart(x,y);
}
}
#endif
#if defined(HARDWARE_KEYS)
void ColorEditor::onEvent(event_t event)
{
switch(event)
{
case EVT_ROTARY_RIGHT:
case EVT_ROTARY_LEFT:
{
int direction = event == EVT_ROTARY_RIGHT ? 1 : -1;
auto bar = &_colorType->barInfo[_focusBar];
int newValue = (int) bar->value;
newValue += direction;
newValue = min(newValue, (int)bar->maxValue);
newValue = max(newValue, 0);
bar->value = newValue;
setRGB();
onKeyPress();
}
break;
default:
FormGroup::onEvent(event);
break;
}
}
#endif
void ColorEditor::drawFocusBox(BitmapBuffer *dc)
{
auto bar = _colorType->barInfo[_focusBar];
dc->drawSolidRect(bar.leftPos, BAR_TOP_MARGIN, bar.barWidth, _colorType->screenHeight, 2, COLOR_THEME_FOCUS);
}
void ColorEditor::paint(BitmapBuffer *dc)
{
dc->clear(COLOR_THEME_SECONDARY3);
_colorType->paint(dc);
drawFocusBox(dc);
}
+147
View File
@@ -0,0 +1,147 @@
/*
* 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 <algorithm>
#include <vector>
#include <iostream>
#include "bitmapbuffer.h"
#include "libopenui.h"
#include "theme_manager.h"
#include "listbox.h"
constexpr int MAX_BARS = 3;
typedef std::function<uint32_t (int bar, int pos)> getRGBFromPos;
class Bar
{
public:
bool sliding = false;
int leftPos = 0;
int barWidth = 0;
uint32_t maxValue = 0;
uint32_t value = 0;
bool invert = false;
};
class ColorType
{
public:
ColorType(coord_t screenHeight) :
screenHeight(screenHeight)
{
}
virtual ~ColorType()
{
};
virtual int valueToScreen(int bar, int value)
{
int scaledValue = (((float)value / barInfo[bar].maxValue) * screenHeight);
if (barInfo[bar].invert)
scaledValue = screenHeight - scaledValue;
return scaledValue;
}
virtual uint32_t screenToValue(int bar, int pos)
{
// range check
pos = min<int>(pos, screenHeight);
pos = max<int>(pos, 0);
uint32_t scaledValue = (((float)pos / screenHeight) * barInfo[bar].maxValue);
if (barInfo[bar].invert)
scaledValue = barInfo[bar].maxValue - scaledValue;
return scaledValue;
}
Bar barInfo[MAX_BARS];
virtual void paint(BitmapBuffer *dc) {}
virtual uint32_t getRGB() {return 0;}
uint32_t screenHeight;
};
class HSVColorType : public ColorType
{
public:
HSVColorType(FormGroup *window, uint32_t color);
uint32_t getRGB() override;
void paint(BitmapBuffer* dc) override;
protected:
void drawBar(BitmapBuffer *dc, int bar, getRGBFromPos getRGB);
void drawBarValue(BitmapBuffer *dc, int bar);
};
class RGBColorType : public ColorType
{
public:
RGBColorType(FormGroup *window, uint32_t color);
uint32_t getRGB() override;
void paint(BitmapBuffer* dc) override;
void drawBarValue(BitmapBuffer *dc, int bar);
};
enum COLOR_EDITOR_TYPE
{
RGB_COLOR_EDITOR,
HSV_COLOR_EDITOR
};
// the ColorEditor() control is a group of other controls
class ColorEditor : public FormGroup
{
public:
ColorEditor(FormGroup *window, const rect_t rect, uint32_t color, std::function<void (uint32_t rgb)> setValue = nullptr);
#if defined(DEBUG_WINDOWS)
std::string getName() const override
{
return "ColorEditor";
}
#endif
void setColorEditorType(COLOR_EDITOR_TYPE colorType);
#if defined(HARDWARE_TOUCH)
bool onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY) override;
bool onTouchEnd(coord_t x, coord_t y) override;
bool onTouchStart(coord_t x, coord_t y) override;
#endif
void setNextFocusBar();
#if defined(HARDWARE_KEYS)
void onEvent(event_t event) override;
#endif
void paint(BitmapBuffer *dc) override;
protected:
ColorType *_colorType = nullptr;
void drawColorBox(BitmapBuffer *dc);
void drawFocusBox(BitmapBuffer *dc);
void setRGB();
std::function<void (uint32_t rgb)> _setValue;
uint32_t _color;
int _focusBar = 0;
bool _focused = false;
};
@@ -0,0 +1,264 @@
/*
* 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 "color_editor_popup.h"
#define SET_DIRTY() setDirty()
////////////////////////////////////////////////////////////////////////////////////////////////
///// ColorEditorContent
////////////////////////////////////////////////////////////////////////////////////////////////
constexpr int GRID_TOP_MARGIN = 60;
constexpr int HUE_BAR_TOP_MARGIN = 35;
constexpr int HUE_BAR_HEIGHT = 20;
constexpr int COLOR_BOX_TOP = 130;
constexpr int COLOR_BOX_WIDTH = 90;
constexpr int COLOR_BOX_HEIGHT = 32;
#if (LCD_W > LCD_H)
constexpr int COLOR_BOX_LEFT = 240;
constexpr int SCREEN_LEFT_MARGIN = 10;
constexpr rect_t contentRect = { 50, 50, 360 + 20, 170 };
#else
constexpr int COLOR_BOX_LEFT = 210;
constexpr rect_t contentRect = { 5, 50, LCD_W - 10, 170 };
constexpr int SCREEN_LEFT_MARGIN = 0;
#endif
ColorEditorContent::ColorEditorContent(ModalWindow *window, const rect_t rect, uint32_t color, std::function<void (uint32_t rgb)> setValue) :
ModalWindowContent(window, rect),
setValue(std::move(setValue))
{
r = GET_RED(color); g = GET_GREEN(color); b = GET_BLUE(color);
float h, s, v;
RGBtoHSV(r, g, b, h, s, v);
hue = (int) h;
this->s = 100 * s;
this->v = 100 * v;
rect_t rText = {COLOR_BOX_LEFT, 60, 50, 20};
rect_t rNumber = {COLOR_BOX_LEFT + 60, 60, 40, 20};
new StaticText(this, rText, STR_RED, 0, COLOR_THEME_PRIMARY1);
rText.y += 23;
rEdit = new NumberEdit(this, rNumber, 0, 255, [=] () { return r; }, [=] (int value) { r = value; });
rNumber.y += 23;
new StaticText(this, rText, STR_GREEN, 0, COLOR_THEME_PRIMARY1);
rText.y += 23;
gEdit = new NumberEdit(this, rNumber, 0, 255, [=] () { return g; }, [=] (int value) { g = value; });
rNumber.y += 23;
new StaticText(this, rText, STR_BLUE, 0, COLOR_THEME_PRIMARY1);
bEdit = new NumberEdit(this, rNumber, 0, 255, [=] () { return b; }, [=] (int value) { b = value; });
invalidate();
}
void ColorEditorContent::setRGB()
{
int rgb = HSVtoRGB(hue, s, v);
r = GET_RED(rgb);
g = GET_GREEN(rgb);
b = GET_BLUE(rgb);
rEdit->setValue(r);
gEdit->setValue(g);
bEdit->setValue(b);
invalidate();
if (setValue != nullptr)
setValue(rgb);
}
#if defined(HARDWARE_TOUCH)
bool ColorEditorContent::onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY)
{
if (touchState.event == TE_SLIDE_END) {
sliding = false;
colorPicking = false;
slidingWindow = nullptr;
invalidate();
} else if (sliding) {
x -= SCREEN_LEFT_MARGIN;
x = max(x, 0);
x = min(x, MAX_HUE);
if (hue != x) {
hue = x;
slidingWindow = this; // KLK: (Hack IMHO) so we get the end slide message
setRGB();
}
} else if (colorPicking) {
s = min((x - SCREEN_LEFT_MARGIN) / 2, MAX_SATURATION);
s = max(s, 0);
v = min(MAX_BRIGHTNESS - ((y - GRID_TOP_MARGIN)), MAX_BRIGHTNESS);
v = max(v, 0);
setRGB();
}
return true;
}
bool ColorEditorContent::onTouchEnd(coord_t x, coord_t y)
{
sliding = false;
colorPicking = false;
invalidate();
return true;
}
bool ColorEditorContent::onTouchStart(coord_t x, coord_t y)
{
if (! sliding && (y > HUE_BAR_TOP_MARGIN && y < HUE_BAR_TOP_MARGIN + HUE_BAR_HEIGHT))
{
hue = x - SCREEN_LEFT_MARGIN;
hue = max(hue, 0);
hue = min(hue, MAX_HUE);
sliding = true;
invalidate();
} else if (x >= SCREEN_LEFT_MARGIN && x <= SCREEN_LEFT_MARGIN + (MAX_SATURATION * 2) && y >= GRID_TOP_MARGIN && y <= GRID_TOP_MARGIN + MAX_BRIGHTNESS) {
colorPicking = true;
v = min(MAX_BRIGHTNESS - (y - GRID_TOP_MARGIN), MAX_BRIGHTNESS);
s = min((x - SCREEN_LEFT_MARGIN) / 2, MAX_SATURATION);
setRGB();
}
return true;
}
#endif
#if defined(HARDWARE_KEYS)
void ColorEditorContent::onEvent(event_t event)
{
int oldSliderVal = hue;
if (event == EVT_ROTARY_LEFT) {
hue -= 2;
killAllEvents();
}
else if (event == EVT_ROTARY_RIGHT) {
hue += 2;
killAllEvents();
}
hue = max(hue, 0);
hue = min(hue, MAX_HUE);
if (hue != oldSliderVal) {
invalidate();
}
}
#endif
void ColorEditorContent::drawHueBar(BitmapBuffer *dc)
{
for (int i = 0; i < MAX_HUE; i++) {
auto rgb = HSVtoRGB(i, MAX_SATURATION, MAX_BRIGHTNESS);
auto r = GET_RED(rgb);
auto g = GET_GREEN(rgb);
auto b = GET_BLUE(rgb);
dc->drawSolidVerticalLine(i + SCREEN_LEFT_MARGIN, HUE_BAR_TOP_MARGIN, HUE_BAR_HEIGHT, COLOR2FLAGS(RGB(r, g, b)));
}
dc->drawFilledCircle(hue + SCREEN_LEFT_MARGIN, HUE_BAR_TOP_MARGIN + (HUE_BAR_HEIGHT / 2), 5, COLOR2FLAGS(WHITE));
if (sliding) {
dc->drawText(hue + (SCREEN_LEFT_MARGIN / 2), 25, std::to_string(hue).c_str(), FONT(XXS) | COLOR2FLAGS(WHITE));
}
}
void ColorEditorContent::drawGrid(BitmapBuffer *dc)
{
for (int s = 0; s <= MAX_SATURATION; s++) {
for (int v = MAX_BRIGHTNESS; v >= 0; v--) {
uint32_t rgbVal = HSVtoRGB(hue, s, v);
dc->drawPixel((s * 2) + SCREEN_LEFT_MARGIN , ((MAX_BRIGHTNESS - v)) + GRID_TOP_MARGIN, rgbVal);
dc->drawPixel((s * 2) + SCREEN_LEFT_MARGIN + 1, ((MAX_BRIGHTNESS - v)) + GRID_TOP_MARGIN, rgbVal);
}
}
dc->drawFilledCircle((this->s * 2) + SCREEN_LEFT_MARGIN, (MAX_BRIGHTNESS - this->v) + GRID_TOP_MARGIN, 3, COLOR2FLAGS(WHITE));
}
void ColorEditorContent::drawColorBox(BitmapBuffer *dc)
{
int32_t rgb = RGB(r,g,b);
dc->drawSolidFilledRect(COLOR_BOX_LEFT, COLOR_BOX_TOP, COLOR_BOX_WIDTH, COLOR_BOX_HEIGHT, COLOR2FLAGS(rgb));
dc->drawSolidRect(COLOR_BOX_LEFT, COLOR_BOX_TOP, COLOR_BOX_WIDTH, COLOR_BOX_HEIGHT, 1, COLOR2FLAGS(BLACK));
}
void ColorEditorContent::paint(BitmapBuffer *dc)
{
ModalWindowContent::paint(dc);
drawHueBar(dc);
drawGrid(dc);
drawColorBox(dc);
}
////////////////////////////////////////////////////////////////////////////////////////////////
///// ColorEditorPopup
////////////////////////////////////////////////////////////////////////////////////////////////
ColorEditorPopup::ColorEditorPopup(Window *window, std::function<uint32_t ()> getValue, std::function<void (uint32_t value)> setValue) :
ModalWindow(window, true),
_getValue(getValue),
_setValue(setValue)
{
color = _getValue();
content = new ColorEditorContent(this, contentRect, color, [=] (uint32_t rgb) {
if (_setValue != nullptr)
_setValue(rgb);
});
content->setFocus();
bringToTop();
content->setTitle(STR_COLOR_PICKER);
killAllEvents();
}
#if defined(HARDWARE_TOUCH)
bool ColorEditorPopup::onTouchStart(coord_t x, coord_t y)
{
content->onTouchStart(x - content->rect.x + content->scrollPositionX, y - content->rect.y + content->scrollPositionY);
return true;
}
bool ColorEditorPopup::onTouchEnd(coord_t x, coord_t y)
{
content->onTouchEnd(x - content->rect.x + content->scrollPositionX, y - content->rect.y + content->scrollPositionY);
if (!Window::onTouchEnd(x, y) && closeWhenClickOutside) {
onKeyPress();
deleteLater();
}
return true;
}
#endif
void ColorEditorPopup::deleteLater(bool detach, bool trash)
{
if (_deleted)
return;
Layer::pop(this);
Window::deleteLater(detach, trash);
}
@@ -0,0 +1,98 @@
/*
* 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 <algorithm>
#include <vector>
#include <iostream>
#include "bitmapbuffer.h"
#include "libopenui.h"
#include "theme_manager.h"
#include "listbox.h"
// the content page of the ColorEditorPupup
class ColorEditorContent : public ModalWindowContent
{
friend class ColorEditorPopup;
public:
ColorEditorContent(ModalWindow *window, const rect_t rect, uint32_t color, std::function<void (uint32_t rgb)> setValue = nullptr);
#if defined(DEBUG_WINDOWS)
std::string getName() const override
{
return "ColorEditorContent";
}
#endif
#if defined(HARDWARE_TOUCH)
bool onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY) override;
bool onTouchEnd(coord_t x, coord_t y) override;
bool onTouchStart(coord_t x, coord_t y) override;
#endif
#if defined(HARDWARE_KEYS)
void onEvent(event_t event) override;
#endif
void paint(BitmapBuffer *dc) override;
protected:
bool sliding = false;
bool colorPicking = false;
int r, g, b;
int hue = 0, s, v;
NumberEdit *rEdit;
NumberEdit *gEdit;
NumberEdit *bEdit;
void drawHueBar(BitmapBuffer *dc);
void drawGrid(BitmapBuffer *dc);
void drawColorBox(BitmapBuffer *dc);
void setRGB();
std::function<void (uint32_t rgb)> setValue;
};
// a color editor popup
class ColorEditorPopup : public ModalWindow
{
public:
ColorEditorPopup(Window *window, std::function<uint32_t ()> getValue, std::function<void (uint32_t value)> setValue = nullptr);
void deleteLater(bool detach = true, bool trash = true) override;
#if defined(DEBUG_WINDOWS)
std::string getName() const override
{
return "ColorEditorPopup";
}
#endif
#if defined(HARDWARE_TOUCH)
bool onTouchStart(coord_t x, coord_t y) override;
bool onTouchEnd(coord_t x, coord_t y) override;
#endif
protected:
uint32_t color;
ColorEditorContent *content;
std::function<uint32_t ()> _getValue;
std::function<void (uint32_t value)> _setValue;
};
+53
View File
@@ -0,0 +1,53 @@
/*
* 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 "color_list.h"
#include "color_editor.h"
#include "color_editor.h"
ColorList::ColorList(
Window *parent, const rect_t &rect, std::vector<ColorEntry> colors,
WindowFlags windowFlag, LcdFlags lcdFlags) :
ListBase(parent, rect, getColorListNames(colors), nullptr, nullptr),
_colorList(colors),
_tp(ThemePersistance::instance())
{
setSelected(0);
}
std::vector<std::string> ColorList::getColorListNames(std::vector<ColorEntry> colors)
{
std::vector<std::string> names;
char **colorNames = ThemePersistance::getColorNames();
for (auto color : colors) {
names.emplace_back(colorNames[color.colorNumber]);
}
return names;
}
void ColorList::drawLine(BitmapBuffer *dc, const rect_t &rect, uint32_t index, LcdFlags lcdFlags)
{
ListBase::drawLine(dc, rect, index, lcdFlags);
auto fontHeight = getFontHeight(FONT(STD));
dc->drawSolidFilledRect(rect.w - 22, rect.y, 16, fontHeight,
COLOR2FLAGS(_colorList[index].colorValue));
dc->drawSolidRect(rect.w - 22, rect.y, 16, fontHeight, 1, COLOR2FLAGS(BLACK));
}
+57
View File
@@ -0,0 +1,57 @@
/*
* 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 <algorithm>
#include <vector>
#include <iostream>
#include "bitmapbuffer.h"
#include "libopenui.h"
#include "theme_manager.h"
#include "listbox.h"
// a list of color theme names with their color to the right
class ColorList : public ListBase
{
public:
ColorList(Window *parent, const rect_t &rect, std::vector<ColorEntry> colors,
WindowFlags windowFlags = 0, LcdFlags lcdFlags = 0);
inline ColorEntry getSelectedColor()
{
return _colorList[selected];
}
std::vector<std::string> getColorListNames(std::vector<ColorEntry> colors);
inline void setColorList(std::vector<ColorEntry> colorList)
{
this->_colorList = colorList;
ListBase::setNames(getColorListNames(colorList));
invalidate();
}
void drawLine(BitmapBuffer *dc, const rect_t &rect, uint32_t index, LcdFlags lcdFlags) override;
protected:
std::vector<ColorEntry> _colorList;
ThemePersistance *_tp;
};
+80
View File
@@ -20,6 +20,86 @@
*/
#include "colors.h"
#include <algorithm>
#include <math.h>
uint16_t lcdColorTable[LCD_COLOR_COUNT];
uint32_t HSVtoRGB(float H, float S, float V)
{
if (H > 360 || H < 0 || S > 100 || S < 0 || V > 100 || V < 0) {
return 0;
}
float s = S / 100;
float v = V / 100;
float C = s * v;
float X = C * (1 - abs(fmod(H / 60.0, 2) - 1));
float m = v - C;
float r, g, b;
if (H >= 0 && H < 60) {
r = C, g = X, b = 0;
}
else if (H >= 60 && H < 120) {
r = X, g = C, b = 0;
}
else if (H >= 120 && H < 180) {
r = 0, g = C, b = X;
}
else if (H >= 180 && H < 240) {
r = 0, g = X, b = C;
}
else if (H >= 240 && H < 300) {
r = X, g = 0, b = C;
}
else {
r = C, g = 0, b = X;
}
int R = (r + m) * 255;
int G = (g + m) * 255;
int B = (b + m) * 255;
return RGB(R, G, B);
}
void RGBtoHSV(uint8_t R, uint8_t G, uint8_t B, float& fH, float& fS, float& fV)
{
float fR = (float)R / 255;
float fG = (float)G / 255;
float fB = (float)B / 255;
float fCMax = std::max(std::max(fR, fG), fB);
float fCMin = std::min(std::min(fR, fG), fB);
float fDelta = fCMax - fCMin;
if (fDelta > 0) {
if (fCMax == fR) {
fH = 60 * (fmod(((fG - fB) / fDelta), 6));
}
else if (fCMax == fG) {
fH = 60 * (((fB - fR) / fDelta) + 2);
}
else if (fCMax == fB) {
fH = 60 * (((fR - fG) / fDelta) + 4);
}
if (fCMax > 0) {
fS = fDelta / fCMax;
}
else {
fS = 0;
}
fV = fCMax;
}
else {
fH = 0;
fS = 0;
fV = fCMax;
}
if (fH < 0) {
fH = 360 + fH;
}
}
+7
View File
@@ -91,4 +91,11 @@ inline void lcdSetColor(uint16_t color)
#define WINDOWS_INSPECT_BORDER_COLOR COLOR_THEME_PRIMARY3
#endif
constexpr int MAX_SATURATION = 100;
constexpr int MAX_HUE = 360;
constexpr int MAX_BRIGHTNESS = 100;
extern uint32_t HSVtoRGB(float H, float S, float V);
extern void RGBtoHSV(uint8_t R, uint8_t G, uint8_t B, float& fH, float& fS, float& fV);
#endif // _COLORS_H_
+64
View File
@@ -0,0 +1,64 @@
/*
* 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 "file_carosell.h"
extern inline tmr10ms_t getTicks()
{
return g_tmr10ms;
}
#define PAGE_INTERVAL ((1000 / 10) * 2)
FileCarosell::FileCarosell(Window *parent, const rect_t &rect,
std::vector<std::string> fileNames, FormField *nextCtrl) :
FormGroup(parent, rect, NO_FOCUS | FORM_NO_BORDER),
_fileNames(fileNames),
fp(new FilePreview(this, {0, 0, rect.w, rect.h}, false))
{
timer = getTicks();
setSelected(0);
}
FileCarosell::~FileCarosell()
{
}
void FileCarosell::setFileNames(std::vector<std::string> fileNames)
{
_fileNames = fileNames;
selected = -1; // so it passes the sameness comparison
setSelected(0);
timer = getTicks();
}
void FileCarosell::checkEvents()
{
FormGroup::checkEvents();
uint32_t newTicks = getTicks();
if (newTicks - timer > PAGE_INTERVAL && _fileNames.size()) {
int newSelected = (selected + 1) % _fileNames.size();
setSelected(newSelected);
timer = newTicks;
}
}
+52
View File
@@ -0,0 +1,52 @@
/*
* 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 "color_editor.h"
#include "file_preview.h"
#include "file_carosell.h"
#include "tabsgroup.h"
class FileCarosell : public FormGroup
{
public:
FileCarosell(Window *parent, const rect_t &rect,
std::vector<std::string> fileNames, FormField *nextCtrl = nullptr);
~FileCarosell();
inline void setSelected(int n)
{
if (n == selected) return;
if (n >= 0 && n < (int)_fileNames.size()) {
selected = n;
fp->setFile(_fileNames[selected].c_str());
} else
fp->setFile("");
}
void setFileNames(std::vector<std::string> fileNames);
void checkEvents() override;
protected:
int selected = -1;
std::vector<std::string> _fileNames;
FilePreview *fp;
uint32_t timer;
};
+20 -8
View File
@@ -25,10 +25,17 @@
class FilePreview : public Window
{
public:
FilePreview(Window *parent, const rect_t &rect) :
Window(parent, rect, NO_SCROLLBAR)
FilePreview(Window *parent, const rect_t &rect, bool drawCentered = true) :
Window(parent, rect, NO_SCROLLBAR),
_drawCentered(drawCentered)
{
}
~FilePreview()
{
if (bitmap != nullptr)
delete bitmap;
}
#if defined(DEBUG_WINDOWS)
std::string getName() const override
@@ -39,7 +46,10 @@ class FilePreview : public Window
void setFile(const char *filename)
{
delete bitmap;
if (bitmap != nullptr)
delete bitmap;
bitmap = nullptr;
const char *ext = getFileExtension(filename);
if (ext && isExtensionMatching(ext, BITMAPS_EXT)) {
bitmap = BitmapBuffer::loadBitmap(filename);
@@ -52,16 +62,18 @@ class FilePreview : public Window
void paint(BitmapBuffer *dc) override
{
dc->setFormat(BMP_RGB565);
coord_t y = parent->getScrollPositionY() + 2;
coord_t h = MENU_BODY_HEIGHT - 4;
if (bitmap) {
coord_t y = _drawCentered ? parent->getScrollPositionY() + 2 : 2;
coord_t h = _drawCentered ? MENU_BODY_HEIGHT - 4 : rect.h;
if (bitmap != nullptr) {
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);
dc->drawScaledBitmap(bitmap, (width() - bitmapWidth) / 2,
_drawCentered ? y + (h - bitmapHeight) / 2 : 0,
bitmapWidth, bitmapHeight);
}
}
protected:
BitmapBuffer *bitmap = nullptr;
bool _drawCentered = true;
};
+208
View File
@@ -0,0 +1,208 @@
/*
* 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 "listbox.h"
constexpr int LONG_PRESS_10MS = 40;
constexpr int ACTIVE_RADIUS = 5;
extern inline tmr10ms_t getTicks()
{
return g_tmr10ms;
}
ListBase::ListBase(Window *parent, const rect_t &rect, std::vector<std::string> names,
std::function<uint32_t()> getValue,
std::function<void(uint32_t)> setValue,
uint8_t lineHeight,
WindowFlags windowFlags, LcdFlags lcdFlags) :
FormField(parent, rect, windowFlags),
names(names),
_getValue(std::move(getValue)),
_setValue(std::move(setValue)),
lineHeight(lineHeight)
{
#if defined(HARDWARE_TOUCH)
duration10ms = 0;
#endif
setInnerHeight(names.size() * lineHeight);
if (_getValue != nullptr)
setSelected(_getValue());
else
setSelected(0);
}
void ListBase::setSelected(int selected)
{
if (selected >= 0 && selected < (int)names.size()) {
this->selected = selected;
setScrollPositionY(lineHeight * this->selected - lineHeight);
if (_setValue != nullptr) {
_setValue(this->selected);
}
invalidate();
}
}
void ListBase::drawLine(BitmapBuffer *dc, const rect_t &rect, uint32_t index, LcdFlags lcdFlags)
{
std::string name = names[index];
int x = rect.x;
if ((uint32_t)activeIndex == index) {
LcdFlags circleColor = index == selected ? COLOR_THEME_PRIMARY2 : COLOR_THEME_PRIMARY1;
auto textWidth = getTextWidth(name.c_str(), name.length(), lcdFlags);
auto fontHeight = getFontHeight(FONT(STD));
dc->drawFilledCircle(rect.x + 4 + textWidth + ACTIVE_RADIUS, rect.y + fontHeight / 2, ACTIVE_RADIUS, circleColor);
}
dc->drawText(x, rect.y, name.c_str(), lcdFlags);
}
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);
}
LcdFlags textColor =
n == selected ? COLOR_THEME_PRIMARY2 : COLOR_THEME_PRIMARY1;
auto fontHeight = getFontHeight(FONT(STD));
drawLine(dc, { 8, curY + (lineHeight - fontHeight) / 2, rect.w, lineHeight}, n, textColor);
curY += lineHeight;
}
if (!(windowFlags & (FORM_NO_BORDER | FORM_FORWARD_FOCUS))) {
dc->drawSolidRect(0, getScrollPositionY(), rect.w, rect.h, 2,
COLOR_THEME_FOCUS);
}
}
#if defined(HARDWARE_KEYS)
void ListBase::onEvent(event_t event)
{
int oldSelected = selected;
switch (event) {
case EVT_ROTARY_RIGHT:
oldSelected = (selected + 1) % names.size();
setSelected(oldSelected);
onKeyPress();
break;
case EVT_ROTARY_LEFT:
oldSelected--;
if (oldSelected < 0) oldSelected = names.size() - 1;
setSelected(oldSelected);
onKeyPress();
break;
case EVT_KEY_LONG(KEY_ENTER):
if (longPressHandler) {
killEvents(event);
longPressHandler(event);
}
break;
case EVT_KEY_BREAK(KEY_ENTER):
if (pressHandler) {
killEvents(event);
pressHandler(event);
}
break;
default:
FormField::onEvent(event);
}
}
#endif
#if defined(HARDWARE_TOUCH)
bool ListBase::isLongPress()
{
unsigned int curTimer = getTicks();
return (slidingWindow == nullptr && duration10ms != 0 && curTimer - duration10ms > LONG_PRESS_10MS);
}
void ListBase::checkEvents(void)
{
Window::checkEvents();
if (isLongPress()) {
if (longPressHandler) {
setSelected(yDown / lineHeight);
longPressHandler(0);
killAllEvents();
duration10ms = 0;
return;
}
}
}
bool ListBase::onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY)
{
if (touchState.event == TE_SLIDE_END) {
duration10ms = 0;
}
return FormField::onTouchSlide(x, y, startX, startY, slideX, slideY);
}
bool ListBase::onTouchStart(coord_t x, coord_t y)
{
if (duration10ms == 0) {
duration10ms = getTicks();
}
yDown = y;
return true; // stop the processing and say that i handled it
}
bool ListBase::onTouchEnd(coord_t x, coord_t y)
{
if (!isEnabled()) return false;
if (slidingWindow)
return false; // if we slide then this is not a selection
auto selected = yDown / lineHeight;
setSelected(selected);
duration10ms = 0;
if (!hasFocus()) {
setFocus(SET_FOCUS_DEFAULT);
}
if (pressHandler != nullptr) {
pressHandler(0);
}
invalidate();
return true;
}
#endif
+135
View File
@@ -0,0 +1,135 @@
/*
* 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 <algorithm>
#include <vector>
#include <iostream>
#include <string>
#include "bitmapbuffer.h"
#include "libopenui.h"
#include "touch.h"
// base class for lists of elements with names
class ListBase : public FormField
{
public:
ListBase(Window *parent, const rect_t &rect, std::vector<std::string> names,
std::function<uint32_t()> getValue = nullptr,
std::function<void(uint32_t)> setValue = nullptr,
uint8_t lineHeight = MENUS_LINE_HEIGHT,
WindowFlags windowFlags = 0, LcdFlags lcdFlags = 0);
// draw one line of the list. Default implementation just draws the names. Oher types of lists
// can draw anything they want.
virtual void drawLine(BitmapBuffer *dc, const rect_t &rect, uint32_t index, LcdFlags lcdFlags);
void paint (BitmapBuffer *dc) override;
virtual void setSelected(int selected);
void setLongPressHandler(std::function<void(event_t)> handler)
{
longPressHandler = std::move(handler);
}
void setPressHandler(std::function<void(event_t)> handler)
{
pressHandler = std::move(handler);
}
void setActiveIndex(int index)
{
if (index >= 0 && index < (int) names.size()) {
activeIndex = index;
} else {
activeIndex = -1;
}
invalidate();
}
inline int getSelected()
{
return selected;
}
inline void setLineHeight(uint8_t height)
{
lineHeight = height;
}
inline void setNames(std::vector<std::string> names)
{
this->names.assign(names.begin(), names.end());
setInnerHeight(names.size() * lineHeight);
invalidate();
}
#if defined(DEBUG_WINDOWS)
std::string getName() const override
{
return "ListBox";
}
#endif
#if defined(HARDWARE_KEYS)
void onEvent(event_t event) override;
#endif
#if defined(HARDWARE_TOUCH)
void checkEvents(void) override;
bool onTouchEnd(coord_t x, coord_t y) override;
bool onTouchStart(coord_t x, coord_t y) override;
bool onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY) override;
bool isLongPress();
#endif
protected:
std::function<void(event_t)> longPressHandler = nullptr;
std::function<void(event_t)> pressHandler = nullptr;
std::vector<std::string> names;
std::function<uint32_t()> _getValue;
std::function<void(uint32_t)> _setValue;
int lineHeight;
int32_t selected = -1;
int32_t activeIndex = -1;
#if defined(HARDWARE_TOUCH)
uint32_t duration10ms;
coord_t yDown;
#endif
};
class ListBox : public ListBase
{
public:
using ListBase::ListBase;
inline void setTitle(std::string title)
{
this->title = title;
}
protected:
std::string title;
};
+2
View File
@@ -29,6 +29,7 @@
#include "radio_trainer.h"
#include "radio_version.h"
#include "radio_hardware.h"
#include "radio_theme.h"
#include "myeeprom.h"
RadioMenu::RadioMenu():
@@ -37,6 +38,7 @@ RadioMenu::RadioMenu():
addTab(new RadioToolsPage());
addTab(new RadioSdManagerPage());
addTab(new RadioSetupPage());
addTab(new ThemeSetupPage());
addTab(new SpecialFunctionsPage(g_eeGeneral.customFn));
addTab(new RadioTrainerPage());
addTab(new RadioHardwarePage());
+1
View File
@@ -41,6 +41,7 @@ enum MenuIcons {
ICON_RADIO_TRAINER,
ICON_RADIO_HARDWARE,
ICON_RADIO_CALIBRATION,
ICON_RADIO_EDIT_THEME,
ICON_RADIO_VERSION,
ICON_MODEL,
ICON_MODEL_SETUP,
+24 -1
View File
@@ -43,6 +43,29 @@ class PageHeader: public FormGroup
}
#endif
void onEvent(event_t event) override
{
if (event == EVT_KEY_BREAK(KEY_PGUP)) {
onKeyPress();
FormField *focus = dynamic_cast<FormField *>(getFocus());
if (focus != nullptr && focus->getPreviousField()) {
focus->getPreviousField()->setFocus(SET_FOCUS_BACKWARD, focus);
}
} else if (event == EVT_KEY_BREAK(KEY_PGDN)) {
onKeyPress();
FormField *focus = dynamic_cast<FormField *>(getFocus());
if (focus != nullptr && focus->getNextField()) {
focus->getNextField()->setFocus(SET_FOCUS_FORWARD, focus);
}
} else if (event == EVT_KEY_FIRST(KEY_EXIT)) {
killEvents(event);
parent->deleteLater(); // destroy the Page
}
else {
FormGroup::onEvent(event);
}
}
void paint(BitmapBuffer * dc) override;
uint8_t getIcon() const
@@ -86,7 +109,7 @@ class Page: public Window
bool onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY) override
{
Window::onTouchSlide(x, y, startX, startY, slideX, slideY);
Window::onTouchSlide(x, y, startX, startY, 0/*slideX*/, slideY);
return true;
}
#endif
+23
View File
@@ -0,0 +1,23 @@
/*
* 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 "preview_window.h"
ColorMaintainer colorMaintainer;
+251
View File
@@ -0,0 +1,251 @@
/*
* 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.
*/
#ifndef PREVIEW_WINDOW_INCLUDE
#define PREVIEW_WINDOW_INCLUDE
#include <algorithm>
#include "libopenui.h"
#include "opentx.h"
#include "sliders.h"
#include "theme_manager.h"
#include "themes/480_bitmaps.h"
extern inline tmr10ms_t getTicks() { return g_tmr10ms; }
#define PREVIEW_WINDOW_REFRESH_INTERVAL ((1000 / 10) * 1)
// class to hold a color list and apply / restore it while drawing standard
// controls
class ColorMaintainer
{
public:
void setColorList(std::vector<ColorEntry> colorList)
{
this->colorList.assign(colorList.begin(), colorList.end());
}
void applyColorValues()
{
// save old values;
for (auto i = 0; i < COLOR_COUNT; i++) {
oldColorVals[i] = lcdColorTable[i];
}
for (auto color : colorList) {
lcdColorTable[color.colorNumber] = color.colorValue;
}
}
void restoreColorValues()
{
for (auto i = 0; i < COLOR_COUNT; i++) {
lcdColorTable[i] = oldColorVals[i];
}
}
protected:
uint32_t oldColorVals[COLOR_COUNT];
std::vector<ColorEntry> colorList;
};
extern ColorMaintainer colorMaintainer;
// override of controls to draw them using a selected theme
class ThemedTextEdit : public TextEdit
{
public:
using TextEdit::TextEdit;
void paint(BitmapBuffer *dc) override
{
colorMaintainer.applyColorValues();
TextEdit::paint(dc);
colorMaintainer.restoreColorValues();
}
};
class ThemedStaticText : public StaticText
{
public:
using StaticText::StaticText;
void paint(BitmapBuffer *dc) override
{
colorMaintainer.applyColorValues();
setTextFlags(COLOR_THEME_PRIMARY1);
StaticText::paint(dc);
colorMaintainer.restoreColorValues();
}
};
class ThemedCheckBox : public CheckBox
{
public:
ThemedCheckBox(Window *parent, rect_t rect, bool checked) :
CheckBox(
parent, rect, [=]() { return checked; }, [](uint8_t value) {}),
checked(checked)
{
}
void paint(BitmapBuffer *dc) override
{
colorMaintainer.applyColorValues();
CheckBox::paint(dc);
colorMaintainer.restoreColorValues();
}
protected:
bool checked;
};
class ThemedMainViewHorizontalTrim : public MainViewHorizontalTrim
{
public:
using MainViewHorizontalTrim::MainViewHorizontalTrim;
void paint(BitmapBuffer *dc) override
{
colorMaintainer.applyColorValues();
MainViewHorizontalTrim::paint(dc);
colorMaintainer.restoreColorValues();
}
};
class ThemedMainViewHorizontalSlider : public MainViewHorizontalSlider
{
public:
using MainViewHorizontalSlider::MainViewHorizontalSlider;
void paint(BitmapBuffer *dc) override
{
colorMaintainer.applyColorValues();
MainViewHorizontalSlider::paint(dc);
colorMaintainer.restoreColorValues();
}
};
// display controls using the appropriate theme.
class PreviewWindow : public FormGroup
{
public:
PreviewWindow(Window *window, rect_t rect,
std::vector<ColorEntry> colorList) :
FormGroup(window, rect, NO_FOCUS), _colorList(colorList)
{
new ThemedStaticText(this, {5, 40, 100, LINE_HEIGHT}, "Checkbox", 0,
COLOR_THEME_PRIMARY1);
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);
ticks = getTicks();
}
void setColorList(std::vector<ColorEntry> colorList)
{
_colorList = colorList;
}
void checkEvents() override
{
Window::checkEvents();
tmr10ms_t newTicks = getTicks();
if (newTicks - ticks > PREVIEW_WINDOW_REFRESH_INTERVAL) {
invalidate();
ticks = newTicks;
}
}
BitmapBuffer *getBitmap(const uint8_t *maskData, uint32_t bgColor,
uint32_t fgColor, int *width)
{
auto mask = BitmapBuffer::load8bitMask(maskData);
BitmapBuffer *newBm =
new BitmapBuffer(BMP_RGB565, mask->width(), mask->height());
newBm->clear(bgColor);
newBm->drawMask(0, 0, mask, fgColor);
delete mask;
return newBm;
}
void drawTime(BitmapBuffer *dc)
{
// time on top bar
struct gtm t;
char str[10];
char str1[20];
const char *const STR_MONTHS[] = TR_MONTHS;
gettime(&t);
sprintf(str, "%d %s\n", t.tm_mday, STR_MONTHS[t.tm_mon]);
getTimerString(str1, getValue(MIXSRC_TX_TIME));
strcat(str, str1);
dc->drawText(rect.w - 40, 5, str, COLOR_THEME_PRIMARY2 | FONT(XS));
}
void paint(BitmapBuffer *dc) override
{
colorMaintainer.setColorList(_colorList);
colorMaintainer.applyColorValues();
// background
dc->clear(COLOR_THEME_SECONDARY3);
// top bar background
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);
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->drawBitmap(x, 5, bm);
x += MENU_HEADER_BUTTON_WIDTH + 2;
delete bm;
bm = getBitmap(mask_radio_setup, COLOR_THEME_SECONDARY1,
COLOR_THEME_PRIMARY2, &width);
dc->drawBitmap(x, 5, bm);
delete bm;
drawTime(dc);
colorMaintainer.restoreColorValues();
}
protected:
std::vector<ColorEntry> _colorList;
CheckBox *checkBox;
tmr10ms_t ticks;
};
#endif
+586
View File
@@ -0,0 +1,586 @@
/*
* 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 "radio_theme.h"
#include <algorithm>
#include "bitmapbuffer.h"
#include "libopenui.h"
#include "opentx.h"
#include "file_preview.h"
#include "theme_manager.h"
#include "view_main.h"
#include "widget_settings.h"
#include "sliders.h"
#include "color_list.h"
#include "color_editor_popup.h"
#include "color_editor.h"
#include "listbox.h"
#include "preview_window.h"
#if (LCD_W > LCD_H)
#define COLOR_PREVIEW_WIDTH 18
#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)
#else
#define COLOR_PREVIEW_WIDTH 18
#define COLOR_PREVIEW_HEIGHT (LCD_H - TOPBAR_HEIGHT - 30)
#define LEFT_LIST_WIDTH LCD_W
#define LEFT_LIST_HEIGHT (LCD_H / 2 - 38)
#endif
#define MARGIN_WIDTH 5
#if (LCD_W > LCD_H)
constexpr int BUTTON_HEIGHT = 30;
constexpr int BUTTON_WIDTH = 75;
constexpr LcdFlags textFont = FONT(STD);
constexpr rect_t detailsDialogRect = {50, 50, 400, 170};
constexpr int labelWidth = 150;
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 LcdFlags textFont = FONT(XS);
constexpr rect_t detailsDialogRect = {5, 50, LCD_W - 10, 340};
constexpr int labelWidth = 120;
constexpr int COLOR_BOX_LEFT = 3;
constexpr int COLOR_BOX_WIDTH = 55;
constexpr int COLOR_BOX_HEIGHT = 32;
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
{
public:
ThemeDetailsDialog(Window *parent, ThemeFile theme, std::function<void (ThemeFile theme)> saveHandler = nullptr) :
Dialog(parent, STR_EDIT_THEME_DETAILS, detailsDialogRect),
theme(theme),
saveHandler(saveHandler)
{
FormGridLayout grid(detailsDialogRect.w);
grid.setLabelWidth(labelWidth);
grid.spacer(8);
new StaticText(&content->form, grid.getLabelSlot(), STR_NAME, 0, COLOR_THEME_PRIMARY1);
new TextEdit(&content->form, grid.getFieldSlot(), this->theme.getName(), NAME_LENGTH);
grid.nextLine();
new StaticText(&content->form, grid.getLabelSlot(), STR_AUTHOR, 0, COLOR_THEME_PRIMARY1);
new TextEdit(&content->form, grid.getFieldSlot(), this->theme.getAuthor(), AUTHOR_LENGTH);
grid.nextLine();
new StaticText(&content->form, grid.getLineSlot(), STR_DESCRIPTION, 0, COLOR_THEME_PRIMARY1);
grid.nextLine();
new TextEdit(&content->form, grid.getLineSlot(), this->theme.getInfo(), INFO_LENGTH);
grid.nextLine();
rect_t r = {detailsDialogRect.w - (BUTTON_WIDTH + 5), grid.getWindowHeight() + 5, BUTTON_WIDTH, BUTTON_HEIGHT };
new TextButton(&content->form, r, STR_SAVE, [=] () {
if (saveHandler != nullptr)
saveHandler(this->theme);
deleteLater();
return 0;
}, BUTTON_BACKGROUND | OPAQUE, textFont);
r.x -= (BUTTON_WIDTH + 5);
new TextButton(&content->form, r, STR_CANCEL, [=] () {
deleteLater();
return 0;
}, BUTTON_BACKGROUND | OPAQUE, textFont);
}
void onEvent(event_t event) override
{
if (event == EVT_KEY_BREAK(KEY_EXIT)) {
deleteLater();
}
}
protected:
ThemeFile theme;
std::function<void(ThemeFile theme)> saveHandler = nullptr;
};
class ColorSquare : public FormField
{
public:
ColorSquare(Window *window, const rect_t &rect, uint32_t color, WindowFlags windowFlags = 0)
: FormField(window, rect, windowFlags | NO_FOCUS),
_color(color)
{
}
void paint (BitmapBuffer *dc) override
{
dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR2FLAGS(_color));
dc->drawSolidRect(0, 0, rect.w, rect.h, 1, COLOR2FLAGS(BLACK));
}
void setColorToEdit(uint32_t colorEntry)
{
_color = colorEntry;
invalidate();
}
protected:
uint32_t _color;
};
void setHexStr(StaticText* hexBox, uint32_t rgb)
{
auto r = GET_RED(rgb), g = GET_GREEN(rgb), b = GET_BLUE(rgb);
char hexstr[80];
sprintf(hexstr, "%02X%02X%02X\n", (uint16_t)r, (uint16_t)g, (uint16_t)b);
hexBox->setText(hexstr);
}
class ColorEditPage : public Page
{
public:
ColorEditPage(ThemeFile theme, LcdColorIndex indexOfColor,
std::function<void (uint32_t rgb)> setValue = nullptr) :
Page(ICON_RADIO_EDIT_THEME),
_setValue(std::move(setValue)),
_indexOfColor(indexOfColor),
_theme(theme)
{
buildHead(&header);
buildBody(&body);
}
void setActiveColorBar(int activeTab)
{
if (activeTab >= 0 && _activeTab <= (int)_tabs.size()) {
_activeTab = activeTab;
for (auto i = 0; i < (int) _tabs.size(); i++)
_tabs[i]->check(i == _activeTab);
_colorEditor->setColorEditorType(_activeTab == 1 ? HSV_COLOR_EDITOR : RGB_COLOR_EDITOR);
onKeyPress();
}
}
#if defined(HARDWARE_KEYS)
void onEvent(event_t event ) override
{
if (event == EVT_KEY_BREAK(KEY_TELEM)) {
_colorEditor->setNextFocusBar();
} else if (event == EVT_KEY_BREAK(KEY_PGUP) || event == EVT_KEY_BREAK(KEY_PGDN)) {
int direction = event == EVT_KEY_BREAK(KEY_PGUP) ? -1 : 1;
int newActiveTab = _activeTab + direction;
if (newActiveTab < 0)
newActiveTab = 1;
if (newActiveTab >= (int) _tabs.size())
newActiveTab = 0;
setActiveColorBar(newActiveTab);
} else {
Page::onEvent(event);
}
}
#endif
protected:
std::function<void (uint32_t rgb)> _setValue;
LcdColorIndex _indexOfColor;
ThemeFile _theme;
TextButton *_cancelButton;
ColorEditor *_colorEditor;
PreviewWindow *_previewWindow;
std::vector<PageButton *> _tabs;
int _activeTab = 0;
ColorSquare *_colorSquare;
StaticText *_hexBox;
void buildBody(FormWindow* window)
{
rect_t r;
// hexBox
r = { COLOR_BOX_LEFT + COLOR_BOX_WIDTH + 5, 4, 90, 30 };
_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};
_colorEditor = new ColorEditor(window, r, _theme.getColorEntryByIndex(_indexOfColor)->colorValue,
[=](uint32_t rgb) {
_theme.setColor(_indexOfColor, rgb);
if (_colorSquare != nullptr) {
_colorSquare->setColorToEdit(rgb);
}
if (_previewWindow != nullptr) {
_previewWindow->setColorList(_theme.getColorList());
}
if (_hexBox != nullptr) {
setHexStr(_hexBox, rgb);
}
if (_setValue != nullptr)
_setValue(rgb);
});
_colorEditor->setColorEditorType(HSV_COLOR_EDITOR);
_activeTab = 1;
r = { COLOR_BOX_LEFT, 4, COLOR_BOX_WIDTH, COLOR_BOX_HEIGHT };
_colorSquare = new ColorSquare(window, r, _theme.getColorEntryByIndex(_indexOfColor)->colorValue);
r = LCD_W > LCD_H ?
rect_t { COLOR_LIST_WIDTH + MARGIN_WIDTH, 4, LCD_W - COLOR_LIST_WIDTH - MARGIN_WIDTH * 2, COLOR_LIST_HEIGHT } :
rect_t { 0, LEFT_LIST_HEIGHT + 4, LEFT_LIST_WIDTH, LEFT_LIST_HEIGHT - 4 };
_previewWindow = new PreviewWindow(window, r, _theme.getColorList());
}
void buildHead(PageHeader* window)
{
LcdFlags flags = 0;
if (LCD_W < LCD_H) {
flags = FONT(XS);
}
// page title
new StaticText(window,
{ PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT,
PAGE_LINE_HEIGHT },
"Edit Color", 0, COLOR_THEME_PRIMARY2 | flags);
new StaticText(window,
{ PAGE_TITLE_LEFT, PAGE_TITLE_TOP + PAGE_LINE_HEIGHT,
LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT },
ThemePersistance::getColorNames()[(int)_indexOfColor], 0, COLOR_THEME_PRIMARY2 | flags);
// page tabs
rect_t r = { LCD_W - (BUTTON_WIDTH + 5), 6, BUTTON_WIDTH, BUTTON_HEIGHT };
_tabs.emplace_back(
new PageButton(window, r, "RGB",
[=] () {
setActiveColorBar(0);
},
0, COLOR_THEME_PRIMARY1));
r.x -= (BUTTON_WIDTH + 5);
_tabs.emplace_back(
new PageButton(window, r, "HSV",
[=] () {
setActiveColorBar(1);
},
0, COLOR_THEME_PRIMARY1));
_tabs[1]->check(true);
}
};
class ThemeEditPage : public Page
{
public:
explicit ThemeEditPage(ThemeFile theme, std::function<void (ThemeFile &theme)> saveHandler = nullptr) :
Page(ICON_RADIO_EDIT_THEME),
_theme(theme),
page(this),
saveHandler(std::move(saveHandler))
{
buildBody(&body);
buildHeader(&header);
}
#if defined(HARDWARE_KEYS)
void onEvent(event_t event) override
{
if (event == EVT_KEY_BREAK(KEY_PGUP)) {
onKeyPress();
FormField *focus = dynamic_cast<FormField *>(getFocus());
if (focus != nullptr && focus->getPreviousField()) {
focus->getPreviousField()->setFocus(SET_FOCUS_BACKWARD, focus);
}
} else if (event == EVT_KEY_BREAK(KEY_PGDN)) {
onKeyPress();
FormField *focus = dynamic_cast<FormField *>(getFocus());
if (focus != nullptr && focus->getNextField()) {
focus->getNextField()->setFocus(SET_FOCUS_FORWARD, focus);
}
} else if (event == EVT_KEY_FIRST(KEY_EXIT)) {
deleteLater();
} else {
Window::onEvent(event);
}
}
#endif
void deleteLater(bool detach = true, bool trash = true) override
{
if (_dirty) {
if (confirmationDialog("Save Theme?", _theme.getName())) {
if (saveHandler != nullptr) {
saveHandler(_theme);
}
}
}
Page::deleteLater(detach, trash);
}
void editColorPage()
{
auto colorEntry = _cList->getSelectedColor();
new ColorEditPage(_theme, colorEntry.colorNumber,
[=] (uint32_t color) {
_dirty = true;
_theme.setColor(colorEntry.colorNumber, color);
_cList->setColorList(_theme.getColorList());
_previewWindow->setColorList(_theme.getColorList());
});
}
void buildHeader(FormGroup *window)
{
LcdFlags flags = 0;
if (LCD_W < LCD_H) {
flags = FONT(XS);
}
// page title
new StaticText(window,
{PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT,
PAGE_LINE_HEIGHT},
STR_EDIT_THEME, 0, COLOR_THEME_PRIMARY2 | flags);
_themeName = new StaticText(window,
{PAGE_TITLE_LEFT, PAGE_TITLE_TOP + PAGE_LINE_HEIGHT,
LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT},
_theme.getName(), 0, COLOR_THEME_PRIMARY2 | flags);
// save and cancel
rect_t r = {LCD_W - (BUTTON_WIDTH + 5), 6, BUTTON_WIDTH, BUTTON_HEIGHT };
_detailButton = new TextButton(window, r, STR_DETAILS, [=] () {
new ThemeDetailsDialog(page, _theme, [=] (ThemeFile t) {
_theme.setAuthor(t.getAuthor());
_theme.setInfo(t.getInfo());
_theme.setName(t.getName());
// update the theme name
_themeName->setText(_theme.getName());
_dirty = true;
});
return 0;
}, BUTTON_BACKGROUND | OPAQUE, textFont);
// setup the prev next controls so save and details are in the mix
_cList->setNextField(_detailButton);
_cList->setPreviousField(_detailButton);
_detailButton->setNextField(_cList);
_detailButton->setPreviousField(_cList);
}
void buildBody(FormGroup *window)
{
rect_t r = { 0, 4, COLOR_LIST_WIDTH, COLOR_LIST_HEIGHT};
_cList = new ColorList(window, r, _theme.getColorList());
_cList->setLongPressHandler([=] (event_t event) { editColorPage(); });
_cList->setPressHandler([=] (event_t event) { editColorPage(); });
if (LCD_W > LCD_H) {
r = { COLOR_LIST_WIDTH + MARGIN_WIDTH, 4, LCD_W - COLOR_LIST_WIDTH - MARGIN_WIDTH*2, COLOR_LIST_HEIGHT };
} else {
r = { 0, LEFT_LIST_HEIGHT + 4, LEFT_LIST_WIDTH, LEFT_LIST_HEIGHT - 4};
}
_previewWindow = new PreviewWindow(window, r, _theme.getColorList());
}
protected:
bool _dirty = false;
ThemeFile _theme;
Page *page;
std::function<void(ThemeFile &theme)> saveHandler = nullptr;
PreviewWindow *_previewWindow = nullptr;
ColorList *_cList = nullptr;
StaticText *_themeName = nullptr;
TextButton *_saveButton;
TextButton *_detailButton;
};
ThemeSetupPage::ThemeSetupPage() : PageTab(STR_THEME_EDITOR, ICON_RADIO_EDIT_THEME) {}
ThemeSetupPage::~ThemeSetupPage()
{
}
void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp)
{
auto menu = new Menu(listBox,false);
// you cant activate the active theme
if (listBox->getSelected() != tp->getThemeIndex()) {
menu->addLine(STR_ACTIVATE, [=]() {
tp->applyTheme(listBox->getSelected());
tp->setDefaultTheme(listBox->getSelected());
nameText->setTextFlags(COLOR_THEME_PRIMARY1);
authorText->setTextFlags(COLOR_THEME_PRIMARY1);
listBox->setActiveIndex(tp->getThemeIndex());
});
}
// you cant edit the default theme
if (listBox->getSelected() != 0) {
menu->addLine(STR_EDIT,
[=] () {
auto theme = tp->getThemeByIndex(currentTheme);
if (theme == nullptr) return;
new ThemeEditPage(*theme,
[=](ThemeFile &theme) {
auto curTheme = tp->getThemeByIndex(currentTheme);
if (curTheme != nullptr) {
curTheme->setName(theme.getName());
curTheme->setAuthor(theme.getAuthor());
curTheme->setInfo(theme.getInfo());
// update the colors that were edited
int n = 0;
for (auto color : theme.getColorList()) {
curTheme->setColorByIndex(n, color.colorValue);
n++;
}
// save it to disk so it will come back the next time.
curTheme->serialize();
// if the active theme was edited then activate it.
if (listBox->getSelected() == tp->getThemeIndex())
tp->applyTheme(listBox->getSelected());
// the list of theme names might have changed
listBox->setNames(tp->getNames());
}
});
});
}
menu->addLine(STR_DUPLICATE, [=] () {
ThemeFile newTheme;
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);
// 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->refresh();
listBox->setNames(tp->getNames());
listBox->setSelected(currentTheme);
}
});
});
// you cant delete the default theme or the currently active theme
if (listBox->getSelected() != 0 && listBox->getSelected() != tp->getThemeIndex()) {
menu->addLine(STR_DELETE, [=] () {
if (confirmationDialog("Delete Theme?", tp->getThemeByIndex(listBox->getSelected())->getName())) {
tp->deleteThemeByIndex(listBox->getSelected());
listBox->setNames(tp->getNames());
listBox->setSelected(currentTheme);
}
});
}
}
void ThemeSetupPage::setupListbox(FormWindow *window, rect_t r, ThemePersistance *tp)
{
listBox = new ListBox(
window, r, tp->getNames(),
[=]() {
return currentTheme;
},
[=](uint8_t value) {
if (themeColorPreview && authorText && nameText && fileCarosell) {
ThemeFile *theme = tp->getThemeByIndex(value);
themeColorPreview->setColorList(theme->getColorList());
authorText->setText(theme->getAuthor());
nameText->setText(theme->getName());
fileCarosell->setFileNames(theme->getThemeImageFileNames());
}
currentTheme = value;
});
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); });
}
void ThemeSetupPage::build(FormWindow *window)
{
auto tp = ThemePersistance::instance();
auto theme = tp->getCurrentTheme();
currentTheme = tp->getThemeIndex();
themeColorPreview = nullptr;
listBox = nullptr;
fileCarosell = nullptr;
nameText = nullptr;
authorText = nullptr;
// create listbox and setup menus
rect_t r = { 2, 3, LEFT_LIST_WIDTH, LEFT_LIST_HEIGHT };
setupListbox(window, r, tp);
if (LCD_W > LCD_H) {
r.x = LEFT_LIST_WIDTH + MARGIN_WIDTH;
r.w = LCD_W - r.x;
} else {
r.y += LEFT_LIST_HEIGHT + 4;
r.x = 0;
r.w = LCD_W;
}
// setup ThemeColorPreview()
auto colorList = theme != nullptr ? theme->getColorList() : std::vector<ColorEntry>();
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;
auto fileNames = theme != nullptr ? theme->getThemeImageFileNames() : std::vector<std::string>();
fileCarosell = new FileCarosell(window, r, fileNames, listBox);
// author and name of theme on right side of screen
r.x += 7;
r.y += 130;
r.h = 20;
nameText = new StaticText(window, r, theme != nullptr ? theme->getName() : "", 0,
COLOR_THEME_PRIMARY1);
if (theme != nullptr && strlen(theme->getAuthor())) {
r.y += 20;
char author[256];
strcpy(author, "By: ");
strcat(author, theme->getAuthor());
authorText = new StaticText(window, r, author, 0, COLOR_THEME_PRIMARY1);
}
}
+140
View File
@@ -0,0 +1,140 @@
/*
* 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 "tabsgroup.h"
#include "color_editor.h"
#include "file_preview.h"
#include "file_carosell.h"
constexpr int BOX_MARGIN = 2;
constexpr int MAX_BOX_WIDTH = 15;
class PageButton : public Window
{
public:
PageButton(FormGroup *parent, const rect_t &rect, std::string text,
std::function<void ()> setTab = nullptr,
WindowFlags windowFlags = BUTTON_BACKGROUND | OPAQUE,
LcdFlags textFlags = 0) :
Window(parent, rect, windowFlags | NO_FOCUS, textFlags),
_setTab(std::move(setTab)),
_text(text)
{
}
#if defined(HARDWARE_TOUCH)
bool onTouchEnd(coord_t x, coord_t y) override
{
if (_setTab != nullptr) {
_setTab();
}
return true;
}
#endif
void paint(BitmapBuffer *dc) override
{
uint32_t textColor = _checked ? COLOR_THEME_PRIMARY2 : COLOR_THEME_PRIMARY1;
dc->clear(_checked ? COLOR_THEME_ACTIVE : COLOR_THEME_SECONDARY2);
dc->drawText(rect.w / 2, 1 + (rect.h - getFontHeight(textFlags)) / 2,
_text.c_str(), CENTERED | textColor);
}
void check(bool checked)
{
_checked = checked;
invalidate();
}
private:
std::function<void ()> _setTab;
bool _checked = false;
std::string _text;
};
class ThemeColorPreview : public FormField
{
public:
ThemeColorPreview(Window *parent, const rect_t &rect, std::vector<ColorEntry> colorList) :
FormField(parent, rect, NO_FOCUS),
colorList(colorList)
{
setBoxWidth();
}
~ThemeColorPreview()
{
}
void setColorList(std::vector<ColorEntry> colorList)
{
this->colorList.assign(colorList.begin(), colorList.end());
setBoxWidth();
invalidate();
}
void paint(BitmapBuffer *dc) override
{
int totalNessessarySpace = colorList.size() * (boxWidth + 2);
int axis = rect.w > rect.h ? rect.w : rect.h;
axis = (axis - totalNessessarySpace) / 2;
for (auto color: colorList) {
if (rect.w > rect.h) {
dc->drawSolidFilledRect(axis, 0, boxWidth, boxWidth, COLOR2FLAGS(color.colorValue));
dc->drawSolidRect(axis, 0, boxWidth, boxWidth, 1, COLOR2FLAGS(BLACK));
} else {
dc->drawSolidFilledRect(0, axis, boxWidth, boxWidth, COLOR2FLAGS(color.colorValue));
dc->drawSolidRect(0, axis, boxWidth, boxWidth, 1, COLOR2FLAGS(BLACK));
}
axis += boxWidth + BOX_MARGIN;
}
}
protected:
std::vector<ColorEntry> colorList;
int boxWidth = MAX_BOX_WIDTH;
void setBoxWidth()
{
auto winSize = rect.w > rect.h ? rect.w : rect.h;
boxWidth = winSize / (colorList.size() + BOX_MARGIN);
boxWidth = min(boxWidth, MAX_BOX_WIDTH);
}
};
class ThemeSetupPage: public PageTab {
public:
ThemeSetupPage();
~ThemeSetupPage();
void build(FormWindow * window) override;
protected:
Window *previewWindow = nullptr;
FileCarosell *fileCarosell = nullptr;
ThemeColorPreview *themeColorPreview = nullptr;
ListBox *listBox = nullptr;
StaticText *authorText = nullptr;
StaticText *nameText = nullptr;
int currentTheme = 0;
void setupListbox(FormWindow *window, rect_t r, ThemePersistance *tp);
void displayThemeMenu(Window *window, ThemePersistance *tp);
};
+118 -8
View File
@@ -18,7 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <algorithm>
#include "screen_setup.h"
#include "opentx.h"
@@ -30,7 +29,10 @@
#include "theme_manager.h"
#include "file_preview.h"
#define SET_DIRTY() storageDirty(EE_MODEL)
#define SET_DIRTY() storageDirty(EE_MODEL)
#define BUTTON_HEIGHT 30
#define BUTTON_WIDTH 75
class LayoutChoice: public FormField
{
@@ -261,15 +263,113 @@ class SetupWidgetsPage: public FormWindow
// }
};
class ThemeEditorPage : public Page
{
public:
explicit ThemeEditorPage(Window *parent, ThemeFile *theme, WindowFlags flags = 0) :
Page(0),
theme(theme)
{
buildBody(&body);
buildHeader(&header);
}
void buildHeader(FormGroup *window)
{
ThemePersistance *tp = ThemePersistance::instance();
new StaticText(window,
{PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT,
PAGE_LINE_HEIGHT},
"Edit Theme", 0, COLOR_THEME_PRIMARY2);
new StaticText(window,
{PAGE_TITLE_LEFT, PAGE_TITLE_TOP + PAGE_LINE_HEIGHT,
LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT},
theme->getName(), 0, COLOR_THEME_PRIMARY2);
rect_t r = {LCD_W - (BUTTON_WIDTH + 5), static_cast<coord_t>(PAGE_TITLE_TOP + ((header.getRect().h - BUTTON_HEIGHT) / 2)), BUTTON_WIDTH, BUTTON_HEIGHT };
new TextButton(window, r, "Save", [=] () {
theme->serialize();
tp->refresh();
tp->applyTheme(tp->getThemeIndex());
body.clear();
buildBody(&body);
return 0;
});
}
void buildBody(FormGroup *window)
{
FormGridLayout grid;
grid.spacer(8);
new StaticText(window, grid.getLabelSlot(), "Name", 0, COLOR_THEME_PRIMARY1);
new TextEdit(window, grid.getFieldSlot(), theme->getName(), NAME_LENGTH);
grid.nextLine();
new StaticText(window, grid.getLabelSlot(), "Author", 0, COLOR_THEME_PRIMARY1);
new TextEdit(window, grid.getFieldSlot(), theme->getAuthor(), AUTHOR_LENGTH);
grid.nextLine();
new StaticText(window, grid.getLineSlot(), "Description", 0, COLOR_THEME_PRIMARY1);
grid.nextLine();
new TextEdit(window, grid.getLineSlot(), theme->getInfo(), INFO_LENGTH);
grid.nextLine();
grid.spacer(10);
new StaticText(window, grid.getLineSlot(), "Colors", 0, COLOR_THEME_PRIMARY1 | FONT(BOLD));
grid.nextLine();
grid.spacer(10);
char **colorNames = ThemePersistance::getColorNames();
for (int i = 0; i < COLOR_COUNT; i++) {
char * name = colorNames[i];
new StaticText(window, grid.getLabelSlot(), name, 0, COLOR_THEME_PRIMARY1);
new ColorEdit(window, grid.getFieldSlot(),
[=] () {
return theme->getColorByName(name);
},
[=] (uint16_t value) {
theme->setColor((LcdColorIndex)i, value);
});
grid.nextLine();
}
grid.spacer(20);
window->setInnerHeight(grid.getWindowHeight());
}
protected:
ThemeFile *theme;
};
ScreenUserInterfacePage::ScreenUserInterfacePage(ScreenMenu* menu):
PageTab(STR_USER_INTERFACE, ICON_THEME_SETUP),
menu(menu)
{
}
class MyFormGridLayout : public FormGridLayout
{
public:
MyFormGridLayout(int width, int top) :
FormGridLayout(width, top)
{
}
rect_t getFieldSlotEx(uint8_t count = 1, uint8_t index = 0, uint8_t number = 1) const
{
coord_t width = (this->width - labelWidth - lineMarginRight - (count - 1) * PAGE_LINE_SPACING) / count;
coord_t left = labelWidth + (width + PAGE_LINE_SPACING) * index;
return {left, currentY, width *number, PAGE_LINE_HEIGHT};
}
};
void ScreenUserInterfacePage::build(FormWindow * window)
{
FormGridLayout grid(LCD_W, 10);
MyFormGridLayout grid(LCD_W, 10);
// Top Bar
new StaticText(window, grid.getLabelSlot(), STR_TOP_BAR, 0, COLOR_THEME_PRIMARY1);
@@ -289,7 +389,7 @@ void ScreenUserInterfacePage::build(FormWindow * window)
auto tp = ThemePersistance::instance();
tp->refresh();
std::vector<std::string> names = tp->getNames();
new Choice(window, grid.getFieldSlot(), names, 0, names.size() - 1,
new Choice(window, grid.getFieldSlotEx(), names, 0, names.size() - 1,
[=] () {
return tp->getThemeIndex();
},
@@ -298,12 +398,24 @@ void ScreenUserInterfacePage::build(FormWindow * window)
tp->applyTheme(value);
tp->setDefaultTheme(value);
window->clear();
build(window);
});
std::function<uint8_t ()> 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);
@@ -328,7 +440,7 @@ void ScreenUserInterfacePage::build(FormWindow * window)
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 {0, 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());
@@ -410,8 +522,6 @@ ScreenSetupPage::ScreenSetupPage(ScreenMenu * menu, unsigned pageIndex, unsigned
{
}
static void updateLayoutOptions(FormGroup* optionsWindow, unsigned customScreenIndex)
{
FormGridLayout grid;
+11
View File
@@ -26,6 +26,17 @@
static char splitChars[] = { ' ', '-', '\t' };
void removeAllWhiteSpace(char *str)
{
int len = strlen(str);
int origLen = len;
while(len) {
if (iswspace(str[len]))
memmove(str + len, str + len + 1, origLen - len);
len--;
}
}
char *trim(char *str)
{
char *end;
+1
View File
@@ -25,5 +25,6 @@
#include <sstream>
extern char *trim(char *str);
extern void removeAllWhiteSpace(char *str);
extern std::string trim_start(std::string str);
extern std::string wrap(std::string str, uint32_t width);
+181 -54
View File
@@ -18,27 +18,34 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "theme_manager.h"
#include "theme.h"
#include <cstdlib>
#include <cstring>
#define MAX_FILES 20
ThemePersistance themePersistance;
#define COLOR_COUNT 12
static const char *conversionArray[COLOR_COUNT] = {
static const char *colorNames[COLOR_COUNT] = {
"DEFAULT", "PRIMARY1", "PRIMARY2", "PRIMARY3",
"SECONDARY1", "SECONDARY2", "SECONDARY3", "FOCUS",
"EDIT", "ACTIVE", "WARNING", "DISABLED",
"EDIT", "ACTIVE", "WARNING", "DISABLED", "CUSTOM",
};
#define HEX_COLOR_VALUE_LEN 8
constexpr const char *RGBSTRING = "RGB(";
constexpr const char* SELECTED_THEME_FILE = THEMES_PATH "/selectedtheme.txt";
ThemeFile& ThemeFile::operator= (const ThemeFile& theme)
{
if (this == &theme)
return *this;
path = theme.path;
strncpy(name, theme.name, NAME_LENGTH);
strncpy(author, theme.author, AUTHOR_LENGTH);
strncpy(info, theme.info, INFO_LENGTH);
colorList.assign(theme.colorList.begin(), theme.colorList.end());
return *this;
};
std::string ThemeFile::getThemeImageFileName()
{
char fullPath[FF_MAX_LFN + 1];
@@ -53,7 +60,65 @@ std::string ThemeFile::getThemeImageFileName()
return "";
}
void ThemeFile::scanFile()
std::vector<std::string> ThemeFile::getThemeImageFileNames()
{
std::vector<std::string> 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;
}
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);
if (result == FR_OK) {
f_printf(&file, "---\n");
f_printf(&file, "summary:\n");
f_printf(&file, " name: %s\n", name);
f_printf(&file, " author: %s\n", author);
f_printf(&file, " info: %s\n", info);
f_printf(&file, "\n");
f_printf(&file, "colors:\n");
for (auto colorEntry : colorList) {
auto r = GET_RED(colorEntry.colorValue);
auto g = GET_GREEN(colorEntry.colorValue);
auto b = GET_BLUE(colorEntry.colorValue);
std::string colorName(colorNames[colorEntry.colorNumber]);
colorName += ":";
f_printf(&file, " %-11s 0x%02X%02X%02X\n", colorName.c_str(), r,g,b);
}
f_close(&file);
}
}
void ThemeFile::deSerialize()
{
char line[256 + 1];
char fullPath[FF_MAX_LFN + 1];
@@ -62,11 +127,12 @@ void ThemeFile::scanFile()
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);
if (result != FR_OK) return;
int lineNo = 1;
while (readNextLine(line, 256)) {
while (readNextLine(file, line, 256)) {
int len = strlen(line);
if (len == 0) continue;
@@ -98,7 +164,7 @@ void ThemeFile::scanFile()
char *plvalue;
char *prvalue;
strncpy(lvalue, line, std::min((int)(ptr - line), 63));
strncpy(lvalue, line, min((int)(ptr - line), 63));
lvalue[ptr - line] = '\0';
strncpy(rvalue, ptr + 1, 63);
plvalue = trim(lvalue);
@@ -117,12 +183,16 @@ void ThemeFile::scanFile()
} 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;
if (strcmp(plvalue, "name") == 0) {
strncpy(name, prvalue, NAME_LENGTH);
name[NAME_LENGTH] = '\0';
} else if (strcmp(plvalue, "author") == 0) {
strncpy(author, prvalue, AUTHOR_LENGTH);
author[AUTHOR_LENGTH] = '\0';
} else if (strcmp(plvalue, "info") == 0) {
strncpy(info, prvalue, INFO_LENGTH);
info[INFO_LENGTH] = '\0';
}
} break;
}
}
@@ -133,8 +203,6 @@ void ThemeFile::scanFile()
f_close(&file);
}
#define HEX_COLOR_VALUE_LEN 8
bool ThemeFile::convertRGB(char *pColorRGB, uint32_t &color)
{
if (strlen(pColorRGB) < strlen(RGBSTRING)) return false;
@@ -189,7 +257,7 @@ LcdColorIndex ThemeFile::findColorIndex(const char *name)
{
int i;
for (i = 0; i < COLOR_COUNT; i++) {
if (strcmp(name, conversionArray[i]) == 0) break;
if (strcmp(name, colorNames[i]) == 0) break;
}
if (i >= COLOR_COUNT) return DEFAULT_COLOR_INDEX;
@@ -197,12 +265,11 @@ LcdColorIndex ThemeFile::findColorIndex(const char *name)
return (LcdColorIndex)i;
}
bool ThemeFile::readNextLine(char *line, int maxlen)
bool ThemeFile::readNextLine(FIL &file, 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] == '\n') { // remove unwanted chars if file was edited using windows
if (line[curlen - 1] == '\r') {
line[curlen - 1] = 0;
} else {
@@ -216,10 +283,21 @@ bool ThemeFile::readNextLine(char *line, int maxlen)
return false;
}
void ThemeFile::setColorByIndex(int index, uint32_t color)
{
if (index >= 0 && index < (int) colorList.size()) {
colorList[index].colorValue = color;
}
}
void ThemeFile::setColor(LcdColorIndex colorIndex, uint32_t color)
{
if (colorIndex >= DEFAULT_COLOR_INDEX && colorIndex < LCD_COLOR_COUNT) {
colorList.emplace_back(ColorEntry {colorIndex, color});
auto colorEntry = std::find(colorList.begin(), colorList.end(), ColorEntry { colorIndex, 0});
if (colorEntry != colorList.end())
colorEntry->colorValue = color;
else
colorList.emplace_back(ColorEntry {colorIndex, color});
}
}
@@ -231,13 +309,20 @@ void ThemeFile::applyTheme()
OpenTxTheme::instance()->update(false);
}
// not sure why this is needed...
constexpr unsigned MAX_SD_FILE_LENGTH = 64;
// avoid leaking memory
void ThemePersistance::clearThemes()
{
for (auto theme: themes) {
delete theme;
}
themes.clear();
}
#define MAX_SD_FILE_LENGTH 128
void ThemePersistance::scanForThemes()
{
TRACE("in scanForThemes");
themes.clear();
clearThemes();
DIR dir;
FILINFO fno;
@@ -259,15 +344,16 @@ void ThemePersistance::scanForThemes()
if (res != FR_OK || fno.fname[0] == 0)
break; // Break on error or end of dir
if (strlen((const char *)fno.fname) > MAX_SD_FILE_LENGTH) continue;
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('.');
auto found = fname.find(".");
if (found != std::string::npos) {
if (strcasecmp(fname.substr(found).c_str(), YAML_EXT) != 0) continue;
}
else continue;
themes.emplace_back(new ThemeFile(fno.fname));
}
@@ -278,6 +364,8 @@ void ThemePersistance::scanForThemes()
void ThemePersistance::loadDefaultTheme()
{
refresh();
FIL file;
FRESULT status = f_open(&file, SELECTED_THEME_FILE, FA_READ);
if (status != FR_OK) return;
@@ -287,7 +375,6 @@ void ThemePersistance::loadDefaultTheme()
status = f_read(&file, line, 256, &len);
if (status == FR_OK) {
refresh();
line[len] = '\0';
@@ -304,6 +391,38 @@ void ThemePersistance::loadDefaultTheme()
f_close(&file);
}
char ** ThemePersistance::getColorNames()
{
return (char **) colorNames;
}
bool ThemePersistance::deleteThemeByIndex(int index)
{
// greater than 0 is intentional here. cant delete default theme.
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);
strcat(newFile, ".deleted");
// for now we are just renaming the file so we don't find it
FRESULT status = f_rename(curFile, newFile);
refresh();
// make sure currentTheme stays in bounds
if (getThemeIndex() >= (int) themes.size())
setThemeIndex(themes.size() - 1);
return status == FR_OK;
}
return false;
}
void ThemePersistance::deleteDefaultTheme()
{
FIL file;
@@ -314,12 +433,15 @@ void ThemePersistance::deleteDefaultTheme()
void ThemePersistance::setDefaultTheme(int index)
{
FIL file;
auto theme = themes[index];
FRESULT status = f_open(&file, SELECTED_THEME_FILE, FA_CREATE_ALWAYS | FA_WRITE);
if (status != FR_OK) return;
if (index >= 0 && index < (int) themes.size()) {
auto theme = themes[index];
FRESULT status = f_open(&file, SELECTED_THEME_FILE, FA_CREATE_ALWAYS | FA_WRITE);
if (status != FR_OK) return;
f_printf(&file, theme->getPath().c_str());
f_close(&file);
currentTheme = index;
f_printf(&file, theme->getPath().c_str());
f_close(&file);
}
}
class DefaultEdgeTxTheme : public ThemeFile
@@ -330,32 +452,37 @@ class DefaultEdgeTxTheme : public ThemeFile
setName("EdgeTX Default");
setAuthor("EdgeTX Team");
setInfo("Default EdgeTX Color Scheme");
// initializze the default color table
colorList.emplace_back(ColorEntry { COLOR_THEME_PRIMARY1_INDEX, RGB(0, 0, 0) });
colorList.emplace_back(ColorEntry { COLOR_THEME_PRIMARY2_INDEX, RGB(255, 255, 255) });
colorList.emplace_back(ColorEntry { COLOR_THEME_PRIMARY3_INDEX, RGB(12, 63, 102) });
colorList.emplace_back(ColorEntry { COLOR_THEME_SECONDARY1_INDEX, RGB(18, 94, 153) });
colorList.emplace_back(ColorEntry { COLOR_THEME_SECONDARY2_INDEX, RGB(182, 224, 242) });
colorList.emplace_back(ColorEntry { COLOR_THEME_SECONDARY3_INDEX, RGB(228, 238, 242) });
colorList.emplace_back(ColorEntry { COLOR_THEME_FOCUS_INDEX, RGB(20, 161, 229) });
colorList.emplace_back(ColorEntry { COLOR_THEME_EDIT_INDEX, RGB(0, 153, 9) });
colorList.emplace_back(ColorEntry { COLOR_THEME_ACTIVE_INDEX, RGB(255, 222, 0) });
colorList.emplace_back(ColorEntry { COLOR_THEME_WARNING_INDEX, RGB(224, 0, 0) });
colorList.emplace_back(ColorEntry { COLOR_THEME_DISABLED_INDEX, RGB(140, 140, 140) });
}
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);
ThemeFile::applyTheme();
}
std::string getThemeImageFileName() override
{
return "/THEMES/EdgeTX.png";
}
std::vector<std::string> getThemeImageFileNames() override
{
std::vector<std::string> fileNames;
fileNames.emplace_back("/THEMES/EdgeTX.png");
return fileNames;
}
};
void ThemePersistance::insertDefaultTheme()
+95 -28
View File
@@ -18,66 +18,116 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include <stdlib.h>
#include <list>
#include <string>
#include <vector>
#include <locale>
#include <algorithm>
#include "debug.h"
#include "sdcard.h"
#include "colors.h"
#include "debug.h"
#include "opentx.h"
#include "sdcard.h"
#include "str_functions.h"
// sdReadir: used to be in "sdcard.h" (another GPL violation...)
#include "libopenui_file.h"
class ThemePersistance;
extern ThemePersistance themePersistance;
#define COLOR_COUNT 13
struct ColorEntry
{
LcdColorIndex colorNumber;
uint32_t colorValue;
bool operator== (const ColorEntry &a) { return this->colorNumber == a.colorNumber; }
};
#define AUTHOR_LENGTH 50
#define INFO_LENGTH 255
#define NAME_LENGTH 26
class ThemeFile
{
public:
ThemeFile() {
name[0] = '\0';
author[0] = '\0';
info[0] = '\0';
}
ThemeFile(std::string themePath) :
path(themePath)
{
if (themePath.size()) {
scanFile();
deSerialize();
}
}
ThemeFile(const ThemeFile &theme)
{
path = theme.path;
strncpy(name, theme.name, NAME_LENGTH);
name[NAME_LENGTH] = '\0';
strncpy(author, theme.author, AUTHOR_LENGTH);
author[AUTHOR_LENGTH] = '\0';
strncpy(info, theme.info, INFO_LENGTH);
info[INFO_LENGTH] = '\0';
colorList.assign(theme.colorList.begin(), theme.colorList.end());
}
virtual ~ThemeFile()
{
}
ThemeFile& operator= (const ThemeFile& theme);
void serialize();
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; }
char *getName() { return name; }
char *getAuthor() { return author; }
char *getInfo() { return info; }
std::vector<ColorEntry> getColorList() { return colorList; }
ColorEntry *getColorEntryByIndex(LcdColorIndex colorNumber) {
int n = 0;
for (auto colorEntry : colorList) {
if (colorEntry.colorNumber == colorNumber)
return &colorList[n];
n++;
}
return nullptr;
}
uint32_t getColorByName(std::string colorName) {
auto colorIndex = findColorIndex(colorName.c_str());
ColorEntry a = {colorIndex, 0};
auto colorEntry = std::find(colorList.begin(), colorList.end(), a);
if (colorEntry != colorList.end())
return colorEntry->colorValue;
return 0;
}
void setName(std::string name) { strncpy(this->name, name.c_str(), NAME_LENGTH); }
void setAuthor(std::string author) { strncpy(this->author, author.c_str(), AUTHOR_LENGTH); }
void setInfo(std::string info) { strncpy(this->info, info.c_str(), INFO_LENGTH); }
void setPath(std::string path) { this->path = path; }
std::vector<ColorEntry>& getColorList() { return colorList; }
void setColor(LcdColorIndex colorIndex, uint32_t color);
void setColorByIndex(int index, uint32_t color);
virtual std::string getThemeImageFileName();
virtual std::vector<std::string> getThemeImageFileNames();
virtual void applyTheme();
protected:
FIL file;
std::string path;
std::string name;
std::string author;
std::string info;
char name[NAME_LENGTH + 1];
char author[AUTHOR_LENGTH + 1];
char info[INFO_LENGTH + 1];
std::vector<ColorEntry> colorList;
enum ScanState
{
none,
@@ -85,10 +135,10 @@ class ThemeFile
colors
};
void scanFile();
void deSerialize();
bool convertRGB(char *pColorRGB, uint32_t &color);
LcdColorIndex findColorIndex(const char *name);
bool readNextLine(char * line, int maxlen);
bool readNextLine(FIL &file, char *line, int maxlen);
};
@@ -106,6 +156,8 @@ class ThemePersistance
void loadDefaultTheme();
void setDefaultTheme(int index);
void deleteDefaultTheme();
static char **getColorNames();
bool deleteThemeByIndex(int index);
std::vector<std::string> getNames()
{
@@ -119,14 +171,28 @@ class ThemePersistance
void applyTheme(int index)
{
auto theme = themes[index];
if (index >= 0 && index < (int) themes.size()) {
auto theme = themes[index];
theme->applyTheme();
}
}
inline int getThemeIndex() {return currentTheme;}
inline void setThemeIndex(int index) { currentTheme = index;}
inline ThemeFile* getCurrentTheme() { return themes[currentTheme]; }
inline ThemeFile* getCurrentTheme()
{
if (currentTheme < (int)themes.size())
return themes[currentTheme];
return nullptr;
}
inline ThemeFile* getThemeByIndex(int index)
{
if (index < (int) themes.size())
return themes[index];
return nullptr;
}
void refresh()
{
@@ -139,4 +205,5 @@ class ThemePersistance
int currentTheme = 0;
void scanForThemes();
void insertDefaultTheme();
void clearThemes();
};
@@ -160,6 +160,10 @@ const uint8_t mask_radio_setup[] = {
const uint8_t mask_radio_tools[] = {
#include "mask_radio_tools.lbm"
};
const uint8_t mask_radio_edit_theme[] = {
#include "mask_radio_edit_theme.lbm"
};
const uint8_t mask_radio_trainer[] = {
#include "mask_radio_trainer.lbm"
};
@@ -109,6 +109,7 @@ class Theme480: public OpenTxTheme
loadMenuIcon(ICON_RADIO_TRAINER, mask_radio_trainer, reload);
loadMenuIcon(ICON_RADIO_HARDWARE, mask_radio_hardware, reload);
loadMenuIcon(ICON_RADIO_CALIBRATION, mask_radio_calibration, reload);
loadMenuIcon(ICON_RADIO_EDIT_THEME, mask_radio_edit_theme, reload);
loadMenuIcon(ICON_RADIO_VERSION, mask_radio_version, reload);
loadMenuIcon(ICON_MODEL, mask_menu_model, reload);
loadMenuIcon(ICON_MODEL_SETUP, mask_model_setup, reload);
@@ -68,6 +68,8 @@ constexpr uint32_t MENU_HEADER_BACK_BUTTON_HEIGHT = MENU_HEADER_HEIGHT;
constexpr coord_t PAGE_PADDING = 6;
constexpr uint32_t PAGE_LINE_HEIGHT = 20;
constexpr coord_t LINE_HEIGHT = PAGE_LINE_HEIGHT;
constexpr uint32_t PAGE_LINE_SPACING = 2;
constexpr uint32_t PAGE_INDENT_WIDTH = 10;
constexpr uint32_t PAGE_LABEL_WIDTH = 240;
@@ -88,6 +88,7 @@ constexpr uint32_t DATETIME_SEPARATOR_X = LCD_W - 53;
constexpr uint32_t DATETIME_LINE1 = 7;
constexpr uint32_t DATETIME_LINE2 = 22;
constexpr uint32_t DATETIME_MIDDLE = (LCD_W + DATETIME_SEPARATOR_X + 1) / 2;
constexpr coord_t LINE_HEIGHT = PAGE_LINE_HEIGHT;
constexpr uint32_t RSSI_X = LCD_W - 80;
constexpr uint32_t AUDIO_X = LCD_W - 115;
+15
View File
@@ -944,3 +944,18 @@ const char STR_RFPOWER_AFHDS2[] = TR_RFPOWER_AFHDS2;
#endif
const char STR_USE_THEME_COLOR[] = TR_USE_THEME_COLOR;
const char STR_DUPLICATE[] = TR_DUPLICATE;
const char STR_ACTIVATE[] = TR_ACTIVATE;
const char STR_RED[] = TR_RED;
const char STR_GREEN[] = TR_GREEN;
const char STR_BLUE[] = TR_BLUE;
const char STR_COLOR_PICKER[] = TR_COLOR_PICKER;
const char STR_EDIT_THEME_DETAILS[] = TR_EDIT_THEME_DETAILS;
const char STR_AUTHOR[] = TR_AUTHOR;
const char STR_DESCRIPTION[] = TR_DESCRIPTION;
const char STR_SAVE[] = TR_SAVE;
const char STR_CANCEL[] = TR_CANCEL;
const char STR_EDIT_THEME[] = TR_EDIT_THEME;
const char STR_DETAILS[] = TR_DETAILS;
const char STR_THEME_EDITOR[] = TR_THEME_EDITOR;
+15
View File
@@ -1140,6 +1140,21 @@ extern const char STR_RACING_MODE[];
extern const char STR_USE_THEME_COLOR[];
extern const char STR_DUPLICATE[];
extern const char STR_ACTIVATE[];
extern const char STR_RED[];
extern const char STR_BLUE[];
extern const char STR_GREEN[];
extern const char STR_COLOR_PICKER[];
extern const char STR_EDIT_THEME_DETAILS[];
extern const char STR_AUTHOR[];
extern const char STR_DESCRIPTION[];
extern const char STR_SAVE[];
extern const char STR_CANCEL[];
extern const char STR_EDIT_THEME[];
extern const char STR_DETAILS[];
extern const char STR_THEME_EDITOR[];
#define CHR_HOUR TR_CHR_HOUR
#define CHR_INPUT TR_CHR_INPUT
+15
View File
@@ -329,3 +329,18 @@
#define CHAR_LUA STR_CHAR_LUA[0]
#define TR_USE_THEME_COLOR "Use theme color"
#define TR_DUPLICATE "Duplicate"
#define TR_ACTIVATE "Set Active"
#define TR_RED "Red"
#define TR_BLUE "Blue"
#define TR_GREEN "Green"
#define TR_COLOR_PICKER "Color Picker"
#define TR_EDIT_THEME_DETAILS "Edit Theme Details"
#define TR_AUTHOR "Author"
#define TR_DESCRIPTION "Description"
#define TR_SAVE "Save"
#define TR_CANCEL "Cancel"
#define TR_EDIT_THEME "EDIT THEME"
#define TR_DETAILS "Details"
#define TR_THEME_EDITOR "THEMES"