14 Commits
rotary ... main

19 changed files with 235 additions and 54 deletions

3
.gitignore vendored
View File

@ -1 +1,2 @@
/build-*
build-*
build/

View File

@ -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

View File

@ -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
View 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 ..

View File

@ -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

View File

@ -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)

View File

@ -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; }

View File

@ -4,7 +4,6 @@
#include <esp_log.h>
// 3rdparty lib includes
#include <fmt/core.h>
#include <strutils.h>
#include <fontrenderer.h>

View File

@ -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++;

View File

@ -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
View 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

View File

@ -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

View File

@ -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))
{

View File

@ -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);

View File

@ -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

View File

@ -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; }

View File

@ -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

View File

@ -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{};
};

View File

@ -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};