Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
ab7319edde | |||
6074189811 | |||
3438b20b58 | |||
20e359a1b9 | |||
5f889d4d73 | |||
beab503d28 | |||
068ef657e8 | |||
13dd0481fb | |||
1f80ca10f9 | |||
950d9dadbb | |||
89f1253298 | |||
7e17dcf7d8 | |||
76041027df | |||
51bae4e7f3 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,2 @@
|
||||
/build-*
|
||||
build-*
|
||||
build/
|
||||
|
@ -81,6 +81,7 @@ set(sources
|
||||
src/screenmanager.cpp
|
||||
src/splitgraphdisplay.cpp
|
||||
src/popupdisplay.cpp
|
||||
src/richtexthelper.cpp
|
||||
src/richtextrenderer.cpp
|
||||
src/icons/back.cpp
|
||||
src/icons/checked.cpp
|
||||
@ -106,7 +107,6 @@ set(dependencies
|
||||
espchrono
|
||||
espcpputils
|
||||
espwifistack
|
||||
fmt
|
||||
TFT_eSPI
|
||||
esptftlib
|
||||
espfontlib
|
||||
@ -122,6 +122,8 @@ idf_component_register(
|
||||
${dependencies}
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 23)
|
||||
|
||||
target_compile_options(${COMPONENT_TARGET}
|
||||
PRIVATE
|
||||
-fstack-reuse=all
|
||||
|
@ -4,4 +4,8 @@ config ESPGUI_MENUDISPLAY_ROWS
|
||||
int "Number of rows for MenuDisplays"
|
||||
default 10
|
||||
|
||||
config ESPGUI_MENUITEM_BACKGROUND_COLOR
|
||||
hex "MenuItem background color"
|
||||
default 0x5AEB
|
||||
|
||||
endmenu
|
||||
|
25
iconconvert/build.sh
Executable file
25
iconconvert/build.sh
Executable file
@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $PWD != *"esp-gui-lib/iconconvert"* ]] || [[ $0 != *"build.sh"* ]]; then
|
||||
echo "This script must be run from the iconconvert directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# check if qmake is installed
|
||||
if ! command -v qmake; then
|
||||
echo "Qmake is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if make is installed
|
||||
if ! command -v make; then
|
||||
echo "Make is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p build
|
||||
cd build || exit 1
|
||||
qmake ..
|
||||
make
|
||||
cd ..
|
@ -1,7 +1,9 @@
|
||||
#include "changevaluedisplay.h"
|
||||
|
||||
// system includes
|
||||
#include <format>
|
||||
|
||||
// 3rdparty lib includes
|
||||
#include <fmt/core.h>
|
||||
#include <fontrenderer.h>
|
||||
|
||||
// local includes
|
||||
@ -38,6 +40,6 @@ void ChangeValueDisplay<float>::redraw(TftInterface &tft)
|
||||
{
|
||||
Base::redraw(tft);
|
||||
|
||||
m_valueLabel.redraw(tft, fmt::format("{:.02f}", m_value), TFT_WHITE, TFT_BLACK, 7);
|
||||
m_valueLabel.redraw(tft, std::format("{:.02f}", m_value), TFT_WHITE, TFT_BLACK, 7);
|
||||
}
|
||||
} // namespace espgui
|
||||
|
@ -14,7 +14,7 @@ void DisplayWithTitle::initScreen(TftInterface &tft)
|
||||
|
||||
// tft.fillRect(0, 0, tft.width(), 35, TFT_GREY);
|
||||
|
||||
tft.fillRect(0, 34, tft.width(), 2, TFT_GREY);
|
||||
tft.fillRect(0, TITLE_HEIGHT, tft.width(), TITLE_BORDER, TFT_GREY);
|
||||
}
|
||||
|
||||
void DisplayWithTitle::redraw(TftInterface &tft)
|
||||
|
@ -14,6 +14,9 @@ class DisplayWithTitle :
|
||||
using Base = Display;
|
||||
|
||||
public:
|
||||
constexpr static int TITLE_HEIGHT = 34;
|
||||
constexpr static int TITLE_BORDER = 2;
|
||||
|
||||
TitleInterface *asTitleInterface() override { return this; }
|
||||
const TitleInterface *asTitleInterface() const override { return this; }
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include <esp_log.h>
|
||||
|
||||
// 3rdparty lib includes
|
||||
#include <fmt/core.h>
|
||||
#include <strutils.h>
|
||||
#include <fontrenderer.h>
|
||||
|
||||
|
@ -59,20 +59,25 @@ void MenuDisplay::update()
|
||||
const auto offset = m_rotateOffset;
|
||||
m_rotateOffset = 0;
|
||||
|
||||
const auto itemCount = menuItemCount();
|
||||
|
||||
if (itemCount)
|
||||
if (const auto itemCount = menuItemCount())
|
||||
{
|
||||
if (m_selectedIndex == -1)
|
||||
m_selectedIndex = 0;
|
||||
|
||||
m_selectedIndex = m_selectedIndex + offset;
|
||||
m_selectedIndex += offset;
|
||||
|
||||
if (m_selectedIndex < 0)
|
||||
m_selectedIndex += itemCount;
|
||||
if (m_selectedIndex >= itemCount)
|
||||
m_selectedIndex -= itemCount;
|
||||
|
||||
if (getMenuItem(m_selectedIndex).skipScroll())
|
||||
{
|
||||
m_selectedIndex = offset >= 0 ?
|
||||
getNextAccessibleMenuItemIndex(m_selectedIndex) :
|
||||
getPreviousAccessibleMenuItemIndex(m_selectedIndex);
|
||||
}
|
||||
|
||||
if (m_selectedIndex < m_scrollOffset)
|
||||
m_scrollOffset = m_selectedIndex;
|
||||
if (m_selectedIndex >= m_scrollOffset + m_labels.size())
|
||||
@ -87,18 +92,6 @@ void MenuDisplay::update()
|
||||
runForEveryMenuItem([&](MenuItem &item){
|
||||
item.update();
|
||||
});
|
||||
|
||||
if (m_selectedIndex >= 0 && m_selectedIndex < m_menuItems.size() && getMenuItem(m_selectedIndex).skipScroll())
|
||||
{
|
||||
if (offset > 0)
|
||||
{
|
||||
m_rotateOffset++;
|
||||
}
|
||||
else if (offset < 0)
|
||||
{
|
||||
m_rotateOffset--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pressed)
|
||||
@ -156,7 +149,7 @@ void MenuDisplay::redraw(TftInterface &tft)
|
||||
|
||||
if (relativeIndex != m_highlightedIndex)
|
||||
{
|
||||
drawItemRect(*labelsIter, TFT_GREY);
|
||||
drawItemRect(*labelsIter, CONFIG_ESPGUI_MENUITEM_BACKGROUND_COLOR);
|
||||
*iconsIter = nullptr;
|
||||
labelsIter->start(tft);
|
||||
|
||||
@ -171,22 +164,22 @@ void MenuDisplay::redraw(TftInterface &tft)
|
||||
drawItemRect(*labelsIter, TFT_BLACK);
|
||||
*iconsIter = nullptr;
|
||||
labelsIter->start(tft);
|
||||
}
|
||||
}
|
||||
|
||||
labelsIter->redraw(tft, item.text(), item.color(), selected ? TFT_GREY : TFT_BLACK, item.font());
|
||||
labelsIter->redraw(tft, item.text(), item.color(), selected ? CONFIG_ESPGUI_MENUITEM_BACKGROUND_COLOR : TFT_BLACK, item.font());
|
||||
|
||||
if (const auto icon = item.icon(selected); icon != *iconsIter)
|
||||
{
|
||||
if (icon)
|
||||
tft.pushImage(6, labelsIter->y() + 1, *icon);
|
||||
else if (*iconsIter)
|
||||
tft.fillRect(6, labelsIter->y() + 1, 24, 24, selected ? TFT_GREY : TFT_BLACK);
|
||||
tft.fillRect(6, labelsIter->y() + 1, 24, 24, selected ? CONFIG_ESPGUI_MENUITEM_BACKGROUND_COLOR : TFT_BLACK);
|
||||
*iconsIter = icon;
|
||||
}
|
||||
|
||||
// if (selected && (relativeIndex != m_highlightedIndex))
|
||||
// {
|
||||
// drawItemRect(*labelsIter, TFT_GREY);
|
||||
// drawItemRect(*labelsIter, CONFIG_ESPGUI_MENUITEM_BACKGROUND_COLOR);
|
||||
// }
|
||||
|
||||
labelsIter++;
|
||||
|
@ -56,6 +56,32 @@ public:
|
||||
return *m_menuItems[index].get();
|
||||
}
|
||||
|
||||
int getNextAccessibleMenuItemIndex(int index) const
|
||||
{
|
||||
for (std::size_t i = index + 1; i < m_menuItems.size(); ++i)
|
||||
if (!m_menuItems[i]->skipScroll())
|
||||
return i;
|
||||
|
||||
for (std::size_t i = 0; i < index; ++i)
|
||||
if (!m_menuItems[i]->skipScroll())
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int getPreviousAccessibleMenuItemIndex(int index) const
|
||||
{
|
||||
for (std::size_t i = index - 1; i < m_menuItems.size(); --i)
|
||||
if (!m_menuItems[i]->skipScroll())
|
||||
return i;
|
||||
|
||||
for (std::size_t i = m_menuItems.size() - 1; i > index; --i)
|
||||
if (!m_menuItems[i]->skipScroll())
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void runForEveryMenuItem(std::function<void(MenuItem&)> &&callback)
|
||||
{
|
||||
for (const auto &ptr : m_menuItems)
|
||||
|
18
src/richtexthelper.cpp
Normal file
18
src/richtexthelper.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#include "richtexthelper.h"
|
||||
|
||||
// 3rdparty lib includes
|
||||
#include <strutils.h>
|
||||
|
||||
namespace espgui {
|
||||
|
||||
void richTextEscape(std::string &subject)
|
||||
{
|
||||
cpputils::stringReplaceAll('&', "&&", subject);
|
||||
}
|
||||
|
||||
std::string richTextEscape(std::string_view subject)
|
||||
{
|
||||
return cpputils::stringReplaceAll('&', "&&", subject);
|
||||
}
|
||||
|
||||
} // namespace espgui
|
@ -1,5 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
// system includes
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace espgui {
|
||||
namespace colors {
|
||||
constexpr char RED[] = "&1"; // #ff0000 (TFT_RED)
|
||||
@ -17,4 +21,8 @@ constexpr char SMALL[] = "&s"; // tft.setTextFont(2)
|
||||
constexpr char MEDIUM[] = "&m"; // tft.setTextFont(4)
|
||||
constexpr char RESTORE[] = "&f"; // restore original font
|
||||
} // namespace fonts
|
||||
|
||||
void richTextEscape(std::string &subject);
|
||||
std::string richTextEscape(std::string_view subject);
|
||||
|
||||
} // namespace espgui
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include <string_view>
|
||||
|
||||
// 3rdparty lib includes
|
||||
#include <strutils.h>
|
||||
#include <fontrenderer.h>
|
||||
|
||||
// local includes
|
||||
@ -13,16 +12,6 @@
|
||||
|
||||
namespace espgui {
|
||||
|
||||
void richTextEscape(std::string &subject)
|
||||
{
|
||||
cpputils::stringReplaceAll('&', "&&", subject);
|
||||
}
|
||||
|
||||
std::string richTextEscape(std::string_view subject)
|
||||
{
|
||||
return cpputils::stringReplaceAll('&', "&&", subject);
|
||||
}
|
||||
|
||||
int16_t renderRichText(TftInterface &tft, std::string_view str, int32_t poX, int32_t poY, uint16_t color, uint16_t bgcolor, uint8_t font)
|
||||
{
|
||||
FontRenderer fontRenderer{tft};
|
||||
@ -110,18 +99,30 @@ again:
|
||||
}
|
||||
case 'f':
|
||||
case 's':
|
||||
case 'S':
|
||||
case 'm':
|
||||
{
|
||||
font = [&controlChar,&oldFont]() -> uint8_t {
|
||||
bool changeHeight = false;
|
||||
font = [&controlChar,&oldFont,&changeHeight]() -> uint8_t {
|
||||
switch (controlChar)
|
||||
{
|
||||
case 'f': return oldFont;
|
||||
case 's': return 2;
|
||||
case 'S': {
|
||||
changeHeight = true;
|
||||
return 2;
|
||||
}
|
||||
case 'm': return 4;
|
||||
}
|
||||
__builtin_unreachable();
|
||||
}();
|
||||
|
||||
if (changeHeight)
|
||||
{
|
||||
// adjust y rendering position to new font height
|
||||
poY += fontRenderer.fontHeight(oldFont) - fontRenderer.fontHeight(font);
|
||||
}
|
||||
|
||||
auto newNewIter = newIter + 1;
|
||||
if (newNewIter != std::end(str))
|
||||
{
|
||||
|
@ -9,9 +9,6 @@ namespace espgui { class TftInterface; class FontRenderer; }
|
||||
|
||||
namespace espgui {
|
||||
|
||||
void richTextEscape(std::string &subject);
|
||||
std::string richTextEscape(std::string_view subject);
|
||||
|
||||
int16_t renderRichText(TftInterface &tft, std::string_view str, int32_t poX, int32_t poY, uint16_t color, uint16_t bgcolor, uint8_t font);
|
||||
int16_t renderRichText(TftInterface &tft, FontRenderer &fontRenderer, std::string_view str, int32_t poX, int32_t poY, uint16_t color, uint16_t bgcolor, uint8_t font);
|
||||
|
||||
|
@ -7,8 +7,11 @@ public:
|
||||
};
|
||||
|
||||
template<bool TScroll>
|
||||
class StaticScrollBehaviour : public ScrollInterface {
|
||||
class StaticScrollBehaviour : public virtual ScrollInterface {
|
||||
public:
|
||||
bool skipScroll() const override { return TScroll; }
|
||||
};
|
||||
|
||||
using SkipScroll = StaticScrollBehaviour<true>;
|
||||
|
||||
} // namespace espgui
|
||||
|
@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
// system includes
|
||||
#include <format>
|
||||
|
||||
// 3rdparty lib includes
|
||||
#include <fmt/core.h>
|
||||
#include <strutils.h>
|
||||
#include <espstrutils.h>
|
||||
#include <espwifiutils.h>
|
||||
@ -26,7 +28,7 @@ struct TextWithValueHelper : public Taccessor, public virtual TextInterface
|
||||
using espchrono::toString;
|
||||
using wifi_stack::toString;
|
||||
|
||||
return fmt::format("{} {}", Tprefix, richTextEscape(toString(Taccessor::getValue())));
|
||||
return std::format("{} {}", Tprefix, richTextEscape(toString(Taccessor::getValue())));
|
||||
}
|
||||
};
|
||||
|
||||
@ -42,7 +44,7 @@ struct ChangeableTextWithValueHelper : public Taccessor, public virtual TextInte
|
||||
using espchrono::toString;
|
||||
using wifi_stack::toString;
|
||||
|
||||
return fmt::format("{} {}", m_prefix, richTextEscape(toString(Taccessor::getValue())));
|
||||
return std::format("{} {}", m_prefix, richTextEscape(toString(Taccessor::getValue())));
|
||||
}
|
||||
|
||||
const std::string &prefix() const { return m_prefix; }
|
||||
@ -65,7 +67,7 @@ struct TextWithHighlightedValueHelper : public Taccessor, public virtual TextInt
|
||||
using espchrono::toString;
|
||||
using wifi_stack::toString;
|
||||
|
||||
return fmt::format("{} {}{}", Tprefix, Tguilib_color, richTextEscape(toString(Taccessor::getValue())));
|
||||
return std::format("{} {}{}", Tprefix, Tguilib_color, richTextEscape(toString(Taccessor::getValue())));
|
||||
}
|
||||
};
|
||||
|
||||
@ -81,7 +83,7 @@ struct ChangeableTextWithHighlightedValueHelper : public Taccessor, public virtu
|
||||
using espchrono::toString;
|
||||
using wifi_stack::toString;
|
||||
|
||||
return fmt::format("{} {}{}", m_prefix, Tguilib_color, richTextEscape(toString(Taccessor::getValue())));
|
||||
return std::format("{} {}{}", m_prefix, Tguilib_color, richTextEscape(toString(Taccessor::getValue())));
|
||||
}
|
||||
|
||||
const std::string &prefix() const { return m_prefix; }
|
||||
|
@ -8,8 +8,8 @@
|
||||
#include "tftcolors.h"
|
||||
|
||||
namespace espgui {
|
||||
ProgressBar::ProgressBar(int x, int y, int width, int height, int min, int max, uint32_t color) :
|
||||
m_x{x}, m_y{y}, m_width{width}, m_height{height}, m_min{min}, m_max{max}, m_color{color}
|
||||
ProgressBar::ProgressBar(int x, int y, int width, int height, int min, int max, uint32_t color, uint32_t backgroundColor) :
|
||||
m_x{x}, m_y{y}, m_width{width}, m_height{height}, m_min{min}, m_max{max}, m_color{color}, m_backgroundColor{backgroundColor}
|
||||
{
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ void ProgressBar::start(TftInterface &tft)
|
||||
{
|
||||
m_lastValue = m_x+1;
|
||||
tft.drawRect(m_x, m_y, m_width, m_height, TFT_WHITE);
|
||||
tft.fillRect(m_x+1, m_y+1, m_width-2, m_height-2, m_backgroundColor);
|
||||
}
|
||||
|
||||
void ProgressBar::redraw(TftInterface &tft, int value)
|
||||
@ -24,10 +25,28 @@ void ProgressBar::redraw(TftInterface &tft, int value)
|
||||
value = cpputils::mapValueClamped(value, m_min, m_max, m_x+1, m_x+m_width-1);
|
||||
|
||||
if (value < m_lastValue)
|
||||
tft.fillRect(value, m_y+1, m_lastValue-value, m_height-2, TFT_BLACK);
|
||||
tft.fillRect(value, m_y+1, m_lastValue-value, m_height-2, m_backgroundColor);
|
||||
else if (value > m_lastValue)
|
||||
tft.fillRect(m_lastValue, m_y+1, value-m_lastValue, m_height-2, m_color);
|
||||
|
||||
m_lastValue = value;
|
||||
}
|
||||
|
||||
void ProgressBar::changeColor(TftInterface &tft, const uint32_t color, const uint32_t backgroundColor)
|
||||
{
|
||||
if (color != m_color)
|
||||
{
|
||||
// redraw already drawn area in new color
|
||||
tft.fillRect(m_x+1, m_y+1, m_lastValue-m_x-1, m_height-2, color);
|
||||
m_color = color;
|
||||
}
|
||||
|
||||
if (backgroundColor != m_backgroundColor)
|
||||
{
|
||||
// redraw background in new color
|
||||
tft.fillRect(m_x+1, m_y+1, m_width-2, m_height-2, backgroundColor);
|
||||
m_backgroundColor = backgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace espgui
|
||||
|
@ -16,11 +16,13 @@ namespace espgui {
|
||||
class ProgressBar
|
||||
{
|
||||
public:
|
||||
ProgressBar(int x, int y, int width, int height, int min, int max, uint32_t color = TFT_YELLOW);
|
||||
ProgressBar(int x, int y, int width, int height, int min, int max, uint32_t color = TFT_YELLOW, uint32_t backgroundColor = TFT_BLACK);
|
||||
|
||||
void start(TftInterface &tft);
|
||||
void redraw(TftInterface &tft, int value);
|
||||
|
||||
void changeColor(TftInterface &tft, const uint32_t color = TFT_YELLOW, const uint32_t backgroundColor = TFT_BLACK);
|
||||
|
||||
private:
|
||||
const int m_x;
|
||||
const int m_y;
|
||||
@ -28,7 +30,8 @@ private:
|
||||
const int m_height;
|
||||
const int m_min;
|
||||
const int m_max;
|
||||
const uint32_t m_color;
|
||||
uint32_t m_color;
|
||||
uint32_t m_backgroundColor;
|
||||
|
||||
int m_lastValue{};
|
||||
};
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
// system includes
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
// 3rdparty lib includes
|
||||
#include <cpputils.h>
|
||||
#include <stdlib_noniso.h>
|
||||
#include <fontrenderer.h>
|
||||
|
||||
// local includes
|
||||
@ -104,6 +104,81 @@ void VuMeter::start(TftInterface &tft)
|
||||
tft.drawRect(5, 3, 230, 119, TFT_BLACK); // Draw bezel line
|
||||
}
|
||||
|
||||
namespace {
|
||||
char * dtostrf(double number, signed char width, unsigned char prec, char *s) {
|
||||
bool negative = false;
|
||||
|
||||
if (std::isnan(number)) {
|
||||
std::strcpy(s, "nan");
|
||||
return s;
|
||||
}
|
||||
if (std::isinf(number)) {
|
||||
std::strcpy(s, "inf");
|
||||
return s;
|
||||
}
|
||||
|
||||
char* out = s;
|
||||
|
||||
int fillme = width; // how many cells to fill for the integer part
|
||||
if (prec > 0) {
|
||||
fillme -= (prec+1);
|
||||
}
|
||||
|
||||
// Handle negative numbers
|
||||
if (number < 0.0) {
|
||||
negative = true;
|
||||
fillme--;
|
||||
number = -number;
|
||||
}
|
||||
|
||||
// Round correctly so that print(1.999, 2) prints as "2.00"
|
||||
// I optimized out most of the divisions
|
||||
double rounding = 2.0;
|
||||
for (uint8_t i = 0; i < prec; ++i)
|
||||
rounding *= 10.0;
|
||||
rounding = 1.0 / rounding;
|
||||
|
||||
number += rounding;
|
||||
|
||||
// Figure out how big our number really is
|
||||
double tenpow = 1.0;
|
||||
int digitcount = 1;
|
||||
while (number >= 10.0 * tenpow) {
|
||||
tenpow *= 10.0;
|
||||
digitcount++;
|
||||
}
|
||||
|
||||
number /= tenpow;
|
||||
fillme -= digitcount;
|
||||
|
||||
// Pad unused cells with spaces
|
||||
while (fillme-- > 0) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
|
||||
// Handle negative sign
|
||||
if (negative) *out++ = '-';
|
||||
|
||||
// Print the digits, and if necessary, the decimal point
|
||||
digitcount += prec;
|
||||
int8_t digit = 0;
|
||||
while (digitcount-- > 0) {
|
||||
digit = (int8_t)number;
|
||||
if (digit > 9) digit = 9; // insurance
|
||||
*out++ = (char)('0' | digit);
|
||||
if ((digitcount == prec) && (prec > 0)) {
|
||||
*out++ = '.';
|
||||
}
|
||||
number -= digit;
|
||||
number *= 10.0;
|
||||
}
|
||||
|
||||
// make sure the string is terminated
|
||||
*out = 0;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
void VuMeter::redraw(TftInterface &tft, float value)
|
||||
{
|
||||
FontRenderer fontRenderer{tft};
|
||||
|
Reference in New Issue
Block a user