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:
co-authored by
kkoenig1@optonline.com <kkoenig1@optonline.com>
Peter Feerick
parent
30780d82ec
commit
7298f0ff00
@@ -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 |
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user