Compare commits

..

8 Commits

Author SHA1 Message Date
e6591ce9be add: config ui 2022-10-16 04:48:46 +02:00
2a0334ba4b add: simple config system with font paths 2022-10-16 02:12:19 +02:00
9867242098 test 2022-03-06 23:33:27 +01:00
bacbf8a95b test 2022-03-06 23:24:38 +01:00
Simon Hardt
c43915a21e fix: digital text align. 2022-03-06 23:13:36 +01:00
Simon Hardt
9bb296f233 fix: mark parent dirty 2022-03-06 22:49:12 +01:00
Simon Hardt
4c70f3440d add: Container Widget 2022-03-06 22:03:12 +01:00
Simon Hardt
33f79d9f92 fix: missing header 2022-03-06 20:18:29 +01:00
24 changed files with 519 additions and 24 deletions

View File

@@ -26,7 +26,7 @@ int main()
export_font["sizes"] = json::object(); export_font["sizes"] = json::object();
std::vector<uint32_t> sizes = std::vector<uint32_t> sizes =
{12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 100, 250}; {12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 35, 40, 100, 250};
for(auto& size : sizes) for(auto& size : sizes)
{ {

View File

@@ -2,13 +2,16 @@
set(target frame) set(target frame)
configure_file ( configure_file (
"${PROJECT_SOURCE_DIR}/frame/src/Config.h.in" "${PROJECT_SOURCE_DIR}/frame/src/BuildConfig.h.in"
"${PROJECT_SOURCE_DIR}/frame/src/Config.h" "${PROJECT_SOURCE_DIR}/frame/src/BuildConfig.h"
) )
set(src set(src
src/main.cpp src/main.cpp
src/Config.hpp
src/Config.cpp
src/Size.hpp src/Size.hpp
src/Vector.hpp src/Vector.hpp
src/Rect.hpp src/Rect.hpp
@@ -39,16 +42,16 @@ set(src
src/widgets/Widget.hpp src/widgets/Widget.hpp
src/widgets/Widget.cpp src/widgets/Widget.cpp
src/widgets/ContainerWidget.hpp
src/widgets/ContainerWidget.cpp
src/widgets/WidgetRegistry.hpp src/widgets/WidgetRegistry.hpp
src/widgets/WidgetRegistry.cpp src/widgets/WidgetRegistry.cpp
src/widgets/clock/Digital.hpp src/widgets/clock/Digital.hpp
src/widgets/clock/Digital.cpp src/widgets/clock/Digital.cpp
src/widgets/clock/Analog.hpp src/widgets/clock/Analog.hpp
src/widgets/clock/Analog.cpp src/widgets/clock/Analog.cpp
src/widgets/clock/Date.hpp
src/widgets/clock/Date.cpp
) )
@@ -69,6 +72,7 @@ endif (BUILD_VIRTUAL_DISPLAY)
add_executable(${target} ${src}) add_executable(${target} ${src})
target_link_libraries(${target} PRIVATE target_link_libraries(${target} PRIVATE
fmt::fmt fmt::fmt
nlohmann_json::nlohmann_json
) )
if (BUILD_EPD) if (BUILD_EPD)

2
frame/src/BuildConfig.h Normal file
View File

@@ -0,0 +1,2 @@
#define BUILD_VIRTUAL_DISPLAY
/* #undef BUILD_EPD */

89
frame/src/Config.cpp Normal file
View File

@@ -0,0 +1,89 @@
#include "Config.hpp"
#include <filesystem>
#include <fmt/format.h>
#include <fstream>
namespace fs = std::filesystem;
#define PROPERTY(str, member) str.member = j.value(#member, str.member)
#define PROPERTY_OPT(str, member, type) \
str.member = \
j.contains(#member) ? j[#member].get<type>() : std::optional<type>{};
#define OBJ(str, mem) \
{ \
# mem, str.mem \
}
Config Config::Load()
{
fs::path const config_path{"config.json"};
if(fs::is_regular_file(config_path))
{
try
{
std::fstream file(config_path.string(), std::ios::in);
return json::parse(file);
}
catch(std::exception const& e)
{
fmt::print("Error: {}\n", e.what());
// TODO: Create Error Display Widget
return {};
}
}
fmt::print("[INFO] Creating config file\n");
Config config;
std::fstream file(config_path.string(), std::ios::out);
file << json(config).dump();
return config;
}
// == to json ==
void to_json(json& j, Config const& c)
{
j = {OBJ(c, font), OBJ(c, root)};
}
void to_json(json& j, FontConfig const& fc)
{
j = {OBJ(fc, base_path), OBJ(fc, fonts)};
}
void to_json(json& j, WidgetConfig const& w)
{
j = {OBJ(w, name), OBJ(w, parameters), OBJ(w, widgets)};
if(w.slot)
{
j["slot"] = w.slot.value();
}
}
// == from json ==
void from_json(json const& j, Config& c)
{
PROPERTY(c, font);
PROPERTY(c, root);
}
void from_json(json const& j, FontConfig& fc)
{
PROPERTY(fc, base_path);
PROPERTY(fc, fonts);
}
void from_json(json const& j, WidgetConfig& w)
{
PROPERTY(w, name);
PROPERTY(w, parameters);
PROPERTY(w, widgets);
PROPERTY_OPT(w, slot, int);
}

38
frame/src/Config.hpp Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <unordered_map>
using json = nlohmann::json;
struct FontConfig
{
std::string base_path = "fonts/";
std::unordered_map<std::string, std::string> fonts{
{"Fira Code", "FiraCode.json"}};
};
struct WidgetConfig
{
std::string name = "DigitalClock";
std::unordered_map<std::string, json> parameters = {};
std::vector<WidgetConfig> widgets = {};
std::optional<int> slot = {};
};
struct Config
{
FontConfig font{};
WidgetConfig root{};
static Config Load();
};
void to_json(json& j, Config const& c);
void to_json(json& j, FontConfig const& fc);
void to_json(json& j, WidgetConfig const& w);
void from_json(json const& j, Config& c);
void from_json(json const& j, FontConfig& fc);
void from_json(json const& j, WidgetConfig& w);

View File

@@ -2,6 +2,7 @@
#include "Color.hpp" #include "Color.hpp"
#include <cstring>
#include <stdint.h> #include <stdint.h>

View File

@@ -32,11 +32,11 @@ namespace frame
if(mRoot) if(mRoot)
{ {
mRenderTarget.Clear(); mRenderTarget.Clear();
mRoot->setSize(mDisplay->getSize());
mRoot->Render(mRenderTarget); mRoot->Render(mRenderTarget);
if(mDisplay)
{ mDisplay->Display(mRenderTarget.getImage());
mDisplay->Display(mRenderTarget.getImage());
}
} }
} }

View File

@@ -1,6 +1,6 @@
#include "Display.hpp" #include "Display.hpp"
#include "../Config.h" #include "../BuildConfig.h"
#ifdef BUILD_EPD #ifdef BUILD_EPD
# include "EPD_7in5_V2.hpp" # include "EPD_7in5_V2.hpp"

View File

@@ -1,7 +1,10 @@
#include "FontRegistry.hpp" #include "FontRegistry.hpp"
#include <filesystem>
#include <fmt/format.h> #include <fmt/format.h>
namespace fs = std::filesystem;
namespace frame::font namespace frame::font
{ {
std::shared_ptr<Font> GetFont(std::string_view name) std::shared_ptr<Font> GetFont(std::string_view name)
@@ -14,11 +17,26 @@ namespace frame::font
return Service::get<FontRegistry>()->Load(name); return Service::get<FontRegistry>()->Load(name);
} }
FontRegistry& LocateRegistry()
{
return *Service::get<FontRegistry>();
}
FontRegistry::FontRegistry() FontRegistry::FontRegistry()
: IService("FontRegistry") : IService("FontRegistry")
{ {
} }
void FontRegistry::LoadFromConfig(FontConfig const& config)
{
fs::path base{config.base_path};
for(auto const& [name, file] : config.fonts)
{
Load(name, (base / file).string());
}
}
bool FontRegistry::Load(std::string_view name) bool FontRegistry::Load(std::string_view name)
{ {
if(fonts.find(std::string{name}) != fonts.end()) if(fonts.find(std::string{name}) != fonts.end())
@@ -43,6 +61,29 @@ namespace frame::font
return false; return false;
} }
bool FontRegistry::Load(std::string_view name, std::string_view path)
{
if(fonts.find(std::string{name}) != fonts.end())
{
fmt::print("Font {} allready loaded!\n", name);
return true;
}
fmt::print("Loading font \"{}\": ", name);
auto ptr = Font::LoadFromFile(path);
if(ptr == nullptr)
{
fmt::print("Error not found!\n");
} else
{
fonts[std::string{name}] = ptr;
fmt::print("OK\n");
return true;
}
return false;
}
std::shared_ptr<Font> FontRegistry::Get(std::string_view name) std::shared_ptr<Font> FontRegistry::Get(std::string_view name)
{ {
auto it = fonts.find({std::string{name}}); auto it = fonts.find({std::string{name}});

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "../Config.hpp"
#include "../ServiceLocator.hpp" #include "../ServiceLocator.hpp"
#include "Font.hpp" #include "Font.hpp"
@@ -8,9 +9,11 @@
namespace frame::font namespace frame::font
{ {
class FontRegistry;
std::shared_ptr<Font> GetFont(std::string_view name); std::shared_ptr<Font> GetFont(std::string_view name);
bool LoadFont(std::string_view name); bool LoadFont(std::string_view name);
FontRegistry& LocateRegistry();
class FontRegistry : public IService class FontRegistry : public IService
{ {
@@ -19,7 +22,9 @@ namespace frame::font
public: public:
FontRegistry(); FontRegistry();
void LoadFromConfig(FontConfig const& config);
bool Load(std::string_view name); bool Load(std::string_view name);
bool Load(std::string_view name, std::string_view path);
std::shared_ptr<Font> Get(std::string_view name); std::shared_ptr<Font> Get(std::string_view name);
}; };

View File

@@ -6,31 +6,48 @@
using namespace std::chrono_literals; using namespace std::chrono_literals;
#include "Config.hpp"
#include "ScreenManager.hpp" #include "ScreenManager.hpp"
#include "display/Display.hpp" #include "display/Display.hpp"
#include "font/FontRegistry.hpp" #include "font/FontRegistry.hpp"
//#include "render/RenderTarget.hpp" //#include "render/RenderTarget.hpp"
//#include "widgets/clock/Analog.hpp" //#include "widgets/clock/Analog.hpp"
#include "widgets/ContainerWidget.hpp"
#include "widgets/WidgetRegistry.hpp" #include "widgets/WidgetRegistry.hpp"
int main() int main()
{ {
// == == Config == ==
auto config = Config::Load();
// == == Display == ==
auto display = frame::display::Create(); auto display = frame::display::Create();
if(!display) if(!display)
{ {
fmt::print("Error: Not display driver!");
return 0; return 0;
} }
display->Init(); display->Init();
frame::font::LoadFont("Fira Code"); // frame::font::LoadFont("Fira Code");
frame::font::LocateRegistry().LoadFromConfig(config.font);
frame::ScreenManager screen(std::move(display)); frame::ScreenManager screen(std::move(display));
auto const& widgets = frame::Service::get<frame::widgets::WidgetRegistry>(); auto const& widgets = frame::Service::get<frame::widgets::WidgetRegistry>();
screen.setRoot(widgets->Create("AnalogClock")); // std::shared_ptr<frame::widgets::ContainerWidget> con =
// std::static_pointer_cast<frame::widgets::ContainerWidget>(
// widgets->Create("AnalogClock"));
// con->setSlot(0, widgets->Create("DigitalClock"));
// con->setSlot(1, widgets->Create("Date"));
auto con = frame::widgets::LocateRegistry().BuildFromConfig(config.root);
screen.setRoot(con);
screen.MainLoop(); screen.MainLoop();
} }

View File

@@ -0,0 +1,35 @@
#include "ContainerWidget.hpp"
namespace frame::widgets
{
ContainerWidget::ContainerWidget(size_t slots)
{
widgets.resize(slots);
maxSlots = slots;
}
Widget::shared_ptr ContainerWidget::getSlot(size_t slot) const
{
if(slot >= maxSlots)
return nullptr;
return widgets[slot];
}
void ContainerWidget::setSlot(size_t slot, Widget::shared_ptr ptr)
{
ptr->setParent(this);
widgets[slot] = ptr;
}
void ContainerWidget::UpdateWidgets()
{
for(auto& el : widgets)
{
if(el)
{
el->Update();
}
}
}
} // namespace frame::widgets

View File

@@ -0,0 +1,23 @@
#pragma once
#include "Widget.hpp"
#include <vector>
namespace frame::widgets
{
class ContainerWidget : public Widget
{
std::vector<Widget::shared_ptr> widgets;
size_t maxSlots = 0;
public:
ContainerWidget(size_t slots);
Widget::shared_ptr getSlot(size_t slot) const;
void setSlot(size_t slot, Widget::shared_ptr);
void UpdateWidgets();
};
} // namespace frame::widgets

View File

@@ -3,7 +3,7 @@
namespace frame::widgets namespace frame::widgets
{ {
Widget::shared_ptr Widget::getParent() const Widget::week_ptr Widget::getParent() const
{ {
return mParent; return mParent;
} }

View File

@@ -11,7 +11,7 @@ namespace frame::widgets
{ {
public: public:
using shared_ptr = std::shared_ptr<Widget>; using shared_ptr = std::shared_ptr<Widget>;
using week_ptr = std::shared_ptr<Widget>; using week_ptr = Widget*;
public: public:
virtual ~Widget() = default; virtual ~Widget() = default;
@@ -20,7 +20,7 @@ namespace frame::widgets
virtual void Render(render::RenderTarget& rt) = 0; virtual void Render(render::RenderTarget& rt) = 0;
virtual void ComputeChildSize(Vector size){}; virtual void ComputeChildSize(Vector size){};
shared_ptr getParent() const; week_ptr getParent() const;
void setParent(week_ptr ptr); void setParent(week_ptr ptr);
void setSize(Vector size); void setSize(Vector size);
@@ -34,8 +34,8 @@ namespace frame::widgets
void setClear(); void setClear();
private: private:
week_ptr mParent; week_ptr mParent = nullptr;
bool mDirty = true; bool mDirty = true;
Vector mRelativePosition; Vector mRelativePosition;
Vector mSize; Vector mSize;

View File

@@ -1,5 +1,7 @@
#include "WidgetRegistry.hpp" #include "WidgetRegistry.hpp"
#include "ContainerWidget.hpp"
namespace frame::widgets namespace frame::widgets
{ {
@@ -19,4 +21,23 @@ namespace frame::widgets
return it->second(); return it->second();
} }
Widget::shared_ptr WidgetRegistry::BuildFromConfig(WidgetConfig const& w)
{
fmt::print("INFO: Creating widget {} \n", w.name);
auto widget = Create(w.name);
auto container = std::dynamic_pointer_cast<ContainerWidget>(widget);
if(container)
{
int counter = 0;
for(auto const& el : w.widgets)
{
int slot = el.slot ? el.slot.value() : counter;
container->setSlot(slot, BuildFromConfig(el));
}
}
return widget;
}
} // namespace frame::widgets } // namespace frame::widgets

View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "../Config.hpp"
#include "../ServiceLocator.hpp" #include "../ServiceLocator.hpp"
#include "Widget.hpp" #include "Widget.hpp"
@@ -14,7 +15,6 @@
namespace frame::widgets namespace frame::widgets
{ {
class WidgetRegistry : public IService class WidgetRegistry : public IService
{ {
@@ -29,8 +29,15 @@ namespace frame::widgets
bool Register(std::string_view name); bool Register(std::string_view name);
Widget::shared_ptr Create(std::string_view name) const; Widget::shared_ptr Create(std::string_view name) const;
Widget::shared_ptr BuildFromConfig(WidgetConfig const& w);
}; };
inline WidgetRegistry& LocateRegistry()
{
return *Service::get<frame::widgets::WidgetRegistry>();
}
template<class WIDGET> template<class WIDGET>
bool WidgetRegistry::Register(std::string_view name) bool WidgetRegistry::Register(std::string_view name)
{ {

View File

@@ -20,6 +20,11 @@ namespace frame::widgets
return std::make_shared<AnalogClock>(); return std::make_shared<AnalogClock>();
} }
AnalogClock::AnalogClock()
: ContainerWidget(2)
{
}
void AnalogClock::Update() void AnalogClock::Update()
{ {
auto const now = std::chrono::system_clock::now(); auto const now = std::chrono::system_clock::now();
@@ -30,6 +35,8 @@ namespace frame::widgets
setDirty(); setDirty();
last_time = min; last_time = min;
} }
UpdateWidgets();
} }
void AnalogClock::Render(render::RenderTarget& rt) void AnalogClock::Render(render::RenderTarget& rt)
@@ -97,9 +104,50 @@ namespace frame::widgets
setClear(); setClear();
// == Widgets ==
auto widget_center_1 = center;
widget_center_1.y -= radius / 2;
auto widget_center_2 = center;
widget_center_2.y += radius / 2;
auto const widget_height_h = radius / 4;
auto const widget_width_h = radius / 2;
Rect widget_box_1{widget_center_1.y - widget_height_h,
widget_center_1.x - widget_width_h,
widget_width_h * 2,
widget_height_h * 2};
Rect widget_box_2{widget_center_2.y - widget_height_h,
widget_center_2.x - widget_width_h,
widget_width_h * 2,
widget_height_h * 2};
// Top
auto top = getSlot(0);
if(top)
{
top->setSize({widget_box_1.width, widget_box_1.height});
rt.pushViewport(widget_box_1);
top->Render(rt);
rt.popViewport();
}
auto bottom = getSlot(1);
if(bottom)
{
bottom->setSize({widget_box_2.width, widget_box_2.height});
rt.pushViewport(widget_box_2);
bottom->Render(rt);
rt.popViewport();
}
// Text // Text
auto font = font::GetFont("Fira Code"); /*auto font = font::GetFont("Fira Code");
if(!font) if(!font)
return; return;
@@ -126,7 +174,7 @@ namespace frame::widgets
font_size, font_size,
AlignHorizontal::CENTER, AlignHorizontal::CENTER,
AlignVertical::CENTER); AlignVertical::CENTER);
rt.setInvert(false); rt.setInvert(false);*/
} }
} // namespace frame::widgets } // namespace frame::widgets

View File

@@ -1,13 +1,13 @@
#pragma once #pragma once
#include "../Widget.hpp" #include "../ContainerWidget.hpp"
#include <chrono> #include <chrono>
namespace frame::widgets namespace frame::widgets
{ {
class AnalogClock : public Widget class AnalogClock : public ContainerWidget
{ {
using Time = std::chrono::time_point<std::chrono::system_clock>; using Time = std::chrono::time_point<std::chrono::system_clock>;
@@ -16,6 +16,8 @@ namespace frame::widgets
public: public:
static Widget::shared_ptr Create(); static Widget::shared_ptr Create();
AnalogClock();
void Update() override; void Update() override;
void Render(render::RenderTarget& rt) override; void Render(render::RenderTarget& rt) override;
}; };

View File

@@ -0,0 +1,56 @@
#include "Date.hpp"
REGISTER_WIDGET(frame::widgets::Date, "Date");
#include "../../font/FontRegistry.hpp"
#include <ctime>
#include <fmt/chrono.h>
#include <fmt/format.h>
namespace frame::widgets
{
Widget::shared_ptr Date::Create()
{
return std::make_shared<Date>();
}
void Date::Update()
{
auto const now = std::chrono::system_clock::now();
auto const min = std::chrono::floor<std::chrono::minutes>(now);
if(last_time != min)
{
setDirty();
last_time = min;
}
}
void Date::Render(render::RenderTarget& rt)
{
std::time_t t = std::chrono::system_clock::to_time_t(last_time);
auto const& size = getSize();
Rect draw{0, 0, size.x, size.y};
auto text = fmt::format("{:%d.%m.%Y}", fmt::localtime(t));
auto font = font::GetFont("Fira Code");
auto font_size = font->getOptimalSize(text, draw);
rt.setInvert(true);
rt.DrawText(draw,
text,
*font,
font_size,
AlignHorizontal::CENTER,
AlignVertical::CENTER);
rt.setInvert(false);
setClear();
}
} // namespace frame::widgets

View File

@@ -0,0 +1,22 @@
#pragma once
#include "../Widget.hpp"
#include "../WidgetRegistry.hpp"
#include <chrono>
namespace frame::widgets
{
class Date : public Widget
{
using Time = std::chrono::time_point<std::chrono::system_clock>;
Time last_time;
public:
static Widget::shared_ptr Create();
void Update() override;
void Render(render::RenderTarget& rt) override;
};
} // namespace frame::widgets

View File

@@ -0,0 +1,60 @@
#include "Digital.hpp"
REGISTER_WIDGET(frame::widgets::DigitalClock, "DigitalClock");
#include "../../font/FontRegistry.hpp"
#include <ctime>
#include <fmt/chrono.h>
#include <fmt/format.h>
namespace frame::widgets
{
Widget::shared_ptr DigitalClock::Create()
{
return std::make_shared<DigitalClock>();
}
void DigitalClock::Update()
{
auto const now = std::chrono::system_clock::now();
auto const min = std::chrono::floor<std::chrono::minutes>(now);
if(last_time != min)
{
setDirty();
last_time = min;
}
}
void DigitalClock::Render(render::RenderTarget& rt)
{
std::time_t t = std::chrono::system_clock::to_time_t(last_time);
auto c_time = std::localtime(&t);
auto const& size = getSize();
Rect draw{0, 0, size.x, size.y};
auto text =
fmt::format("{:0>2}:{:0>2}", c_time->tm_hour, c_time->tm_min);
auto font = font::GetFont("Fira Code");
auto font_size = font->getOptimalSize(text, draw);
rt.setInvert(true);
rt.DrawText(draw,
text,
*font,
font_size,
AlignHorizontal::CENTER,
AlignVertical::CENTER);
rt.setInvert(false);
setClear();
}
} // namespace frame::widgets

View File

@@ -0,0 +1,24 @@
#pragma once
#include <chrono>
#include "../Widget.hpp"
#include "../WidgetRegistry.hpp"
namespace frame::widgets
{
class DigitalClock : public Widget
{
using Time = std::chrono::time_point<std::chrono::system_clock>;
Time last_time;
public:
static Widget::shared_ptr Create();
void Update() override;
void Render(render::RenderTarget& rt) override;
};
} // namespace frame::widgets