From ad0d90339e63ad97ff64875d6c17aba887d7d6e2 Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Fri, 20 Aug 2021 13:16:55 +0200 Subject: [PATCH] Moved all the bobbycar gui code into this library --- CMakeLists.txt | 44 ++++- icons/back.png | Bin 0 -> 9724 bytes icons/checked.png | Bin 0 -> 10088 bytes icons/unchecked.png | Bin 0 -> 7277 bytes src/accessorinterface.h | 21 ++ src/actioninterface.h | 9 + src/actions/backproxyaction.h | 23 +++ src/actions/dummyaction.h | 12 ++ src/actions/setvalueaction.h | 31 +++ src/actions/switchscreenaction.h | 13 ++ src/changevaluedisplay.cpp | 34 ++++ src/changevaluedisplay.h | 129 +++++++++++++ src/changevaluedisplay_bool.cpp | 32 ++++ src/changevaluedisplay_bool.h | 22 +++ src/changevaluedisplay_daylightsavingmode.cpp | 43 +++++ src/changevaluedisplay_daylightsavingmode.h | 25 +++ src/changevaluedisplay_sntp_sync_mode_t.cpp | 40 ++++ src/changevaluedisplay_sntp_sync_mode_t.h | 24 +++ src/checkboxicon.h | 18 ++ src/colorinterface.h | 33 ++++ src/display.h | 88 +++++++++ src/fontinterface.h | 29 +++ src/icon.h | 2 + src/iconinterface.h | 20 ++ src/icons/back.cpp | 44 +++++ src/icons/back.h | 10 + src/icons/checked.cpp | 44 +++++ src/icons/checked.h | 10 + src/icons/unchecked.cpp | 44 +++++ src/icons/unchecked.h | 10 + src/menudisplay.cpp | 180 ++++++++++++++++++ src/menudisplay.h | 127 ++++++++++++ src/menuitem.h | 30 +++ src/textinterface.h | 77 ++++++++ src/tftinstance.cpp | 5 + src/tftinstance.h | 8 + src/widgets/graph.h | 148 ++++++++++++++ src/widgets/label.cpp | 59 ++++++ src/widgets/label.h | 30 +++ src/widgets/progressbar.cpp | 32 ++++ src/widgets/progressbar.h | 29 +++ src/widgets/reverseprogressbar.cpp | 32 ++++ src/widgets/reverseprogressbar.h | 29 +++ src/widgets/verticalmeter.cpp | 62 ++++++ src/widgets/verticalmeter.h | 23 +++ src/widgets/vumeter.cpp | 135 +++++++++++++ src/widgets/vumeter.h | 17 ++ 47 files changed, 1876 insertions(+), 1 deletion(-) create mode 100644 icons/back.png create mode 100644 icons/checked.png create mode 100644 icons/unchecked.png create mode 100644 src/accessorinterface.h create mode 100644 src/actioninterface.h create mode 100644 src/actions/backproxyaction.h create mode 100644 src/actions/dummyaction.h create mode 100644 src/actions/setvalueaction.h create mode 100644 src/actions/switchscreenaction.h create mode 100644 src/changevaluedisplay.cpp create mode 100644 src/changevaluedisplay.h create mode 100644 src/changevaluedisplay_bool.cpp create mode 100644 src/changevaluedisplay_bool.h create mode 100644 src/changevaluedisplay_daylightsavingmode.cpp create mode 100644 src/changevaluedisplay_daylightsavingmode.h create mode 100644 src/changevaluedisplay_sntp_sync_mode_t.cpp create mode 100644 src/changevaluedisplay_sntp_sync_mode_t.h create mode 100644 src/checkboxicon.h create mode 100644 src/colorinterface.h create mode 100644 src/display.h create mode 100644 src/fontinterface.h create mode 100644 src/iconinterface.h create mode 100644 src/icons/back.cpp create mode 100644 src/icons/back.h create mode 100644 src/icons/checked.cpp create mode 100644 src/icons/checked.h create mode 100644 src/icons/unchecked.cpp create mode 100644 src/icons/unchecked.h create mode 100644 src/menudisplay.cpp create mode 100644 src/menudisplay.h create mode 100644 src/menuitem.h create mode 100644 src/textinterface.h create mode 100644 src/tftinstance.cpp create mode 100644 src/tftinstance.h create mode 100644 src/widgets/graph.h create mode 100644 src/widgets/label.cpp create mode 100644 src/widgets/label.h create mode 100644 src/widgets/progressbar.cpp create mode 100644 src/widgets/progressbar.h create mode 100644 src/widgets/reverseprogressbar.cpp create mode 100644 src/widgets/reverseprogressbar.h create mode 100644 src/widgets/verticalmeter.cpp create mode 100644 src/widgets/verticalmeter.h create mode 100644 src/widgets/vumeter.cpp create mode 100644 src/widgets/vumeter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a1d4bad..33eb39c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,59 @@ set(headers + src/accessorinterface.h + src/actioninterface.h + src/changevaluedisplay.h + src/changevaluedisplay_bool.h + src/changevaluedisplay_daylightsavingmode.h + src/changevaluedisplay_sntp_sync_mode_t.h + src/checkboxicon.h + src/colorinterface.h + src/display.h + src/fontinterface.h src/icon.h + src/iconinterface.h + src/menudisplay.h + src/menuitem.h + src/tftinstance.h + src/textinterface.h + src/actions/backproxyaction.h + src/actions/dummyaction.h + src/actions/setvalueaction.h + src/actions/switchscreenaction.h + src/icons/back.h + src/icons/checked.h + src/icons/unchecked.h + src/widgets/graph.h + src/widgets/label.h + src/widgets/progressbar.h + src/widgets/reverseprogressbar.h + src/widgets/verticalmeter.h + src/widgets/vumeter.h ) set(sources + src/changevaluedisplay.cpp + src/changevaluedisplay_bool.cpp + src/changevaluedisplay_daylightsavingmode.cpp + src/changevaluedisplay_sntp_sync_mode_t.cpp + src/menudisplay.cpp + src/tftinstance.cpp + src/icons/back.cpp + src/icons/checked.cpp + src/icons/unchecked.cpp + src/widgets/label.cpp + src/widgets/progressbar.cpp + src/widgets/reverseprogressbar.cpp + src/widgets/verticalmeter.cpp + src/widgets/vumeter.cpp ) set(dependencies cpputils + cxx-ring-buffer espchrono espcpputils expected fmt - TFT_eSPI ) diff --git a/icons/back.png b/icons/back.png new file mode 100644 index 0000000000000000000000000000000000000000..c19f8d85307dfc83494c178bb85ef77ea4c5821d GIT binary patch literal 9724 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 O7(8A5T-G@yGywqXnfL1e literal 0 HcmV?d00001 diff --git a/src/accessorinterface.h b/src/accessorinterface.h new file mode 100644 index 0000000..e6f38f6 --- /dev/null +++ b/src/accessorinterface.h @@ -0,0 +1,21 @@ +#pragma once + +namespace espgui { +template +struct AccessorInterface +{ + virtual T getValue() const = 0; + virtual void setValue(T value) = 0; +}; + +//! A special type of AccessorInterface that allows for simple variable read/write operations +//! Can be used to read and write global settings for example. +template +struct RefAccessor : public virtual AccessorInterface +{ + virtual T& getRef() const = 0; + + T getValue() const override { return getRef(); }; + void setValue(T value) override { getRef() = value; }; +}; +} // namespace espgui diff --git a/src/actioninterface.h b/src/actioninterface.h new file mode 100644 index 0000000..6725bce --- /dev/null +++ b/src/actioninterface.h @@ -0,0 +1,9 @@ +#pragma once + +namespace espgui { +class ActionInterface +{ +public: + virtual void triggered() = 0; +}; +} // namespace espgui diff --git a/src/actions/backproxyaction.h b/src/actions/backproxyaction.h new file mode 100644 index 0000000..12cfaef --- /dev/null +++ b/src/actions/backproxyaction.h @@ -0,0 +1,23 @@ +#pragma once + +// local includes +#include "actioninterface.h" + +namespace espgui { +class BackProxyAction : public virtual ActionInterface +{ +public: + BackProxyAction(BackInterface &backInterface) : + m_backInterface{backInterface} + { + } + + void triggered() override + { + m_backInterface.back(); + } + +private: + BackInterface &m_backInterface; +}; +} // namespace espgui diff --git a/src/actions/dummyaction.h b/src/actions/dummyaction.h new file mode 100644 index 0000000..4fc3171 --- /dev/null +++ b/src/actions/dummyaction.h @@ -0,0 +1,12 @@ +#pragma once + +// local includes +#include "actioninterface.h" + +namespace espgui { +class DummyAction : public virtual ActionInterface +{ +public: + void triggered() override {} +}; +} // namespace espgui diff --git a/src/actions/setvalueaction.h b/src/actions/setvalueaction.h new file mode 100644 index 0000000..593b2b5 --- /dev/null +++ b/src/actions/setvalueaction.h @@ -0,0 +1,31 @@ +#pragma once + +// local includes +#include "actioninterface.h" +#include "accessorinterface.h" + +namespace espgui { +template +class SetValueAction : public virtual ActionInterface +{ +public: + SetValueAction(T value, AccessorInterface &accessorInterface, BackInterface &backInterface) : + m_value{value}, + m_accessorInterface{accessorInterface}, + m_backInterface{backInterface} + { + } + + void triggered() override + { + m_accessorInterface.setValue(m_value); + } + + T value() const { return m_value; } + +private: + const T m_value; + AccessorInterface &m_accessorInterface; + BackInterface &m_backInterface; +}; +} // namespace espgui diff --git a/src/actions/switchscreenaction.h b/src/actions/switchscreenaction.h new file mode 100644 index 0000000..507abbe --- /dev/null +++ b/src/actions/switchscreenaction.h @@ -0,0 +1,13 @@ +#pragma once + +// local includes +#include "actioninterface.h" + +namespace espgui { +template +class SwitchScreenAction : public virtual ActionInterface +{ +public: + void triggered() override { switchScreen(std::make_unique()...); } +}; +} // namespace espgui diff --git a/src/changevaluedisplay.cpp b/src/changevaluedisplay.cpp new file mode 100644 index 0000000..d4df3ef --- /dev/null +++ b/src/changevaluedisplay.cpp @@ -0,0 +1,34 @@ +#include "changevaluedisplay.h" + +namespace espgui { +void ChangeValueDisplayInterface::initScreen() +{ + tft.fillScreen(TFT_BLACK); + + m_titleLabel.start(); + + tft.fillRect(0, 34, tft.width(), 3, TFT_WHITE); + + tft.drawRect(25, 75, 190, 65, TFT_WHITE); + m_valueLabel.start(); + + tft.setTextFont(4); + tft.setTextColor(TFT_WHITE); + tft.drawString("Change value and", 10, 160); + tft.drawString("press button to", 10, 185); + tft.drawString("confirm and go", 10, 210); + tft.drawString("back.", 10, 235); +} + +template<> +void ChangeValueDisplay::redraw() +{ + tft.setTextFont(4); + tft.setTextColor(TFT_YELLOW); + m_titleLabel.redraw(text()); + + tft.setTextColor(TFT_WHITE, TFT_BLACK); + tft.setTextFont(7); + m_valueLabel.redraw(fmt::format("{:.02f}", m_value)); +} +} // namespace espgui diff --git a/src/changevaluedisplay.h b/src/changevaluedisplay.h new file mode 100644 index 0000000..a735c7a --- /dev/null +++ b/src/changevaluedisplay.h @@ -0,0 +1,129 @@ +#pragma once + +// 3rdparty lib includes +#include + +// local includes +#include "display.h" +#include "textinterface.h" +#include "actioninterface.h" +#include "accessorinterface.h" +#include "widgets/label.h" +#include "tftinstance.h" + +namespace espgui { +class ChangeValueDisplayInterface : + public Display, + public virtual TextInterface, + public virtual ActionInterface +{ +public: + void initScreen() override; + + TextInterface *asTextInterface() override { return this; } + const TextInterface *asTextInterface() const override { return this; } + + ChangeValueDisplayInterface *asChangeValueDisplayInterface() override { return this; } + const ChangeValueDisplayInterface *asChangeValueDisplayInterface() const override { return this; } + + virtual int shownValue() const = 0; + virtual void setShownValue(int value) = 0; + +protected: + Label m_titleLabel{5, 5}; // 230, 25 + Label m_valueLabel{26, 81}; // 188, 53 +}; + +template +class ChangeValueDisplaySettingsInterface +{ +public: + virtual Tvalue step() const { return 1; }; +}; + +template +class RatioNumberStep : public virtual ChangeValueDisplaySettingsInterface +{ +public: + Tvalue step() const override { return Tvalue(Tratio::num) / Tratio::den; } +}; + +template +class ChangeValueDisplay : + public ChangeValueDisplayInterface, + public virtual AccessorInterface, + public virtual ChangeValueDisplaySettingsInterface +{ + using Base = ChangeValueDisplayInterface; + +public: + void start() override; + void update() override; + void redraw() override; + + void rotate(int offset) override; + void confirm() override; + + int shownValue() const { return m_value; } + void setShownValue(int value) { m_value = value; } + +private: + Tvalue m_value{}; + + int m_rotateOffset; + bool m_pressed{}; +}; + +template +void ChangeValueDisplay::start() +{ + m_value = static_cast*>(this)->getValue(); + + m_rotateOffset = 0; + m_pressed = false; +} + +template +void ChangeValueDisplay::update() +{ + if (!m_pressed) + { + const auto rotateOffset = m_rotateOffset; + m_rotateOffset = 0; + + m_value -= rotateOffset * static_cast*>(this)->step(); + } + else + { + static_cast*>(this)->setValue(m_value); + triggered(); + } +} + +template +void ChangeValueDisplay::redraw() +{ + tft.setTextFont(4); + tft.setTextColor(TFT_YELLOW); + m_titleLabel.redraw(text()); + + tft.setTextColor(TFT_WHITE, TFT_BLACK); + tft.setTextFont(7); + m_valueLabel.redraw(std::to_string(m_value)); +} + +template<> +void ChangeValueDisplay::redraw(); + +template +void ChangeValueDisplay::rotate(int offset) +{ + m_rotateOffset += offset; +} + +template +void ChangeValueDisplay::confirm() +{ + m_pressed = true; +} +} // namespace espgui diff --git a/src/changevaluedisplay_bool.cpp b/src/changevaluedisplay_bool.cpp new file mode 100644 index 0000000..7d28613 --- /dev/null +++ b/src/changevaluedisplay_bool.cpp @@ -0,0 +1,32 @@ +#include "changevaluedisplay_bool.h" + +// local includes +#include "actions/setvalueaction.h" +#include "actions/backproxyaction.h" +#include "icons/back.h" + +namespace espgui { +namespace { +constexpr char TEXT_TRUE[] = "true"; +constexpr char TEXT_FALSE[] = "false"; +constexpr char TEXT_BACK[] = "Back"; +} // namespace + +ChangeValueDisplay::ChangeValueDisplay() +{ + constructMenuItem, StaticText>>(true, *this, *this); + constructMenuItem, StaticText>>(false, *this, *this); + constructMenuItem, StaticMenuItemIcon<&icons::back>>>(*this); +} + +void ChangeValueDisplay::start() +{ + Base::start(); + + switch (getValue()) + { + case true: setSelectedIndex(0); break; + case false: setSelectedIndex(1); break; + } +} +} // namespace espgui diff --git a/src/changevaluedisplay_bool.h b/src/changevaluedisplay_bool.h new file mode 100644 index 0000000..8c7f7cf --- /dev/null +++ b/src/changevaluedisplay_bool.h @@ -0,0 +1,22 @@ +#pragma once + +// local includes +#include "changevaluedisplay.h" +#include "menudisplay.h" +#include "actioninterface.h" + +namespace espgui { +template<> +class ChangeValueDisplay : + public MenuDisplay, + public virtual AccessorInterface, + public virtual ActionInterface +{ + using Base = MenuDisplay; + +public: + ChangeValueDisplay(); + + void start() override; +}; +} // namespace espgui diff --git a/src/changevaluedisplay_daylightsavingmode.cpp b/src/changevaluedisplay_daylightsavingmode.cpp new file mode 100644 index 0000000..3952c43 --- /dev/null +++ b/src/changevaluedisplay_daylightsavingmode.cpp @@ -0,0 +1,43 @@ +#include "changevaluedisplay_daylightsavingmode.h" + +// esp-idf includes +#include + +// local includes +#include "actions/setvalueaction.h" +#include "actions/backproxyaction.h" +#include "icons/back.h" + +namespace espgui { +namespace { +constexpr const char * const TAG = "ESPGUI"; + +constexpr char TEXT_NONE[] = "None"; +constexpr char TEXT_EUROPEANSUMMERTIME[] = "EuropeanSummerTime"; +constexpr char TEXT_USDAYLIGHTTIME[] = "UsDaylightTime"; +constexpr char TEXT_BACK[] = "Back"; +} // namespace + +ChangeValueDisplay::ChangeValueDisplay() +{ + constructMenuItem, StaticText>>(espchrono::DayLightSavingMode::None, *this, *this); + constructMenuItem, StaticText>>(espchrono::DayLightSavingMode::EuropeanSummerTime, *this, *this); + constructMenuItem, StaticText>>(espchrono::DayLightSavingMode::UsDaylightTime, *this, *this); + constructMenuItem, StaticMenuItemIcon<&icons::back>>>(*this); +} + +void ChangeValueDisplay::start() +{ + Base::start(); + + switch (const auto value = getValue()) + { + case espchrono::DayLightSavingMode::None: setSelectedIndex(0); break; + case espchrono::DayLightSavingMode::EuropeanSummerTime: setSelectedIndex(1); break; + case espchrono::DayLightSavingMode::UsDaylightTime: setSelectedIndex(2); break; + default: + ESP_LOGW(TAG, "Unknown DayLightSavingMode: %i", int(value)); + setSelectedIndex(3); + } +} +} diff --git a/src/changevaluedisplay_daylightsavingmode.h b/src/changevaluedisplay_daylightsavingmode.h new file mode 100644 index 0000000..fde36d0 --- /dev/null +++ b/src/changevaluedisplay_daylightsavingmode.h @@ -0,0 +1,25 @@ +#pragma once + +// 3rdparty lib includes +#include + +// local includes +#include "changevaluedisplay.h" +#include "menudisplay.h" +#include "actioninterface.h" + +namespace espgui { +template<> +class ChangeValueDisplay : + public MenuDisplay, + public virtual AccessorInterface, + public virtual ActionInterface +{ + using Base = MenuDisplay; + +public: + ChangeValueDisplay(); + + void start() override; +}; +} // namespace espgui diff --git a/src/changevaluedisplay_sntp_sync_mode_t.cpp b/src/changevaluedisplay_sntp_sync_mode_t.cpp new file mode 100644 index 0000000..bfefa17 --- /dev/null +++ b/src/changevaluedisplay_sntp_sync_mode_t.cpp @@ -0,0 +1,40 @@ +#include "changevaluedisplay_sntp_sync_mode_t.h" + +// esp-idf includes +#include + +// local includes +#include "actions/setvalueaction.h" +#include "actions/backproxyaction.h" +#include "icons/back.h" + +namespace espgui { +namespace { +constexpr const char * const TAG = "ESPGUI"; + +constexpr char TEXT_IMMED[] = "IMMED"; +constexpr char TEXT_SMOOTH[] = "SMOOTH"; +constexpr char TEXT_BACK[] = "Back"; +} // namespace + +ChangeValueDisplay::ChangeValueDisplay() +{ + constructMenuItem, StaticText>>(SNTP_SYNC_MODE_IMMED, *this, *this); + constructMenuItem, StaticText>>(SNTP_SYNC_MODE_SMOOTH, *this, *this); + constructMenuItem, StaticMenuItemIcon<&icons::back>>>(*this); +} + +void ChangeValueDisplay::start() +{ + Base::start(); + + switch (const auto value = getValue()) + { + case SNTP_SYNC_MODE_IMMED: setSelectedIndex(0); break; + case SNTP_SYNC_MODE_SMOOTH: setSelectedIndex(1); break; + default: + ESP_LOGW("BOBBY", "Unknown sntp_sync_mode_t: %i", int(value)); + setSelectedIndex(2); + } +} +} diff --git a/src/changevaluedisplay_sntp_sync_mode_t.h b/src/changevaluedisplay_sntp_sync_mode_t.h new file mode 100644 index 0000000..7053ea2 --- /dev/null +++ b/src/changevaluedisplay_sntp_sync_mode_t.h @@ -0,0 +1,24 @@ +#pragma once + +// esp-idf includes +#include + +// local includes +#include "changevaluedisplay.h" +#include "menudisplay.h" + +namespace espgui { +template<> +class ChangeValueDisplay : + public MenuDisplay, + public virtual AccessorInterface, + public virtual ActionInterface +{ + using Base = MenuDisplay; + +public: + ChangeValueDisplay(); + + void start() override; +}; +} // namespace espgui diff --git a/src/checkboxicon.h b/src/checkboxicon.h new file mode 100644 index 0000000..480b4f8 --- /dev/null +++ b/src/checkboxicon.h @@ -0,0 +1,18 @@ +#pragma once + +// local includes +#include "menuitem.h" +#include "accessorinterface.h" +#include "icons/checked.h" +#include "icons/unchecked.h" + +namespace espgui { +class CheckboxIcon : public virtual MenuItemIconInterface, public virtual AccessorInterface +{ +public: + const MenuItemIcon *icon() const override + { + return getValue() ? &icons::checked : &icons::unchecked; + } +}; +} // namespace espgui diff --git a/src/colorinterface.h b/src/colorinterface.h new file mode 100644 index 0000000..0defede --- /dev/null +++ b/src/colorinterface.h @@ -0,0 +1,33 @@ +#pragma once + +// 3rdparty lib includes +#include + +namespace espgui { +class ColorInterface { +public: + virtual int color() const { return TFT_WHITE; }; +}; + +template +class StaticColor : public virtual ColorInterface +{ +public: + static constexpr int STATIC_COLOR = TColor; + + int color() const override { return TColor; } +}; + +using DefaultColor = StaticColor; +using DisabledColor = StaticColor; + +class ChangeableColor : public virtual ColorInterface +{ +public: + int color() const override { return m_color; } + void setColor(const int &color) { m_color = color; } + +private: + int m_color; +}; +} // namespace espgui diff --git a/src/display.h b/src/display.h new file mode 100644 index 0000000..b0e7c21 --- /dev/null +++ b/src/display.h @@ -0,0 +1,88 @@ +#pragma once + +// system includes +#include + +// forward declares +namespace espgui { +class TextInterface; +class MenuDisplay; +class ChangeValueDisplayInterface; +} + +namespace espgui { +template +class makeComponent : public T... +{}; + +template +class makeComponentArgs : public T1, public T2, public T3... +{ +public: + template + makeComponentArgs(T&& ...args) : + T2{std::forward(args)...} + { + } +}; + +class ConfirmInterface +{ +public: + virtual void confirm() = 0; +}; + +class BackInterface +{ +public: + virtual void back() = 0; +}; + +template +class ConfirmActionInterface : public virtual ConfirmInterface +{ +public: + void confirm() override { T{}.triggered(); } +}; + +class DummyConfirm : public virtual ConfirmInterface +{ +public: + void confirm() override {} +}; + +template +class BackActionInterface : public virtual BackInterface +{ +public: + void back() override { T{}.triggered(); } +}; + +class DummyBack : public virtual BackInterface +{ +public: + void back() override {} +}; + +class Display : public virtual ConfirmInterface, public virtual BackInterface { +public: + virtual ~Display() = default; + + virtual void start() {}; + virtual void initScreen() {}; + virtual void update() {}; + virtual void redraw() {}; + virtual void stop() {} + + virtual void rotate(int offset) {} + + virtual TextInterface *asTextInterface() { return nullptr; } + virtual const TextInterface *asTextInterface() const { return nullptr; } + + virtual MenuDisplay *asMenuDisplay() { return nullptr; } + virtual const MenuDisplay *asMenuDisplay() const { return nullptr; } + + virtual ChangeValueDisplayInterface *asChangeValueDisplayInterface() { return nullptr; } + virtual const ChangeValueDisplayInterface *asChangeValueDisplayInterface() const { return nullptr; } +}; +} // namespace espgui diff --git a/src/fontinterface.h b/src/fontinterface.h new file mode 100644 index 0000000..d6d04b8 --- /dev/null +++ b/src/fontinterface.h @@ -0,0 +1,29 @@ +#pragma once + +namespace espgui { +class FontInterface { +public: + virtual int font() const { return 4; }; +}; + +template +class StaticFont : public virtual FontInterface +{ +public: + static constexpr int STATIC_FONT = TFont; + + int font() const override { return TFont; } +}; + +using DefaultFont = StaticFont<4>; + +class ChangeableFont : public virtual FontInterface +{ +public: + int font() const override { return m_font; } + void setfont(const int &font) { m_font = font; } + +private: + int m_font; +}; +} // namespace espgui diff --git a/src/icon.h b/src/icon.h index 4dd4a1d..92dde15 100644 --- a/src/icon.h +++ b/src/icon.h @@ -3,6 +3,7 @@ // system includes #include +namespace espgui { template struct Icon { @@ -11,3 +12,4 @@ struct Icon const unsigned short buffer[width*height]; }; +} // namespace espgui diff --git a/src/iconinterface.h b/src/iconinterface.h new file mode 100644 index 0000000..8ff1fe9 --- /dev/null +++ b/src/iconinterface.h @@ -0,0 +1,20 @@ +#pragma once + +// local includes +#include "icon.h" + +namespace espgui { +template +class IconInterface +{ +public: + virtual const Icon *icon() const { return nullptr; } +}; + +template *T> +class StaticIcon : public virtual IconInterface +{ +public: + virtual const Icon *icon() const { return T; } +}; +} // namespace espgui diff --git a/src/icons/back.cpp b/src/icons/back.cpp new file mode 100644 index 0000000..998b11a --- /dev/null +++ b/src/icons/back.cpp @@ -0,0 +1,44 @@ +#include "back.h" + +namespace espgui { +namespace icons { +const Icon<24, 24> back{{ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x016C, 0x014B, 0x010B, 0x01AC, 0x018C, 0x018C, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0010 (16) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x014B, 0x0009, 0x0000, // 0x0020 (32) pixels + 0x2AD0, 0x3331, 0x00CB, 0x09CD, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0030 (48) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x012B, 0x0007, 0x0000, 0x1A6F, 0x5CD7, 0x5C96, 0x0003, 0x09ED, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0040 (64) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x010B, 0x0006, 0xFFFF, 0x022F, 0x6518, // 0x0050 (80) pixels + 0x75FB, 0x5C96, 0x0000, 0x1AB0, 0x2312, 0x22F1, 0x1AB0, 0x1A6F, 0x0A0E, 0x016C, 0x0048, 0x00EA, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0060 (96) pixels + 0x0000, 0x0000, 0x010A, 0x0029, 0x0000, 0x022F, 0x6538, 0x761B, 0x761B, 0x5CD7, 0x024F, 0x22D0, 0x22D0, 0x1AB0, 0x1A6F, 0x124F, // 0x0070 (112) pixels + 0x01CD, 0x002A, 0x0000, 0x018C, 0x0007, 0x014B, 0x0000, 0x0000, 0x0002, 0x0003, 0x0A2E, 0x0000, 0x1A90, 0x6518, 0x7E3C, 0x761C, // 0x0080 (128) pixels + 0x761C, 0x6E1C, 0x65BB, 0x5D9A, 0x5D7A, 0x5539, 0x54F8, 0x4CB7, 0x4C76, 0x3BD4, 0x2B11, 0x1A6F, 0x016C, 0x43F5, 0x012B, 0x00A6, // 0x0090 (144) pixels + 0x0006, 0x024F, 0x0000, 0x22F1, 0x6518, 0x7E3C, 0x7E5C, 0x765C, 0x765C, 0x6E3C, 0x661C, 0x65FC, 0x5DBB, 0x559A, 0x4D5A, 0x44F9, // 0x00A0 (160) pixels + 0x44D8, 0x3C97, 0x4456, 0x3BD4, 0x2AF1, 0x09CD, 0x5D7C, 0x00AA, 0x022F, 0x0000, 0x2AF1, 0x6518, 0x863C, 0x7E3C, 0x6E3C, 0x663C, // 0x00B0 (176) pixels + 0x663D, 0x5E3D, 0x5E1C, 0x5DFC, 0x55DB, 0x559B, 0x4D5A, 0x4519, 0x3CB8, 0x3477, 0x3436, 0x33F5, 0x3BD4, 0x2AF1, 0x018C, 0x0000, // 0x00C0 (192) pixels + 0x014B, 0x22B0, 0x54B7, 0x5D9A, 0x4D7A, 0x3D7B, 0x357B, 0x2D9C, 0x2DDC, 0x2DDC, 0x2DBC, 0x2D7B, 0x355A, 0x3D5A, 0x453A, 0x4519, // 0x00D0 (208) pixels + 0x3CD8, 0x3477, 0x3436, 0x2BD5, 0x2BB5, 0x3394, 0x1A6F, 0x014C, 0x016B, 0x1A90, 0x3436, 0x24D9, 0x14D9, 0x1D1A, 0x1D5B, 0x1D9C, // 0x00E0 (224) pixels + 0x25DD, 0x25DD, 0x1D9C, 0x1D5B, 0x1D1A, 0x1CD9, 0x1CB8, 0x2CB8, 0x3CB8, 0x3477, 0x2C36, 0x2BD5, 0x2394, 0x33D5, 0x22F1, 0x11CC, // 0x00F0 (240) pixels + 0x022F, 0x0004, 0x1AD0, 0x2C57, 0x24F9, 0x1D1A, 0x1D5B, 0x1D7C, 0x1DBC, 0x25BC, 0x1D9C, 0x1D5B, 0x1D1A, 0x1CD9, 0x1C98, 0x1457, // 0x0100 (256) pixels + 0x1C37, 0x2C36, 0x2C16, 0x23D5, 0x2394, 0x23B5, 0x2B32, 0x11CD, 0x0000, 0x0A0E, 0x0000, 0x22F1, 0x2C77, 0x251A, 0x1D3A, 0x1D5B, // 0x0110 (272) pixels + 0x1D7B, 0x1D7B, 0x1D5B, 0x1D3A, 0x1CFA, 0x1CB9, 0x1C78, 0x1457, 0x1416, 0x13D5, 0x23D5, 0x23B5, 0x1B94, 0x1B94, 0x2B52, 0x11ED, // 0x0120 (288) pixels + 0x0023, 0x0000, 0x0A4F, 0x0000, 0x22F1, 0x3497, 0x251A, 0x1D1A, 0x1D3A, 0x2D5B, 0x2D3A, 0x2D1A, 0x24F9, 0x24D9, 0x1478, 0x1437, // 0x0130 (304) pixels + 0x13F6, 0x13B5, 0x1394, 0x1B74, 0x1B74, 0x1B94, 0x2B52, 0x11ED, 0x0000, 0x0087, 0x0000, 0x124F, 0x0000, 0x1AB0, 0x3497, 0x1D1A, // 0x0140 (320) pixels + 0x1CFA, 0x3497, 0x1B32, 0x2373, 0x2393, 0x2BD4, 0x3415, 0x2C57, 0x13F6, 0x1395, 0x1374, 0x1353, 0x1354, 0x2394, 0x2B32, 0x11ED, // 0x0150 (336) pixels + 0x0000, 0x0000, 0x0000, 0x00EA, 0x00EA, 0x0000, 0x126F, 0x3497, 0x24D9, 0x3435, 0x0000, 0x1270, 0x0000, 0x0029, 0x01AC, 0x22F1, // 0x0160 (352) pixels + 0x33F5, 0x1395, 0x1354, 0x1333, 0x0B33, 0x2394, 0x22F1, 0x22B0, 0x0000, 0x0000, 0x0000, 0x0000, 0x010B, 0x0008, 0x0000, 0x1A6F, // 0x0170 (368) pixels + 0x3477, 0x3435, 0x0006, 0x09ED, 0x0027, 0x00EA, 0x01AC, 0x0000, 0x22B0, 0x2BB4, 0x0B33, 0x1333, 0x0B34, 0x2B73, 0x22B0, 0x09AD, // 0x0180 (384) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x012B, 0x0009, 0x0000, 0x1A90, 0x3352, 0x004A, 0x09CD, 0x0000, 0x0000, 0x010A, 0x012B, // 0x0190 (400) pixels + 0x09CD, 0x2B53, 0x1354, 0x0B33, 0x1B54, 0x2B32, 0x228F, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x014B, 0x010B, // 0x01A0 (416) pixels + 0x0007, 0x018D, 0x01AC, 0x01AC, 0x0000, 0x0000, 0x0000, 0x014B, 0x09CD, 0x2B32, 0x1374, 0x0B33, 0x2353, 0x2AF1, 0x08EA, 0x1A0D, // 0x01B0 (432) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x01AD, 0x09ED, 0x00CA, 0x018C, 0x018C, 0x0000, 0x0000, 0x0000, 0x018C, // 0x01C0 (448) pixels + 0x09ED, 0x2B53, 0x1354, 0x1B53, 0x2B32, 0x224E, 0x2B11, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x01D0 (464) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00A8, 0x09ED, 0x122E, 0x2B73, 0x1B54, 0x2B53, 0x32D0, 0x43D4, 0x0004, 0x0000, // 0x01E0 (480) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0049, 0x451B, // 0x01F0 (496) pixels + 0x1AB0, 0x2BB4, 0x2B53, 0x32F0, 0x5D3A, 0x0008, 0x00C9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0200 (512) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x018C, 0x010B, 0x3332, 0x3393, 0x2AD0, 0x873F, 0x012B, 0x11ED, 0x0000, 0x0000, // 0x0210 (528) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0109, 0x01AD, 0x018C, // 0x0220 (544) pixels + 0x22B0, 0x32F0, 0x761E, 0x016C, 0x1A2E, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0230 (560) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x014B, 0x018C, 0x018C, 0x09CD, 0x4C77, 0x012B, 0x1A0E, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0240 (576) pixels +}}; +} // namespace icons +} // namespace espgui diff --git a/src/icons/back.h b/src/icons/back.h new file mode 100644 index 0000000..9af49d4 --- /dev/null +++ b/src/icons/back.h @@ -0,0 +1,10 @@ +#pragma once + +// local includes +#include "icon.h" + +namespace espgui { +namespace icons { +extern const Icon<24, 24> back; +} // namespace icons +} // namespace espgui diff --git a/src/icons/checked.cpp b/src/icons/checked.cpp new file mode 100644 index 0000000..45f7f54 --- /dev/null +++ b/src/icons/checked.cpp @@ -0,0 +1,44 @@ +#include "checked.h" + +namespace espgui { +namespace icons { +const Icon<24, 24> checked{{ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0010 (16) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0020 (32) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0030 (48) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0040 (64) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2945, 0x6B4D, 0x5AEB, 0x5AEB, // 0x0050 (80) pixels + 0x5AEB, 0x5AEB, 0x5AEB, 0x5AEB, 0x5AEB, 0x5AEB, 0x5AEB, 0x8BF1, 0x0240, 0x01E0, 0x02C0, 0x0280, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0060 (96) pixels + 0x0000, 0x0000, 0xA514, 0xA534, 0xA534, 0xBDD7, 0xBDF7, 0xBDF7, 0xBDF7, 0xBDF7, 0xBDF7, 0xBDF7, 0xBDF7, 0xBDF7, 0xBDF7, 0x8C91, // 0x0070 (112) pixels + 0x3807, 0x0000, 0x0000, 0x0260, 0x0260, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xBDD7, 0xCE59, 0xAD75, 0xB596, 0xB596, // 0x0080 (128) pixels + 0xB596, 0xB596, 0xAD75, 0xAD75, 0xAD75, 0xB596, 0xAD75, 0x9CD3, 0x0300, 0x8E31, 0x00E0, 0x0240, 0x0260, 0x0000, 0x0000, 0x0000, // 0x0090 (144) pixels + 0x0000, 0x8431, 0xA534, 0xA514, 0xA534, 0xAD75, 0xAD75, 0xAD75, 0xAD75, 0xAD75, 0xAD75, 0xAD55, 0xAD75, 0xAD75, 0xA554, 0x5BEB, // 0x00A0 (160) pixels + 0x4508, 0xBF77, 0x1C43, 0x0240, 0x01C0, 0x02C0, 0x0000, 0x0000, 0x0000, 0x8C51, 0xFFFF, 0x9492, 0xCE59, 0xF7BE, 0xFFBF, 0xF79E, // 0x00B0 (176) pixels + 0xF79E, 0xF79E, 0xEF7D, 0xEF7D, 0xF7BE, 0xEF9D, 0x8D51, 0x34E6, 0xA6F4, 0xA734, 0xA6F4, 0x54EA, 0x02E0, 0x0240, 0x0000, 0x0000, // 0x00C0 (192) pixels + 0x0000, 0x7BCF, 0x0000, 0x8C72, 0x7C8F, 0x6CCD, 0xC698, 0xF79E, 0xEF7D, 0xEF7D, 0xEF5D, 0xF79E, 0xF79E, 0x8510, 0x34C6, 0x9EB3, // 0x00D0 (208) pixels + 0x9692, 0x9672, 0x9692, 0x44C8, 0x02E0, 0x0220, 0x0000, 0x0000, 0x0160, 0x632C, 0x02A0, 0x3B27, 0x3426, 0x8E30, 0x23C4, 0xCEB9, // 0x00E0 (224) pixels + 0xF77E, 0xEF7D, 0xF79E, 0xF79E, 0x8D51, 0x2463, 0x8E51, 0x8630, 0x8610, 0x8610, 0x44A8, 0x02A0, 0x0160, 0x0000, 0x0000, 0x0000, // 0x00F0 (240) pixels + 0x0240, 0x02A0, 0x0260, 0x1BA3, 0x75AD, 0x860F, 0x656B, 0x4C29, 0xCED9, 0xF7BE, 0xEF7D, 0x8D31, 0x2C24, 0x7E0E, 0x7DEE, 0x7DCE, // 0x0100 (256) pixels + 0x7DCE, 0x4CA9, 0x02A0, 0x1843, 0x0220, 0x0280, 0x0000, 0x0000, 0x0240, 0x0000, 0x0321, 0x6D6B, 0x75AC, 0x75AC, 0x75EC, 0x654A, // 0x0110 (272) pixels + 0x33A6, 0xCED9, 0x9D73, 0x3C26, 0x7E0D, 0x75CC, 0x75AC, 0x6D8C, 0x5CEA, 0x2AE5, 0x634D, 0x2288, 0x7BCE, 0x0000, 0x0000, 0x0000, // 0x0120 (288) pixels + 0x0260, 0x0220, 0x0200, 0x1361, 0x6549, 0x658A, 0x6DCA, 0x760B, 0x5CE8, 0x02E0, 0x33C4, 0x760B, 0x6DCA, 0x6DCA, 0x6DAA, 0x7D6D, // 0x0130 (304) pixels + 0x646C, 0x6B6D, 0x6B6E, 0x8C4E, 0x52AD, 0x0000, 0x0000, 0x0000, 0x0200, 0x5B0B, 0x0220, 0x2A85, 0x2B83, 0x6568, 0x5DC8, 0x65E8, // 0x0140 (320) pixels + 0x7649, 0x6D49, 0x75EA, 0x6E09, 0x65C8, 0x65A9, 0x6CEB, 0x3BA8, 0xE75C, 0x7BD0, 0x634D, 0x73B1, 0x5ACA, 0x0000, 0x0000, 0x0000, // 0x0150 (336) pixels + 0x0000, 0x5AAD, 0x000A, 0x630D, 0x5BCB, 0x2344, 0x65A6, 0x4D84, 0x55C4, 0x4DC4, 0x4DA3, 0x4D83, 0x4563, 0x4463, 0x1B83, 0xE73C, // 0x0160 (352) pixels + 0xFFDF, 0x7BF0, 0x6B4D, 0x7BEE, 0x52AC, 0x0000, 0x0000, 0x0000, 0x0000, 0x630E, 0x0010, 0x5AEC, 0xCE79, 0x9DB3, 0x1300, 0x5DC3, // 0x0170 (368) pixels + 0x4E01, 0x4E22, 0x4E21, 0x4E02, 0x5D24, 0x4388, 0xE73C, 0xF79E, 0xFFDF, 0x8410, 0x6B6E, 0x840D, 0x630E, 0x0000, 0x0000, 0x0000, // 0x0180 (384) pixels + 0x0000, 0x73AE, 0x8C6A, 0x630D, 0xC638, 0xFFFF, 0xADF5, 0x0B60, 0x6664, 0x56A1, 0x66C3, 0x5503, 0x3B48, 0xDF3B, 0xF7BE, 0xF79E, // 0x0190 (400) pixels + 0xFFDF, 0x8431, 0x73AF, 0x6B4F, 0x73AE, 0x0000, 0x0000, 0x0000, 0x0000, 0x738D, 0x5280, 0x6B4D, 0xC638, 0xFFFF, 0xFFFF, 0xA5D4, // 0x01A0 (416) pixels + 0x1280, 0x7F45, 0x5D83, 0x0300, 0xEF9D, 0xFFFF, 0xFFDF, 0xF7BE, 0xFFFF, 0x8C51, 0x7BCF, 0x7BF2, 0x738D, 0x0000, 0x0000, 0x0000, // 0x01B0 (432) pixels + 0x0000, 0x738D, 0x0000, 0x6B6E, 0xC638, 0xFFFF, 0xFFFF, 0xFFFF, 0x9572, 0x32A5, 0x53C9, 0xEF9D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, // 0x01C0 (448) pixels + 0xFFFF, 0x8C72, 0x7BF0, 0x8C53, 0x738D, 0x0000, 0x0000, 0x0000, 0x0000, 0x7BD0, 0xA510, 0x7BCF, 0xA535, 0xCE59, 0xCE59, 0xCE59, // 0x01D0 (464) pixels + 0xCE79, 0x84B0, 0xADB5, 0xCE59, 0xCE59, 0xCE59, 0xCE59, 0xCE59, 0xC638, 0x8C51, 0x8410, 0x8430, 0x7BD0, 0x0000, 0x0000, 0x0000, // 0x01E0 (480) pixels + 0x0000, 0x8C71, 0x8431, 0x8431, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, 0x8410, 0x83F0, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, // 0x01F0 (496) pixels + 0x7BF0, 0x8410, 0x8C51, 0x8431, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9CD3, 0x9492, 0xB593, 0x9493, 0x9491, 0x94B1, // 0x0200 (512) pixels + 0x94B1, 0x94B1, 0x94B1, 0x94B1, 0x94B1, 0x94B1, 0x94B1, 0x9491, 0x9CF3, 0x4200, 0x8430, 0x9492, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0210 (528) pixels + 0x0000, 0x0020, 0x0000, 0x630D, 0x8C71, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, // 0x0220 (544) pixels + 0x8C71, 0x8410, 0x8C51, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0230 (560) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0240 (576) pixels +}}; +} // namespace icons +} // namespace espgui diff --git a/src/icons/checked.h b/src/icons/checked.h new file mode 100644 index 0000000..9ee2551 --- /dev/null +++ b/src/icons/checked.h @@ -0,0 +1,10 @@ +#pragma once + +// local includes +#include "icon.h" + +namespace espgui { +namespace icons { +extern const Icon<24, 24> checked; +} // namespace icons +} // namespace espgui diff --git a/src/icons/unchecked.cpp b/src/icons/unchecked.cpp new file mode 100644 index 0000000..d16ff69 --- /dev/null +++ b/src/icons/unchecked.cpp @@ -0,0 +1,44 @@ +#include "unchecked.h" + +namespace espgui { +namespace icons { +const Icon<24, 24> unchecked{{ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0010 (16) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0020 (32) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0030 (48) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0040 (64) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2945, 0x6B4D, 0x5AEB, 0x5AEB, // 0x0050 (80) pixels + 0x5AEB, 0x5AEB, 0x5AEB, 0x5AEB, 0x2124, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0060 (96) pixels + 0x0000, 0x0000, 0xA514, 0xA534, 0xA534, 0xBDD7, 0xBDF7, 0xBDF7, 0xBDF7, 0xBDF7, 0xBDD7, 0xB5B6, 0xB5B6, 0x31A6, 0x31A6, 0x39E7, // 0x0070 (112) pixels + 0x0180, 0x0000, 0x11E3, 0x4269, 0x4229, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xBDD7, 0xCE59, 0xAD75, 0xB596, 0xB596, // 0x0080 (128) pixels + 0xB596, 0xB596, 0xAD75, 0xAD55, 0xA514, 0xA514, 0xA514, 0xA514, 0xA514, 0x8C71, 0x8C51, 0x5B0C, 0x4AC9, 0x0000, 0x0000, 0x0000, // 0x0090 (144) pixels + 0x0000, 0x8431, 0xA534, 0xA514, 0xA534, 0xAD75, 0xAD75, 0xAD75, 0xAD75, 0xAD75, 0xAD75, 0xAD75, 0xAD75, 0xAD75, 0xAD75, 0xAD75, // 0x00A0 (160) pixels + 0xB596, 0x8C71, 0x8431, 0x5BCC, 0x2A85, 0x52AC, 0x0000, 0x0000, 0x0000, 0x8C51, 0xFFFF, 0x9492, 0xC638, 0xF7BE, 0xFFDF, 0xFFDF, // 0x00B0 (176) pixels + 0xF7BE, 0xF7BE, 0xFFDF, 0xF7BE, 0xF7DE, 0xF7BE, 0xFFDF, 0xFFDF, 0xFFFF, 0x8C51, 0x6B6E, 0x538A, 0x2A85, 0x528C, 0x0000, 0x0000, // 0x00C0 (192) pixels + 0x5ACB, 0x52AA, 0xFFFF, 0x8451, 0xC638, 0xF7BE, 0xF7BE, 0xF7BE, 0xF79E, 0xF79E, 0xF79E, 0xF7BE, 0xF7BE, 0xF79E, 0xF7BE, 0xF7BE, // 0x00D0 (208) pixels + 0xFFDF, 0x8C51, 0x6B6E, 0x53AA, 0x2284, 0x52AC, 0x0000, 0x0000, 0xEF7D, 0xFFFF, 0xEF5D, 0x632D, 0xBDF7, 0xF7BE, 0xF7BE, 0xF79E, // 0x00E0 (224) pixels + 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF79E, 0xF7BE, 0xF7BE, 0xFFDF, 0x8431, 0x6B6E, 0x4B0A, 0x52CB, 0x528C, 0x0000, 0x0000, // 0x00F0 (240) pixels + 0xF7BE, 0xE73C, 0x8CD1, 0x738E, 0xBDF7, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, // 0x0100 (256) pixels + 0xFFDF, 0x8431, 0x6B6E, 0x5B0C, 0x5ACC, 0x39C8, 0x0000, 0x0000, 0xF7BE, 0xF79E, 0xB616, 0x73CF, 0xC638, 0xFFDF, 0xF79E, 0xF7BE, // 0x0110 (272) pixels + 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF79E, 0xF7BE, 0xFFDF, 0xF7BE, 0xFFDF, 0x8431, 0x6B6E, 0x630D, 0x5AED, 0x0000, 0x0000, 0x0000, // 0x0120 (288) pixels + 0xF79E, 0xFFFF, 0x8CD1, 0x52CB, 0xC618, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF79E, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, // 0x0130 (304) pixels + 0xFFDF, 0x8431, 0x6B6E, 0x5AEC, 0x52AC, 0x0000, 0x0000, 0x0000, 0x10A2, 0xBDF7, 0x634B, 0x4A4A, 0xC618, 0xF7DE, 0xF7BE, 0xF7BE, // 0x0140 (320) pixels + 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xFFFF, 0x8C51, 0x6B6E, 0x7BCF, 0x5AED, 0x0000, 0x0000, 0x0000, // 0x0150 (336) pixels + 0x0000, 0x0000, 0x528C, 0x4A6A, 0xC618, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7DF, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF79E, 0xF7BE, // 0x0160 (352) pixels + 0xFFDF, 0x8431, 0x6B6E, 0x7BCF, 0x5AED, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x5ACD, 0x4A4A, 0xBDF8, 0xF7BE, 0xF7BE, 0xF7BE, // 0x0170 (368) pixels + 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xFFFF, 0x8C51, 0x738E, 0x73AE, 0x632E, 0x0000, 0x0000, 0x0000, // 0x0180 (384) pixels + 0x0000, 0x0000, 0x6B6D, 0x4209, 0xC618, 0xFFDF, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xF7BE, 0xFFDF, 0xF7BE, // 0x0190 (400) pixels + 0xFFDF, 0x8C51, 0x73AF, 0x738F, 0x73AE, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6B4C, 0x4A6A, 0xC638, 0xFFFF, 0xFFFF, 0xF7BE, // 0x01A0 (416) pixels + 0xFFDF, 0xFFDF, 0xF7BE, 0xFFDF, 0xF7BE, 0xF7BE, 0xFFDF, 0xF7BE, 0xFFFF, 0x8C51, 0x7BCF, 0x7BF1, 0x738D, 0x0000, 0x0000, 0x0000, // 0x01B0 (432) pixels + 0x0000, 0x0000, 0x6B4C, 0x5ACB, 0xC638, 0xFFFF, 0xFFFF, 0xFFDF, 0xF7BE, 0xFFDF, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, // 0x01C0 (448) pixels + 0xFFFF, 0x8C72, 0x7BF0, 0x8C53, 0x738D, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9492, 0x738E, 0xA534, 0xCE59, 0xCE59, 0xCE59, // 0x01D0 (464) pixels + 0xCE59, 0xC638, 0xC618, 0xCE59, 0xCE59, 0xCE59, 0xCE59, 0xCE59, 0xC638, 0x8C51, 0x8410, 0x8430, 0x7BD0, 0x0000, 0x0000, 0x0000, // 0x01E0 (480) pixels + 0x0000, 0x0000, 0x8C51, 0x8431, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, 0x7BF0, // 0x01F0 (496) pixels + 0x7BF0, 0x8410, 0x8C51, 0x8431, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x9CD3, 0x9492, 0xB593, 0x9493, 0x9491, 0x94B1, // 0x0200 (512) pixels + 0x94B1, 0x94B1, 0x94B1, 0x94B1, 0x94B1, 0x94B1, 0x94B1, 0x9491, 0x9CF3, 0x4200, 0x8430, 0x9492, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0210 (528) pixels + 0x0000, 0x0020, 0x0000, 0x630D, 0x8C71, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, 0x8C51, // 0x0220 (544) pixels + 0x8C71, 0x8410, 0x8C51, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0230 (560) pixels + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0240 (576) pixels +}}; +} // namespace icons +} // namespace espgui diff --git a/src/icons/unchecked.h b/src/icons/unchecked.h new file mode 100644 index 0000000..ce3c525 --- /dev/null +++ b/src/icons/unchecked.h @@ -0,0 +1,10 @@ +#pragma once + +// local includes +#include "icon.h" + +namespace espgui { +namespace icons { +extern const Icon<24, 24> unchecked; +} // namespace icons +} // namespace espgui diff --git a/src/menudisplay.cpp b/src/menudisplay.cpp new file mode 100644 index 0000000..c4f983c --- /dev/null +++ b/src/menudisplay.cpp @@ -0,0 +1,180 @@ +#include "menudisplay.h" + +// local includes +#include "tftinstance.h" + +namespace espgui { +void MenuDisplay::start() +{ + m_selectedIndex = 0; + m_scrollOffset = 0; + + m_rotateOffset = 0; + m_pressed = false; +} + +void MenuDisplay::initScreen() +{ + tft.fillScreen(TFT_BLACK); + + m_titleLabel.start(); + tft.fillRect(0, 34, tft.width(), 3, TFT_WHITE); + + for (auto &label : m_labels) + label.start(); + + runForEveryMenuItem([](MenuItem &item){ + item.start(); + }); + + m_icons.fill(nullptr); + + m_highlightedIndex = -1; +} + +void MenuDisplay::update() +{ + if (!m_pressed) + { + const auto offset = m_rotateOffset; + m_rotateOffset = 0; + + const auto itemCount = menuItemCount(); + + if (itemCount) + { + if (m_selectedIndex == -1) + m_selectedIndex = 0; + + m_selectedIndex = m_selectedIndex + offset; + + if (m_selectedIndex < 0) + m_selectedIndex += itemCount; + if (m_selectedIndex >= itemCount) + m_selectedIndex -= itemCount; + + if (m_selectedIndex < m_scrollOffset) + m_scrollOffset = m_selectedIndex; + if (m_selectedIndex >= m_scrollOffset + m_labels.size()) + m_scrollOffset = m_selectedIndex - m_labels.size() + 1; + } + else + { + m_selectedIndex = -1; + m_scrollOffset = 0; + } + + runForEveryMenuItem([&](MenuItem &item){ + item.update(); + }); + } + else + { + m_pressed = false; + if (m_selectedIndex >= 0) + getMenuItem(m_selectedIndex).triggered(); + } +} + +void MenuDisplay::redraw() +{ + tft.setTextFont(4); + tft.setTextColor(TFT_YELLOW, TFT_BLACK); + m_titleLabel.redraw(text()); + + int i{0}; + + auto labelsIter = std::begin(m_labels); + + auto iconsIter = std::begin(m_icons); + + int newHighlightedIndex{-1}; + + const auto drawItemRect = [](const auto &label, const auto color){ + tft.drawRect(5, + label.y()-1, + 240 - 10, + lineHeight+2, + color); + }; + + runForEveryMenuItem([&](MenuItem &item){ + const auto index = i++; + + if (index < m_scrollOffset) + return; + + if (labelsIter == std::end(m_labels)) + return; + + const auto relativeIndex = index - m_scrollOffset; + const auto selected = index == m_selectedIndex; + + if (selected) + newHighlightedIndex = relativeIndex; + else if (relativeIndex == m_highlightedIndex) + drawItemRect(*labelsIter, TFT_BLACK); + + tft.setTextFont(item.font()); + tft.setTextColor(item.color(), TFT_BLACK); + labelsIter->redraw(item.text()); + + if (item.icon() != *iconsIter) + { + tft.fillRect(5, labelsIter->y()+1, 24, 24, TFT_BLACK); + + auto icon = item.icon(); + if (icon) + { + tft.setSwapBytes(true); + tft.pushImage(6, labelsIter->y()+1, icon->WIDTH, icon->HEIGHT, icon->buffer); + tft.setSwapBytes(false); + } + *iconsIter = icon; + } + + if (selected && (relativeIndex != m_highlightedIndex)) + { + drawItemRect(*labelsIter, TFT_WHITE); + } + + labelsIter++; + iconsIter++; + }); + + for (; labelsIter != std::end(m_labels); labelsIter++, iconsIter++) + { + const auto relativeIndex = std::distance(std::begin(m_labels), labelsIter); + + if (relativeIndex == m_highlightedIndex) + drawItemRect(*labelsIter, TFT_BLACK); + + labelsIter->clear(); + + if (*iconsIter) + { + tft.fillRect(5, labelsIter->y()+1, 24, 24, TFT_BLACK); + *iconsIter = nullptr; + } + } + + m_highlightedIndex = newHighlightedIndex; +} + +void MenuDisplay::stop() +{ + runForEveryMenuItem([](MenuItem &item){ + item.stop(); + }); +} + +void MenuDisplay::rotate(int offset) +{ + m_rotateOffset += offset; +} + +void MenuDisplay::confirm() +{ + m_pressed = true; +} +} // namespace espgui diff --git a/src/menudisplay.h b/src/menudisplay.h new file mode 100644 index 0000000..e854be3 --- /dev/null +++ b/src/menudisplay.h @@ -0,0 +1,127 @@ +#pragma once + +// system includes +#include +#include +#include +#include +#include + +// local includes +#include "display.h" +#include "textinterface.h" +#include "widgets/label.h" +#include "menuitem.h" + +namespace espgui { +class MenuDisplay : public Display, public virtual TextInterface +{ +public: + void start() override; + void initScreen() override; + void update() override; + void redraw() override; + void stop() override; + + void rotate(int offset) override; + void confirm() override; + + TextInterface *asTextInterface() override { return this; } + const TextInterface *asTextInterface() const override { return this; } + + MenuDisplay *asMenuDisplay() override { return this; } + const MenuDisplay *asMenuDisplay() const override { return this; } + + int selectedIndex() const { return m_selectedIndex; } + + + std::size_t menuItemCount() const { return m_menuItems.size(); } + + MenuItem& getMenuItem(std::size_t index) + { + assert(index < m_menuItems.size()); + return *m_menuItems[index].get(); + } + + const MenuItem& getMenuItem(std::size_t index) const + { + assert(index < m_menuItems.size()); + return *m_menuItems[index].get(); + } + + void runForEveryMenuItem(std::function &&callback) + { + for (const auto &ptr : m_menuItems) + callback(*ptr); + } + + void runForEveryMenuItem(std::function &&callback) const + { + for (const auto &ptr : m_menuItems) + callback(*ptr); + } + + template + T &constructMenuItem(Args&&... args) + { + auto ptr = std::make_unique(std::forward(args)...); + T &ref = *ptr; + emplaceMenuItem(std::move(ptr)); + return ref; + } + + void emplaceMenuItem(std::unique_ptr &&ptr) + { + m_menuItems.emplace_back(std::move(ptr)); + } + + void clearMenuItems() + { + m_menuItems.clear(); + } + + std::unique_ptr takeLastMenuItem() + { + assert(!m_menuItems.empty()); + std::unique_ptr ptr = std::move(m_menuItems.back()); + m_menuItems.pop_back(); + return ptr; + } + +protected: + void setSelectedIndex(int selectedIndex) { m_selectedIndex = selectedIndex; } + +private: + Label m_titleLabel{5, 5}; // 230, 25 + + static constexpr auto iconWidth = 25; + static constexpr auto horizontalSpacing = 10; + static constexpr auto topMargin = 40; + static constexpr auto lineHeight = 25; + static constexpr auto verticalSpacing = 3; + + std::array m_labels {{ + Label{horizontalSpacing + iconWidth, topMargin+(0*(lineHeight+verticalSpacing))}, // 240-(horizontalSpacing*2)-iconWidth, lineHeight + Label{horizontalSpacing + iconWidth, topMargin+(1*(lineHeight+verticalSpacing))}, // 240-(horizontalSpacing*2)-iconWidth, lineHeight + Label{horizontalSpacing + iconWidth, topMargin+(2*(lineHeight+verticalSpacing))}, // 240-(horizontalSpacing*2)-iconWidth, lineHeight + Label{horizontalSpacing + iconWidth, topMargin+(3*(lineHeight+verticalSpacing))}, // 240-(horizontalSpacing*2)-iconWidth, lineHeight + Label{horizontalSpacing + iconWidth, topMargin+(4*(lineHeight+verticalSpacing))}, // 240-(horizontalSpacing*2)-iconWidth, lineHeight + Label{horizontalSpacing + iconWidth, topMargin+(5*(lineHeight+verticalSpacing))}, // 240-(horizontalSpacing*2)-iconWidth, lineHeight + Label{horizontalSpacing + iconWidth, topMargin+(6*(lineHeight+verticalSpacing))}, // 240-(horizontalSpacing*2)-iconWidth, lineHeight + Label{horizontalSpacing + iconWidth, topMargin+(7*(lineHeight+verticalSpacing))}, // 240-(horizontalSpacing*2)-iconWidth, lineHeight + Label{horizontalSpacing + iconWidth, topMargin+(8*(lineHeight+verticalSpacing))}, // 240-(horizontalSpacing*2)-iconWidth, lineHeight + Label{horizontalSpacing + iconWidth, topMargin+(9*(lineHeight+verticalSpacing))}, // 240-(horizontalSpacing*2)-iconWidth, lineHeight + }}; + + std::array *, 10> m_icons; + + int m_selectedIndex; + int m_scrollOffset; + int m_highlightedIndex; + + int m_rotateOffset; + bool m_pressed; + + std::vector> m_menuItems; +}; +} // namespace espgui diff --git a/src/menuitem.h b/src/menuitem.h new file mode 100644 index 0000000..9d9be87 --- /dev/null +++ b/src/menuitem.h @@ -0,0 +1,30 @@ +#pragma once + +// local includes +#include "textinterface.h" +#include "fontinterface.h" +#include "colorinterface.h" +#include "iconinterface.h" +#include "actioninterface.h" + +namespace espgui { +using MenuItemIconInterface = IconInterface<24, 24>; + +using MenuItemIcon = Icon<24, 24>; + +template +using StaticMenuItemIcon = StaticIcon<24, 24, T>; + +class MenuItem : + public virtual ActionInterface, + public virtual TextInterface, + public virtual FontInterface, + public virtual ColorInterface, + public virtual MenuItemIconInterface +{ +public: + virtual void start() {} + virtual void update() {} + virtual void stop() {} +}; +} // namespace espgui diff --git a/src/textinterface.h b/src/textinterface.h new file mode 100644 index 0000000..e660c6a --- /dev/null +++ b/src/textinterface.h @@ -0,0 +1,77 @@ +#pragma once + +// system includes +#include + +// 3rdparty lib includes +#include + +namespace espgui { +class TextInterface { +public: + virtual std::string text() const = 0; +}; + +class EmptyText : public virtual TextInterface +{ +public: + std::string text() const override { return {}; } +}; + +template +class StaticText : public virtual TextInterface +{ +public: + static constexpr const char *STATIC_TEXT = Ttext; + + std::string text() const override { return Ttext; } +}; + +class ChangeableText : public virtual TextInterface +{ +public: + std::string text() const override { return m_title; } + void setTitle(std::string &&title) { m_title = std::move(title); } + void setTitle(const std::string &title) { m_title = title; } + +private: + std::string m_title; +}; + +template +class CachedText : public virtual T +{ +public: + std::string text() const override + { + if (!m_loaded) + { + m_text = T::text(); + m_loaded = true; + } + + return m_text; + } + +private: + mutable bool m_loaded{}; + mutable std::string m_text; +}; + +template +class StaticallyCachedText : public virtual T +{ +public: + std::string text() const override + { + static const auto text = T::text(); + return text; + } +}; + +template +struct TextWithValueHelper : public virtual TextInterface +{ + std::string text() const override { return fmt::format("{} {}", Tprefix, Taccessor{}.getValue()); } +}; +} // namespace espgui diff --git a/src/tftinstance.cpp b/src/tftinstance.cpp new file mode 100644 index 0000000..4b6e7b4 --- /dev/null +++ b/src/tftinstance.cpp @@ -0,0 +1,5 @@ +#include "tftinstance.h" + +namespace espgui { +TFT_eSPI tft; +} diff --git a/src/tftinstance.h b/src/tftinstance.h new file mode 100644 index 0000000..87ec58b --- /dev/null +++ b/src/tftinstance.h @@ -0,0 +1,8 @@ +#pragma once + +// 3rdparty lib includes +#include + +namespace espgui { +extern TFT_eSPI tft; +} diff --git a/src/widgets/graph.h b/src/widgets/graph.h new file mode 100644 index 0000000..117e11b --- /dev/null +++ b/src/widgets/graph.h @@ -0,0 +1,148 @@ +#pragma once + +// system includes +#include + +// 3rdparty lib includes +#include +#include + +// local includes +#include "label.h" + +namespace espgui { +template +class Graph +{ + static constexpr int labelOffset = -5; + static constexpr int leftMargin = 40; + +public: + using Container = std::array>, COUNT>; + static constexpr int WIDTH = LENGTH+40; + + Graph(int x, int y, int height); + + void start(const Container &buffers); + void redraw(const Container &buffers); + +private: + void render(const Container &buffers, bool delta); + + const int m_x, m_y, m_height; + + std::array m_labels; + + std::array, LENGTH> m_lastPixels; + + float m_min; + float m_max; +}; + +template +Graph::Graph(int x, int y, int height) : + m_x{x}, m_y{y}, m_height{height}, + m_labels{ + Label{m_x, int(m_y+(m_height/4.f*0)+labelOffset)}, + Label{m_x, int(m_y+(m_height/4.f*1)+labelOffset)}, + Label{m_x, int(m_y+(m_height/4.f*2)+labelOffset)}, + Label{m_x, int(m_y+(m_height/4.f*3)+labelOffset)}, + Label{m_x, int(m_y+(m_height/4.f*4)+labelOffset)}, + } +{ +} + +template +void Graph::start(const Container &buffers) +{ + m_min = 0.f; + m_max = 10.f; + + tft.drawFastVLine(m_x+leftMargin-1, m_y, m_height, TFT_WHITE); + + for (auto iter = std::begin(m_labels); iter != std::end(m_labels); iter++) + { + tft.drawFastHLine(m_x+leftMargin-5, float(m_y)+(float(m_height)/(m_labels.size()-1)*std::distance(std::begin(m_labels), iter)), 4, TFT_WHITE); + iter->start(); + } + + render(buffers, false); +} + +template +void Graph::redraw(const Container &buffers) +{ + render(buffers, true); +} + +template +void Graph::render(const Container &buffers, bool delta) +{ + float min{std::numeric_limits::quiet_NaN()}, max{std::numeric_limits::quiet_NaN()}; + bool first{true}; + for (const ring_buffer &buffer : buffers) + { + auto minmax = std::minmax_element(std::cbegin(buffer), std::cend(buffer)); + + if (first || *minmax.first < min) + min = *minmax.first; + if (first || *minmax.second > max) + max = *minmax.second; + first = false; + } + + if (min < m_min) + m_min = min*0.9f; + else if (min > m_min*1.1f) + m_min = min*1.1f; + + if (max > m_max) + m_max = max*1.1f; + else if (max < m_max*0.9f) + m_max = max*1.1f; + + if (m_max-m_min < 2.f) + { + m_min-=1.f; + m_max+=1.f; + } + + if (m_min > 0 && m_max > 0) + m_min = 0; + if (m_min < 0 && m_max < 0) + m_max = 0; + + tft.setTextFont(2); + tft.setTextColor(TFT_WHITE, TFT_BLACK); + for (auto iter = std::begin(m_labels); iter != std::end(m_labels); iter++) + iter->redraw(std::to_string(int(m_max+((m_min-m_max)/(m_labels.size()-1)*std::distance(std::begin(m_labels), iter))))); + + int x{leftMargin}; + for (auto pixelsIter = std::begin(m_lastPixels); pixelsIter!=std::end(m_lastPixels); pixelsIter++) + { + const auto index0 = std::distance(std::begin(m_lastPixels), pixelsIter); + auto &pixels = *pixelsIter; + + for (auto iter = std::begin(pixels); iter != std::end(pixels); iter++) + { + const auto index1 = std::distance(std::begin(pixels), iter); + + const std::reference_wrapper> &ref = *(std::begin(buffers)+index1); + const ring_buffer &buffer = ref.get(); + + const float &val = *(std::begin(buffer)+index0); + + int y = cpputils::mapValueClamped(val, m_min, m_max, m_y+m_height-1, m_y+1); + if (!delta || *iter != y) + { + if (delta) + tft.drawFastVLine(x, *iter-1, 3, TFT_BLACK); + tft.drawFastVLine(x, y-1, 3, TFT_WHITE); + *iter = y; + } + } + + x++; + } +} +} // namespace espgui diff --git a/src/widgets/label.cpp b/src/widgets/label.cpp new file mode 100644 index 0000000..519e772 --- /dev/null +++ b/src/widgets/label.cpp @@ -0,0 +1,59 @@ +#include "label.h" + +// local includes +#include "tftinstance.h" + +namespace espgui { +Label::Label(int x, int y) : + m_x{x}, + m_y{y} +{ +} + +void Label::start() +{ + m_lastStr.clear(); + m_lastFont = -1; + m_lastColor = -1; + + m_lastWidth = 0; + m_lastHeight = 0; +} + +void Label::redraw(std::string_view str, bool forceRedraw) +{ + if (m_lastStr == str && + m_lastFont == tft.textfont && + m_lastColor == tft.textcolor && + !forceRedraw) + return; + + const auto renderedWidth = tft.drawString(str.data(), m_x, m_y); + const auto renderedHeight = tft.fontHeight(); + + if (renderedWidth < m_lastWidth) + tft.fillRect(m_x + renderedWidth, m_y, + m_lastWidth - renderedWidth, m_lastHeight, + tft.textbgcolor); + + if (renderedHeight < m_lastHeight) + tft.fillRect(m_x, m_y + renderedHeight, + renderedWidth, m_lastHeight - renderedHeight, + tft.textbgcolor); + + m_lastStr = str; + m_lastFont = tft.textfont; + m_lastColor = tft.textcolor; + + m_lastWidth = renderedWidth; + m_lastHeight = renderedHeight; +} + +void Label::clear() +{ + if (m_lastWidth || m_lastHeight) + tft.fillRect(m_x, m_y, m_lastWidth, m_lastHeight, tft.textbgcolor); + + start(); +} +} diff --git a/src/widgets/label.h b/src/widgets/label.h new file mode 100644 index 0000000..7ed13f5 --- /dev/null +++ b/src/widgets/label.h @@ -0,0 +1,30 @@ +#pragma once + +// system includes +#include + +namespace espgui { +class Label +{ +public: + Label(int x, int y); + + int x() const { return m_x; }; + int y() const { return m_y; }; + + void start(); + void redraw(std::string_view str, bool forceRedraw = false); + void clear(); + +private: + const int m_x; + const int m_y; + + std::string m_lastStr; + int m_lastFont; + int m_lastColor; + + int m_lastWidth; + int m_lastHeight; +}; +} // namespace espgui diff --git a/src/widgets/progressbar.cpp b/src/widgets/progressbar.cpp new file mode 100644 index 0000000..2bac405 --- /dev/null +++ b/src/widgets/progressbar.cpp @@ -0,0 +1,32 @@ +#include "progressbar.h" + +// 3rdparty lib includes +#include + +// local includes +#include "tftinstance.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} +{ +} + +void ProgressBar::start() +{ + m_lastValue = m_x+1; + tft.drawRect(m_x, m_y, m_width, m_height, TFT_WHITE); +} + +void ProgressBar::redraw(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); + else if (value > m_lastValue) + tft.fillRect(m_lastValue, m_y+1, value-m_lastValue, m_height-2, m_color); + + m_lastValue = value; +} +} // namespace espgui diff --git a/src/widgets/progressbar.h b/src/widgets/progressbar.h new file mode 100644 index 0000000..23b5180 --- /dev/null +++ b/src/widgets/progressbar.h @@ -0,0 +1,29 @@ +#pragma once + +// system includes +#include + +// 3rdparty lib includes +#include + +namespace espgui { +class ProgressBar +{ +public: + ProgressBar(int x, int y, int width, int height, int min, int max, uint32_t color=TFT_YELLOW); + + void start(); + void redraw(int value); + +private: + const int m_x; + const int m_y; + const int m_width; + const int m_height; + const int m_min; + const int m_max; + const uint32_t m_color; + + int m_lastValue{}; +}; +} // namespace espgui diff --git a/src/widgets/reverseprogressbar.cpp b/src/widgets/reverseprogressbar.cpp new file mode 100644 index 0000000..714977d --- /dev/null +++ b/src/widgets/reverseprogressbar.cpp @@ -0,0 +1,32 @@ +#include "reverseprogressbar.h" + +// 3rdparty lib includes +#include + +// local includes +#include "tftinstance.h" + +namespace espgui { +ReverseProgressBar::ReverseProgressBar(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} +{ +} + +void ReverseProgressBar::start() +{ + m_lastValue = m_x+m_width-1; + tft.drawRect(m_x, m_y, m_width, m_height, TFT_WHITE); +} + +void ReverseProgressBar::redraw(int value) +{ + value = cpputils::mapValueClamped(value, m_min, m_max, m_x+m_width-1, m_x+1); + + if (value < m_lastValue) + tft.fillRect(value, m_y+1, m_lastValue-value, m_height-2, m_color); + else if (value > m_lastValue) + tft.fillRect(m_lastValue, m_y+1, value-m_lastValue, m_height-2, TFT_BLACK); + + m_lastValue = value; +} +} // namespace espgui diff --git a/src/widgets/reverseprogressbar.h b/src/widgets/reverseprogressbar.h new file mode 100644 index 0000000..159bfe1 --- /dev/null +++ b/src/widgets/reverseprogressbar.h @@ -0,0 +1,29 @@ +#pragma once + +// system includes +#include + +// 3rdparty lib includes +#include + +namespace espgui { +class ReverseProgressBar +{ +public: + ReverseProgressBar(int x, int y, int width, int height, int min, int max, uint32_t color=TFT_YELLOW); + + void start(); + void redraw(int value); + +private: + const int m_x; + const int m_y; + const int m_width; + const int m_height; + const int m_min; + const int m_max; + const uint32_t m_color; + + int m_lastValue{}; +}; +} // namespace espgui diff --git a/src/widgets/verticalmeter.cpp b/src/widgets/verticalmeter.cpp new file mode 100644 index 0000000..3a0df4a --- /dev/null +++ b/src/widgets/verticalmeter.cpp @@ -0,0 +1,62 @@ +#include "verticalmeter.h" + +// 3rdparty lib includes +#include + +// local includes +#include "tftinstance.h" + +namespace espgui { +VerticalMeter::VerticalMeter(const char *text, const char *format, int x, int y) : + m_text{text}, m_format{format}, m_x{x}, m_y{y} +{ +} + +void VerticalMeter::start() +{ + int w = 36; + tft.drawRect(m_x, m_y, w, 155, TFT_GREY); + tft.fillRect(m_x + 2, m_y + 19, w - 3, 155 - 38, TFT_WHITE); + tft.setTextColor(TFT_CYAN, TFT_BLACK); + tft.drawCentreString(m_text, m_x + w / 2, m_y + 2, 2); + + for (int i = 0; i < 110; i += 10) + tft.drawFastHLine(m_x + 20, m_y + 27 + i, 6, TFT_BLACK); + + for (int i = 0; i < 110; i += 50) + tft.drawFastHLine(m_x + 20, m_y + 27 + i, 9, TFT_BLACK); + + tft.fillTriangle(m_x + 3, m_y + 127, m_x + 3 + 16, m_y + 127, m_x + 3, m_y + 127 - 5, TFT_RED); + tft.fillTriangle(m_x + 3, m_y + 127, m_x + 3 + 16, m_y + 127, m_x + 3, m_y + 127 + 5, TFT_RED); + + tft.drawCentreString("---", m_x + w / 2, m_y + 155 - 18, 2); +} + +void VerticalMeter::redraw(float value, float min, float max) +{ + tft.setTextColor(TFT_GREEN, TFT_BLACK); + + char buf[16]; + snprintf(&buf[0], 16, m_format, value); + tft.drawRightString(buf, m_x + 36 - 5, 187 - 27 + 155 - 18, 2); + + const int dx = 3 + m_x; + value = cpputils::mapValueClamped(value, min, max, 0.f, 100.f); + + while (m_oldValue > value) + { + const int dy = 187 + 100 - m_oldValue; + tft.drawLine(dx, dy - 5, dx + 16, dy, TFT_WHITE); + m_oldValue--; + tft.drawLine(dx, dy + 6, dx + 16, dy + 1, TFT_RED); + } + + while (m_oldValue < value) + { + const int dy = 187 + 100 - m_oldValue; + tft.drawLine(dx, dy + 5, dx + 16, dy, TFT_WHITE); + m_oldValue++; + tft.drawLine(dx, dy - 6, dx + 16, dy - 1, TFT_RED); + } +} +} diff --git a/src/widgets/verticalmeter.h b/src/widgets/verticalmeter.h new file mode 100644 index 0000000..2c5922c --- /dev/null +++ b/src/widgets/verticalmeter.h @@ -0,0 +1,23 @@ +#pragma once + +// system includes +#include + +namespace espgui { +class VerticalMeter +{ +public: + VerticalMeter(const char *text, const char *format, int x, int y); + + void start(); + void redraw(float value, float min, float max); + +private: + const char * const m_text; + const char * const m_format; + const int m_x; + const int m_y; + + float m_oldValue{}; +}; +} // namespace espgui diff --git a/src/widgets/vumeter.cpp b/src/widgets/vumeter.cpp new file mode 100644 index 0000000..5400eea --- /dev/null +++ b/src/widgets/vumeter.cpp @@ -0,0 +1,135 @@ +#include "vumeter.h" + +// local includes +#include "tftinstance.h" + +namespace espgui { +void VuMeter::start() +{ + ltx = 0; + osx = 120; + osy = 120; + + // Meter outline + tft.fillRect(0, 0, 239, 126, TFT_GREY); + tft.fillRect(5, 3, 230, 119, TFT_WHITE); + + tft.setTextColor(TFT_BLACK); // Text colour + + // Draw ticks every 5 degrees from -50 to +50 degrees (100 deg. FSD swing) + for (int i = -50; i < 51; i += 5) { + // Long scale tick length + int tl = 15; + + // Coodinates of tick to draw + float sx = cos((i - 90) * 0.0174532925); + float sy = sin((i - 90) * 0.0174532925); + uint16_t x0 = sx * (100 + tl) + 120; + uint16_t y0 = sy * (100 + tl) + 140; + uint16_t x1 = sx * 100 + 120; + uint16_t y1 = sy * 100 + 140; + + // Coordinates of next tick for zone fill + float sx2 = cos((i + 5 - 90) * 0.0174532925); + float sy2 = sin((i + 5 - 90) * 0.0174532925); + int x2 = sx2 * (100 + tl) + 120; + int y2 = sy2 * (100 + tl) + 140; + int x3 = sx2 * 100 + 120; + int y3 = sy2 * 100 + 140; + + // Yellow zone limits + //if (i >= -50 && i < 0) { + // tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_YELLOW); + // tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_YELLOW); + //} + + // Green zone limits + if (i >= 0 && i < 25) { + tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_GREEN); + tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREEN); + } + + // Orange zone limits + if (i >= 25 && i < 50) { + tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_ORANGE); + tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_ORANGE); + } + + // Short scale tick length + if (i % 25 != 0) tl = 8; + + // Recalculate coords incase tick lenght changed + x0 = sx * (100 + tl) + 120; + y0 = sy * (100 + tl) + 140; + x1 = sx * 100 + 120; + y1 = sy * 100 + 140; + + // Draw tick + tft.drawLine(x0, y0, x1, y1, TFT_BLACK); + + // Check if labels should be drawn, with position tweaks + if (i % 25 == 0) { + // Calculate label positions + x0 = sx * (100 + tl + 10) + 120; + y0 = sy * (100 + tl + 10) + 140; + switch (i / 25) { + case -2: tft.drawCentreString("0", x0, y0 - 12, 2); break; + case -1: tft.drawCentreString("7.5", x0, y0 - 9, 2); break; + case 0: tft.drawCentreString("15", x0, y0 - 6, 2); break; + case 1: tft.drawCentreString("22.5", x0, y0 - 9, 2); break; + case 2: tft.drawCentreString("30", x0, y0 - 12, 2); break; + } + } + + // Now draw the arc of the scale + sx = cos((i + 5 - 90) * 0.0174532925); + sy = sin((i + 5 - 90) * 0.0174532925); + x0 = sx * 100 + 120; + y0 = sy * 100 + 140; + // Draw scale arc, don't draw the last part + if (i < 50) tft.drawLine(x0, y0, x1, y1, TFT_BLACK); + } + + tft.drawString("KM/h", 5 + 230 - 40, 119 - 20, 2); // Units at bottom right + tft.drawCentreString("KM/h", 120, 70, 4); // Comment out to avoid font 4 + tft.drawRect(5, 3, 230, 119, TFT_BLACK); // Draw bezel line +} + +void VuMeter::redraw(float value) +{ + tft.setTextColor(TFT_BLACK, TFT_WHITE); + char buf[8]; dtostrf(value, 4, 0, buf); + tft.drawRightString(buf, 50, 119 - 25, 4); + + if (value < -3) value = -3; // Limit value to emulate needle end stops + if (value > 33) value = 33; + + float sdeg = map(value, -3, 33, -150, -30); // Map value to angle + // Calcualte tip of needle coords + float sx = cos(sdeg * 0.0174532925); + float sy = sin(sdeg * 0.0174532925); + + // Calculate x delta of needle start (does not start at pivot point) + float tx = tan((sdeg + 90) * 0.0174532925); + + // Erase old needle image + tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, TFT_WHITE); + tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, TFT_WHITE); + tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, TFT_WHITE); + + // Re-plot text under needle + tft.setTextColor(TFT_BLACK); + tft.drawCentreString("KM/h", 120, 70, 4); // // Comment out to avoid font 4 + + // Store new needle end coords for next erase + ltx = tx; + osx = sx * 98 + 120; + osy = sy * 98 + 140; + + // Draw the needle in the new postion, magenta makes needle a bit bolder + // draws 3 lines to thicken needle + tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, TFT_RED); + tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, TFT_MAGENTA); + tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, TFT_RED); +} +} // namespace espgui diff --git a/src/widgets/vumeter.h b/src/widgets/vumeter.h new file mode 100644 index 0000000..f903dc1 --- /dev/null +++ b/src/widgets/vumeter.h @@ -0,0 +1,17 @@ +#pragma once + +// system includes +#include + +namespace espgui { +class VuMeter +{ +public: + void start(); + void redraw(float value); + +private: + float ltx; // Saved x coord of bottom of needle + uint16_t osx, osy; // Saved x & y coords +}; +} // namespace espgui