Compare commits

..

15 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
Simon Hardt
fe9fc4d69c add: Font registry 2022-03-06 20:15:57 +01:00
Simon Hardt
1772a3baff add: Widget registry 2022-03-06 19:05:37 +01:00
Simon Hardt
243bf41e90 add: Simple widget system 2022-03-06 03:04:32 +01:00
Simon Hardt
e1e410561c add: Rect font rendering 2022-03-05 23:06:10 +01:00
Simon Hardt
0fe1509c6c add: more font 2022-03-05 21:58:04 +01:00
Simon Hardt
0553927dad add: Basic Font Rendering 2022-03-05 02:37:08 +01:00
Simon Hardt
df1ce5583b remove Config 2022-03-04 21:53:27 +01:00
41 changed files with 1864 additions and 18 deletions

2
.gitignore vendored
View File

@@ -34,3 +34,5 @@
build/* build/*
.vscode/* .vscode/*
frame/src/Config.h
.editorconfig

View File

@@ -15,6 +15,7 @@ option(BUILD_EPD
) )
find_package(fmt CONFIG REQUIRED) find_package(fmt CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
option(BUILD_VIRTUAL_DISPLAY option(BUILD_VIRTUAL_DISPLAY
"Build virtual sfml based display" "Build virtual sfml based display"
@@ -28,6 +29,7 @@ endif (BUILD_EPD)
if (BUILD_VIRTUAL_DISPLAY) if (BUILD_VIRTUAL_DISPLAY)
find_package(SFML COMPONENTS system window graphics CONFIG REQUIRED) find_package(SFML COMPONENTS system window graphics CONFIG REQUIRED)
add_subdirectory(fontConverter)
endif(BUILD_VIRTUAL_DISPLAY) endif(BUILD_VIRTUAL_DISPLAY)
add_subdirectory(frame) add_subdirectory(frame)

View File

@@ -0,0 +1,10 @@
add_executable(font src/main.cpp)
target_link_libraries(font PUBLIC
sfml-system
sfml-window
sfml-graphics
fmt::fmt
nlohmann_json::nlohmann_json
)

102
fontConverter/src/main.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include <fmt/format.h>
#include <fstream>
#include <nlohmann/json.hpp>
#include <sfml/Graphics.hpp>
using json = nlohmann::json;
void PrintGlyph(sf::Font const& font, sf::Glyph const& g, unsigned int size);
void ExportSize(json& out, uint32_t size, sf::Font& font);
int main()
{
sf::Font font;
auto ok =
font.loadFromFile(R"(C:\Users\s-har\Downloads\FiraCode-Regular.ttf)");
fmt::print("Loaded {}\n", ok);
fmt::print("{}\n", font.getInfo().family);
fmt::print("Line Spacing: {}\n", font.getLineSpacing(14));
json export_font;
export_font["name"] = font.getInfo().family;
export_font["sizes"] = json::object();
std::vector<uint32_t> sizes =
{12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 35, 40, 100, 250};
for(auto& size : sizes)
{
fmt::print("Export: {}\n", size);
ExportSize(export_font["sizes"][fmt::format("{}", size)], size, font);
}
std::fstream out(fmt::format("{}.json", font.getInfo().family),
std::ios::out);
out << export_font.dump();
}
void ExportSize(json& out, uint32_t size, sf::Font& font)
{
std::map<char, sf::Glyph> glyphs;
for(uint8_t c = 32; c <= 126; ++c)
{
glyphs[(char)c] = font.getGlyph(c, size, false);
}
auto font_image = font.getTexture(size).copyToImage();
out["LineSpacing"] = font.getLineSpacing(size);
auto& Glyphs = out["Glyphs"];
for(auto&& [c, g] : glyphs)
{
auto& current = Glyphs[std::string{c}];
current["advance"] = g.advance;
current["x_offset"] = g.bounds.left;
current["y_offset"] = g.bounds.top;
current["width"] = g.bounds.width;
current["height"] = g.bounds.height;
std::vector<uint8_t> data;
for(int y = 0; y < (int)g.bounds.height; ++y)
{
for(int x = 0; x < (int)g.bounds.width; ++x)
{
auto c = font_image.getPixel(g.textureRect.left + x,
g.textureRect.top + y);
data.push_back(c.a > 100 ? 1 : 0);
}
}
current["data"] = data;
}
}
void PrintGlyph(sf::Font const& font, sf::Glyph const& g, unsigned int size)
{
auto font_image = font.getTexture(size).copyToImage();
for(int y = 0; y < (int)g.bounds.height; ++y)
{
for(int x = 0; x < (int)g.bounds.width; ++x)
{
auto c = font_image.getPixel(g.textureRect.left + x,
g.textureRect.top + y);
if(c.a > 100)
{
fmt::print("X");
} else
{
fmt::print(" ");
}
}
fmt::print("\n");
}
}

View File

@@ -2,20 +2,57 @@
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/Vector.hpp
src/Rect.hpp
src/Image.hpp src/Image.hpp
src/Image.cpp src/Image.cpp
src/ScreenManager.hpp
src/ScreenManager.cpp
src/ServiceLocator.hpp
src/ServiceLocator.cpp
src/display/IDisplay.hpp src/display/IDisplay.hpp
src/display/Display.hpp src/display/Display.hpp
src/display/Display.cpp src/display/Display.cpp
src/render/RenderTarget.hpp src/render/RenderTarget.hpp
src/render/RenderTarget.cpp src/render/RenderTarget.cpp
src/font/Font.hpp
src/font/Font.cpp
src/font/Glyph.hpp
src/font/Glyph.cpp
src/font/FontRegistry.hpp
src/font/FontRegistry.cpp
src/widgets/Widget.hpp
src/widgets/Widget.cpp
src/widgets/ContainerWidget.hpp
src/widgets/ContainerWidget.cpp
src/widgets/WidgetRegistry.hpp
src/widgets/WidgetRegistry.cpp
src/widgets/clock/Digital.hpp
src/widgets/clock/Digital.cpp
src/widgets/clock/Analog.hpp
src/widgets/clock/Analog.cpp
src/widgets/clock/Date.hpp
src/widgets/clock/Date.cpp
) )
if (BUILD_EPD) if (BUILD_EPD)
@@ -33,12 +70,23 @@ if (BUILD_VIRTUAL_DISPLAY)
endif (BUILD_VIRTUAL_DISPLAY) endif (BUILD_VIRTUAL_DISPLAY)
add_executable(${target} ${src}) add_executable(${target} ${src})
target_link_libraries(${target} PRIVATE fmt::fmt) target_link_libraries(${target} PRIVATE
fmt::fmt
nlohmann_json::nlohmann_json
)
if (BUILD_EPD) if (BUILD_EPD)
target_link_libraries(${target} PRIVATE waveshare) target_link_libraries(${target} PRIVATE
waveshare
)
endif (BUILD_EPD) endif (BUILD_EPD)
if (BUILD_VIRTUAL_DISPLAY) if (BUILD_VIRTUAL_DISPLAY)
target_link_libraries(${target} PRIVATE sfml-system sfml-window sfml-graphics) target_link_libraries(${target} PRIVATE
sfml-system
sfml-window
sfml-graphics
fmt::fmt
nlohmann_json::nlohmann_json
)
endif(BUILD_VIRTUAL_DISPLAY) endif(BUILD_VIRTUAL_DISPLAY)

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

@@ -1,7 +1,11 @@
#include "Image.hpp" #include "Image.hpp"
#include "Color.hpp"
#include <cstring>
#include <stdint.h> #include <stdint.h>
namespace frame namespace frame
{ {
@@ -17,6 +21,15 @@ namespace frame
{ {
} }
void Image::Create(uint32_t pWidth, uint32_t pHeight)
{
mWidth = pWidth;
mHeight = pHeight;
mBuffer.clear();
mBuffer.resize(mWidth * mHeight);
}
uint8_t& Image::at(uint32_t x, uint32_t y) uint8_t& Image::at(uint32_t x, uint32_t y)
{ {
return mBuffer.at(toInternal(x, y)); return mBuffer.at(toInternal(x, y));
@@ -27,6 +40,18 @@ namespace frame
return mBuffer.at(toInternal(x, y)); return mBuffer.at(toInternal(x, y));
} }
void Image::Clear(Color color)
{
memset(mBuffer.data(), color, mBuffer.size());
}
void Image::operator=(Image const& image)
{
mHeight = image.mHeight;
mWidth = image.mWidth;
mBuffer = image.mBuffer;
}
size_t Image::toInternal(uint32_t x, uint32_t y) const size_t Image::toInternal(uint32_t x, uint32_t y) const
{ {
return x + mWidth * y; return x + mWidth * y;

View File

@@ -1,9 +1,11 @@
#pragma once #pragma once
#include "Color.hpp"
#include "Size.hpp" #include "Size.hpp"
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
namespace frame namespace frame
{ {
@@ -14,12 +16,19 @@ namespace frame
std::vector<uint8_t> mBuffer; std::vector<uint8_t> mBuffer;
public: public:
Image() = default;
Image(uint32_t pWidth, uint32_t pHeight); Image(uint32_t pWidth, uint32_t pHeight);
Image(Size size); Image(Size size);
Image(Image const&) = default;
Image(Image&&) = default;
void Create(uint32_t pWidth, uint32_t pHeight);
uint8_t& at(uint32_t x, uint32_t y); uint8_t& at(uint32_t x, uint32_t y);
uint8_t const& at(uint32_t x, uint32_t y) const; uint8_t const& at(uint32_t x, uint32_t y) const;
void Clear(Color color);
auto getWidth() const auto getWidth() const
{ {
return mWidth; return mWidth;
@@ -29,6 +38,8 @@ namespace frame
return mHeight; return mHeight;
} }
void operator=(Image const& image);
private: private:
size_t toInternal(uint32_t x, uint32_t y) const; size_t toInternal(uint32_t x, uint32_t y) const;
}; };

15
frame/src/Rect.hpp Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <cstdint>
namespace frame
{
struct Rect
{
int32_t top;
int32_t left;
int32_t width;
int32_t height;
};
} // namespace frame

View File

@@ -0,0 +1,64 @@
#include "ScreenManager.hpp"
#include <thread>
namespace frame
{
ScreenManager::ScreenManager(std::unique_ptr<display::IDisplay> pDisplay)
: mDisplay(std::move(pDisplay))
, mRenderTarget(mDisplay->getSize())
{
}
void ScreenManager::Update()
{
if(mRoot)
{
mRoot->Update();
}
if(mDisplay)
{
mDisplay->Update();
if(!mDisplay->isOpen())
{
mIsRunning = false;
}
}
}
void ScreenManager::Render()
{
if(mRoot)
{
mRenderTarget.Clear();
mRoot->setSize(mDisplay->getSize());
mRoot->Render(mRenderTarget);
mDisplay->Display(mRenderTarget.getImage());
}
}
void ScreenManager::MainLoop()
{
while(mIsRunning)
{
Update();
if(mRoot && mRoot->isDirty())
{
Render();
}
std::this_thread::sleep_for(mUpdateInterval);
}
}
void ScreenManager::setRoot(widgets::Widget::shared_ptr widget)
{
mRoot = widget;
mRoot->ComputeChildSize(mDisplay->getSize());
}
} // namespace frame

View File

@@ -0,0 +1,36 @@
#pragma once
#include "display/IDisplay.hpp"
#include "render/RenderTarget.hpp"
#include "widgets/Widget.hpp"
#include <chrono>
#include <memory>
using namespace std::chrono_literals;
namespace frame
{
class ScreenManager
{
std::unique_ptr<display::IDisplay> mDisplay;
render::RenderTarget mRenderTarget;
widgets::Widget::shared_ptr mRoot;
std::chrono::duration<double> mUpdateInterval = 0.5s;
bool mIsRunning = true;
public:
ScreenManager(std::unique_ptr<display::IDisplay> pDisplay);
void Update();
void Render();
void MainLoop();
void setRoot(widgets::Widget::shared_ptr widget);
};
} // namespace frame

View File

@@ -0,0 +1,19 @@
#include "ServiceLocator.hpp"
namespace frame
{
IService::IService(std::string_view name)
: name(name)
{
}
std::string_view IService::getName() const
{
return name;
}
std::unordered_map<std::type_index, std::shared_ptr<IService>>
Service::Services = {};
} // namespace frame

View File

@@ -0,0 +1,40 @@
#pragma once
#include <memory>
#include <string>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
namespace frame
{
class IService
{
std::string name;
public:
IService(std::string_view name);
virtual ~IService() = default;
std::string_view getName() const;
};
class Service
{
static std::unordered_map<std::type_index, std::shared_ptr<IService>>
Services;
public:
template<class SERVICE>
static std::shared_ptr<SERVICE> get()
{
auto const type = std::type_index(typeid(SERVICE));
if(Services.find(type) == Services.end())
{
Services[type] = std::make_shared<SERVICE>();
}
return std::static_pointer_cast<SERVICE>(Services[type]);
}
};
} // namespace frame

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

@@ -12,6 +12,8 @@ namespace frame::display
std::string mName; std::string mName;
Size mSize; Size mSize;
bool mIsOpen = true;
public: public:
IDisplay(std::string_view pName, Size pSize) IDisplay(std::string_view pName, Size pSize)
: mName(pName) : mName(pName)
@@ -33,5 +35,15 @@ namespace frame::display
{ {
return mSize; return mSize;
} }
bool isOpen() const
{
return mIsOpen;
}
void Close()
{
mIsOpen = false;
}
}; };
} // namespace frame::display } // namespace frame::display

View File

@@ -55,6 +55,10 @@ namespace frame::display
sf::Event ev; sf::Event ev;
while(window.pollEvent(ev)) while(window.pollEvent(ev))
{ {
if(ev.type == sf::Event::Closed)
{
Close();
}
} }
} }

107
frame/src/font/Font.cpp Normal file
View File

@@ -0,0 +1,107 @@
#include "Font.hpp"
#include <fstream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace frame::font
{
std::shared_ptr<Font> Font::LoadFromFile(std::string_view file)
{
try
{
std::fstream file_data(std::string{file}, std::ios::in);
auto out = std::make_shared<Font>();
out->LoadFromStream(file_data);
return out;
}
catch(std::exception const& e)
{
return nullptr;
}
}
Glyph const& Font::getGlyph(uint32_t size, char c) const
{
return sizes.at(size).at(c);
}
uint32_t Font::getLength(std::string_view text, uint32_t size) const
{
uint32_t length = 0;
for(auto c : text)
{
auto const& g = getGlyph(size, c);
length += g.advance;
}
return length;
}
uint32_t Font::getHeight(std::string_view text, uint32_t size) const
{
uint32_t height = 0;
for(auto c : text)
{
auto const& g = getGlyph(size, c);
height = std::max((uint32_t)g.height, height);
}
return height;
}
int32_t Font::getMaxYOffset(std::string_view text, uint32_t size) const
{
int32_t y_offset = 0;
for(auto c : text)
{
auto const& g = getGlyph(size, c);
y_offset = std::min(g.y_offset, y_offset);
}
return y_offset;
}
uint32_t Font::getOptimalSize(std::string_view text, Rect rect) const
{
auto best_size = 0;
for(auto const& [size, value] : sizes)
{
auto width = getLength(text, size);
auto height = getHeight(text, size);
if(rect.width < width || rect.height < height)
break;
else
best_size = size;
}
return best_size;
}
void Font::LoadFromStream(std::istream& os)
{
json file_data = json::parse(os);
name = file_data["name"];
for(auto& [key, el] : file_data["sizes"].items())
{
auto size = std::atoi(key.c_str());
Glyphs curren_glyphs;
lineSpacing[size] = el["LineSpacing"];
for(auto& [glyph_c, glyph_d] : el["Glyphs"].items())
{
curren_glyphs[glyph_c[0]] = std::move(Glyph{glyph_d});
}
sizes[size] = std::move(curren_glyphs);
}
}
} // namespace frame::font

42
frame/src/font/Font.hpp Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include "../Rect.hpp"
#include "Glyph.hpp"
#include <map>
#include <memory>
#include <string_view>
namespace frame::font
{
using Glyphs = std::map<char, Glyph>;
using LineSpacing = std::map<uint32_t, uint32_t>;
class Font
{
std::string name;
std::map<uint32_t, Glyphs> sizes;
LineSpacing lineSpacing;
public:
Font() = default;
static std::shared_ptr<Font> LoadFromFile(std::string_view file);
Glyph const& getGlyph(uint32_t size, char c) const;
uint32_t getLength(std::string_view text, uint32_t size) const;
uint32_t getHeight(std::string_view text, uint32_t size) const;
int32_t getMaxYOffset(std::string_view text, uint32_t size) const;
uint32_t getOptimalSize(std::string_view text, Rect rect) const;
uint32_t getLineSpacing(uint32_t size) const
{
return lineSpacing.at(size);
}
protected:
void LoadFromStream(std::istream& os);
};
} // namespace frame::font

View File

@@ -0,0 +1,98 @@
#include "FontRegistry.hpp"
#include <filesystem>
#include <fmt/format.h>
namespace fs = std::filesystem;
namespace frame::font
{
std::shared_ptr<Font> GetFont(std::string_view name)
{
return Service::get<FontRegistry>()->Get(name);
}
bool LoadFont(std::string_view name)
{
return Service::get<FontRegistry>()->Load(name);
}
FontRegistry& LocateRegistry()
{
return *Service::get<FontRegistry>();
}
FontRegistry::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)
{
if(fonts.find(std::string{name}) != fonts.end())
{
fmt::print("Font {} allready loaded!\n", name);
return true;
} else
{
fmt::print("Loading font \"{}\": ", name);
auto ptr = Font::LoadFromFile(fmt::format("{}.json", name));
if(ptr == nullptr)
{
fmt::print("Error not found!\n");
} else
{
fonts[std::string{name}] = ptr;
fmt::print("OK\n");
return true;
}
}
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)
{
auto it = fonts.find({std::string{name}});
if(it == fonts.end())
{
if(!Load(name))
return nullptr;
it = fonts.find({std::string{name}});
}
return it->second;
}
} // namespace frame::font

View File

@@ -0,0 +1,31 @@
#pragma once
#include "../Config.hpp"
#include "../ServiceLocator.hpp"
#include "Font.hpp"
#include <memory>
#include <unordered_map>
namespace frame::font
{
class FontRegistry;
std::shared_ptr<Font> GetFont(std::string_view name);
bool LoadFont(std::string_view name);
FontRegistry& LocateRegistry();
class FontRegistry : public IService
{
std::unordered_map<std::string, std::shared_ptr<Font>> fonts;
public:
FontRegistry();
void LoadFromConfig(FontConfig const& config);
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);
};
} // namespace frame::font

39
frame/src/font/Glyph.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include "Glyph.hpp"
namespace frame::font
{
Glyph::Glyph(json const& data)
{
height = data["height"];
width = data["width"];
x_offset = data["x_offset"];
y_offset = data["y_offset"];
advance = data["advance"];
image.Create(width, height);
auto& image_data = data["data"];
int index = 0;
for(auto y = 0; y < height; ++y)
{
for(auto x = 0; x < width; ++x)
{
uint8_t i = image_data[index];
image.at(x, y) = i * 0xFF;
++index;
}
}
}
void Glyph::operator=(Glyph const& glyph)
{
image = glyph.image;
height = glyph.height;
width = glyph.width;
x_offset = glyph.x_offset;
y_offset = glyph.y_offset;
advance = glyph.advance;
}
} // namespace frame::font

28
frame/src/font/Glyph.hpp Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include "../Image.hpp"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace frame::font
{
struct Glyph
{
Image image;
uint8_t height;
uint8_t width;
int32_t x_offset;
int32_t y_offset;
int32_t advance;
public:
Glyph() = default;
Glyph(json const& data);
Glyph(Glyph const&) = default;
Glyph(Glyph&&) = default;
void operator=(Glyph const& glyph);
};
} // namespace frame::font

View File

@@ -6,13 +6,59 @@
using namespace std::chrono_literals; using namespace std::chrono_literals;
#include "Config.hpp"
#include "ScreenManager.hpp"
#include "display/Display.hpp" #include "display/Display.hpp"
#include "render/RenderTarget.hpp" #include "font/FontRegistry.hpp"
//#include "render/RenderTarget.hpp"
//#include "widgets/clock/Analog.hpp"
#include "widgets/ContainerWidget.hpp"
#include "widgets/WidgetRegistry.hpp"
int main()
{
// == == Config == ==
auto config = Config::Load();
// == == Display == ==
auto display = frame::display::Create();
if(!display)
{
fmt::print("Error: Not display driver!");
return 0;
}
display->Init();
// frame::font::LoadFont("Fira Code");
frame::font::LocateRegistry().LoadFromConfig(config.font);
frame::ScreenManager screen(std::move(display));
auto const& widgets = frame::Service::get<frame::widgets::WidgetRegistry>();
// 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();
}
/*
int main() int main()
{ {
constexpr double pi = 3.14159; constexpr double pi = 3.14159;
auto font = frame::font::Font::LoadFromFile("Fira Code.json");
auto display = frame::display::Create(); auto display = frame::display::Create();
if(!display) if(!display)
@@ -62,13 +108,109 @@ int main()
{110, (int)display->getSize().height - 10}, {110, (int)display->getSize().height - 10},
5); 5);
target.DrawText("Hallo Welt!", {100, 50}, *font, 14);
target.DrawText("Hallo Welt!", {50, 100}, *font, 30);
display->Display(target.getImage()); display->Display(target.getImage());
std::this_thread::sleep_for(10s); std::this_thread::sleep_for(10s);
target.Clear();
frame::Vector pos = display->getSize() / 2;
auto text = "10:30";
pos.x -= font->getLength(text, 250) / 2;
pos.y += font->getHeight(text, 250) / 2;
pos.y -= font->getMaxYOffset(text, 250) + font->getHeight(text, 250);
// target.DrawTextGlyphBounds(text, pos, *font, 250);
// target.DrawLine({0, (int32_t)pos.y},
// {(int32_t)display->getSize().width, pos.y});
auto rect = frame::Rect{0,
0,
(int)display->getSize().width,
(int)display->getSize().height};
target.DrawText(rect, text, *font, 250);
display->Display(target.getImage());
std::this_thread::sleep_for(2s);
target.Clear();
target.DrawText(rect, text, *font, 250, frame::AlignHorizontal::CENTER);
display->Display(target.getImage());
std::this_thread::sleep_for(2s);
target.Clear();
target.DrawText(rect, text, *font, 250, frame::AlignHorizontal::RIGHT);
display->Display(target.getImage());
std::this_thread::sleep_for(2s);
target.Clear();
target.DrawText(rect,
text,
*font,
250,
frame::AlignHorizontal::LEFT,
frame::AlignVertical::CENTER);
display->Display(target.getImage());
std::this_thread::sleep_for(2s);
target.Clear();
target.DrawText(rect,
text,
*font,
250,
frame::AlignHorizontal::CENTER,
frame::AlignVertical::CENTER);
display->Display(target.getImage());
std::this_thread::sleep_for(2s);
target.Clear();
target.DrawText(rect,
text,
*font,
250,
frame::AlignHorizontal::RIGHT,
frame::AlignVertical::CENTER);
display->Display(target.getImage());
std::this_thread::sleep_for(2s);
target.Clear();
target.DrawText(rect,
text,
*font,
250,
frame::AlignHorizontal::LEFT,
frame::AlignVertical::BOTTOM);
display->Display(target.getImage());
std::this_thread::sleep_for(2s);
target.Clear();
target.DrawText(rect,
text,
*font,
250,
frame::AlignHorizontal::CENTER,
frame::AlignVertical::BOTTOM);
display->Display(target.getImage());
std::this_thread::sleep_for(2s);
target.Clear();
target.DrawText(rect,
text,
*font,
250,
frame::AlignHorizontal::RIGHT,
frame::AlignVertical::BOTTOM);
display->Display(target.getImage());
std::this_thread::sleep_for(2s);
display->Clear(frame::display::Color::WHITE); display->Clear(frame::display::Color::WHITE);
} }
*/
/* /*
#include <EPD_7in5_V2.h> #include <EPD_7in5_V2.h>

View File

@@ -7,15 +7,25 @@ namespace frame::render
RenderTarget::RenderTarget(Size size) RenderTarget::RenderTarget(Size size)
: image(size) : image(size)
, root_size{0, 0, (int)size.width, (int)size.height}
{ {
} }
void RenderTarget::DrawPixel(Vector const& position, Color color) void RenderTarget::DrawPixel(Vector const& position, Color color)
{ {
if(position.x < 0 || position.y < 0 || position.x >= image.getWidth() auto current = getCurrentSize();
|| position.y >= image.getHeight())
if(position.x < 0 || position.y < 0 || position.x >= current.width
|| position.y >= current.height
|| (position.x + current.left) >= root_size.width
|| (position.y + current.top) >= root_size.height)
return; return;
image.at(position.x, position.y) = color; if(!invert)
image.at(position.x + current.left, position.y + current.top) =
color;
else
image.at(position.x + current.left, position.y + current.top) ^=
color;
} }
void RenderTarget::DrawLine(Vector const& start, void RenderTarget::DrawLine(Vector const& start,
@@ -77,12 +87,16 @@ namespace frame::render
void RenderTarget::DrawCircle(Vector const& center, void RenderTarget::DrawCircle(Vector const& center,
int32_t radius, int32_t radius,
int32_t line,
Color color) Color color)
{ {
int32_t const r_2 = radius * radius; int32_t const r_2 = radius * radius;
Vector pos{0, -radius}; Vector pos{0, -radius};
DrawPointsMirrorCircle(center, pos, color); for(auto i = 0; i < line; ++i)
{
DrawPointsMirrorCircle(center, {pos.x, pos.y + i}, color);
}
while(pos.x <= radius / std::sqrt(2.f)) while(pos.x <= radius / std::sqrt(2.f))
{ {
@@ -102,7 +116,10 @@ namespace frame::render
E += 2 * pos.x - 2 * pos.y + 5; E += 2 * pos.x - 2 * pos.y + 5;
} }
DrawPointsMirrorCircle(center, pos, color); for(auto i = 0; i < line; ++i)
{
DrawPointsMirrorCircle(center, {pos.x, pos.y + i}, color);
}
} }
} }
@@ -142,6 +159,146 @@ namespace frame::render
{BottomRight.x, BottomRight.y - lw}); {BottomRight.x, BottomRight.y - lw});
} }
void
RenderTarget::DrawRect(Rect const& rect, uint8_t LineWidth, Color color)
{
DrawRect(Vector{rect.left, rect.top},
Vector{rect.left + rect.width, rect.top + rect.height},
LineWidth,
color);
}
void RenderTarget::DrawImage(Vector topLeft, Image const& image)
{
auto pos = topLeft;
for(auto y = 0; y < image.getHeight(); ++y)
{
for(auto x = 0; x < image.getWidth(); ++x)
{
DrawPixel({pos.x + x, pos.y + y}, (Color)image.at(x, y));
}
}
}
Vector RenderTarget::DrawGlyph(Vector centerLine,
font::Glyph const& g,
Color color)
{
auto const& height = g.height;
Vector topleft = centerLine;
topleft.x += g.x_offset;
topleft.y += g.y_offset;
DrawImage(topleft, g.image);
centerLine.x += g.advance;
return centerLine;
}
void RenderTarget::DrawText(std::string_view pText,
Vector pCenterLineStart,
font::Font const& pFont,
uint32_t size)
{
auto pos = pCenterLineStart;
for(auto c : pText)
{
pos = DrawGlyph(pos, pFont.getGlyph(size, c));
}
}
void RenderTarget::DrawText(Rect rect,
std::string pText,
font::Font const& pFont,
uint32_t size,
AlignHorizontal alignHorizontal,
AlignVertical alignVertical)
{
auto const lineSpacing = pFont.getLineSpacing(size);
auto const lineSpacing_3 = lineSpacing / 3;
Vector start{rect.left, rect.top};
auto text_height = pFont.getHeight(pText, size);
auto length = pFont.getLength(pText, size);
auto y_offset = pFont.getMaxYOffset(pText, size);
switch(alignVertical)
{
case AlignVertical::TOP: start.y -= y_offset; break;
case AlignVertical::CENTER:
start.y += rect.height / 2;
start.y += text_height / 2;
start.y -= y_offset + text_height;
break;
case AlignVertical::BOTTOM:
start.y += rect.height;
start.y -= text_height + y_offset;
break;
}
switch(alignHorizontal)
{
case AlignHorizontal::CENTER:
start.x += rect.width / 2;
start.x -= length / 2;
break;
case AlignHorizontal::RIGHT: start.x += rect.width - length; break;
}
DrawText(pText, start, pFont, size);
}
void RenderTarget::DrawTextGlyphBounds(std::string_view pText,
Vector pCenterLineStart,
font::Font const& pFont,
uint32_t size)
{
auto pos = pCenterLineStart;
auto lineSpacing = pFont.getLineSpacing(size);
auto _3 = lineSpacing / 3;
for(auto c : pText)
{
auto const& g = pFont.getGlyph(size, c);
DrawRect({int32_t(pos.x), int32_t(pos.y - _3 * 2)},
{int32_t(pos.x + g.advance), int32_t(pos.y + _3)},
1);
pos.x += g.advance;
}
}
void RenderTarget::Clear(Color color)
{
image.Clear(color);
}
Rect RenderTarget::getCurrentSize() const
{
if(Scissor.size() == 0)
{
return root_size;
}
return Scissor.top();
}
void RenderTarget::pushViewport(Rect rect)
{
auto current = getCurrentSize();
Scissor.push({current.top + rect.top,
current.left + rect.left,
rect.width,
rect.height});
}
void RenderTarget::popViewport()
{
Scissor.pop();
}
void RenderTarget::DrawPointsMirrorCircle(Vector const& center, void RenderTarget::DrawPointsMirrorCircle(Vector const& center,
Vector const& pos, Vector const& pos,
Color color) Color color)

View File

@@ -2,7 +2,17 @@
#include "../Color.hpp" #include "../Color.hpp"
#include "../Image.hpp" #include "../Image.hpp"
#include "../Rect.hpp"
#include "../Vector.hpp" #include "../Vector.hpp"
#include "../font/Font.hpp"
#include <stack>
namespace frame
{
enum class AlignVertical { TOP, CENTER, BOTTOM };
enum class AlignHorizontal { LEFT, CENTER, RIGHT };
} // namespace frame
namespace frame::render namespace frame::render
{ {
@@ -11,6 +21,11 @@ namespace frame::render
{ {
Image image; Image image;
Rect root_size;
std::stack<Rect> Scissor;
bool invert = false;
public: public:
RenderTarget(Size size); RenderTarget(Size size);
@@ -22,19 +37,63 @@ namespace frame::render
void DrawCircle(Vector const& center, void DrawCircle(Vector const& center,
int32_t radius, int32_t radius,
int32_t line,
Color color = BLACK); Color color = BLACK);
void DrawRectFilled(Vector const& TopLeft, Vector const& BottomRight, Color color = BLACK); void DrawRectFilled(Vector const& TopLeft,
Vector const& BottomRight,
Color color = BLACK);
void DrawRect(Vector const& TopLeft, Vector const& BottomRight, uint8_t LineWidth, Color color = BLACK); void DrawRect(Vector const& TopLeft,
Vector const& BottomRight,
uint8_t LineWidth,
Color color = BLACK);
void DrawRect(Rect const& rect, uint8_t LineWidth, Color color = BLACK);
void DrawImage(Vector topLeft, Image const& image);
Vector DrawGlyph(Vector centerLine,
font::Glyph const& g,
Color color = BLACK);
void DrawText(std::string_view pText,
Vector pCenterLineStart,
font::Font const& pFont,
uint32_t size);
void DrawText(Rect rect,
std::string pText,
font::Font const& pFont,
uint32_t size,
AlignHorizontal alignHorizontal = AlignHorizontal::LEFT,
AlignVertical alignVertical = AlignVertical::TOP);
void DrawTextGlyphBounds(std::string_view pText,
Vector pCenterLineStart,
font::Font const& pFont,
uint32_t size);
void Clear(Color color = WHITE);
Image const& getImage() Image const& getImage()
{ {
return image; return image;
} }
private:
void DrawPointsMirrorCircle(Vector const& center, Vector const& pos, Color color);
Rect getCurrentSize() const;
void pushViewport(Rect rect);
void popViewport();
void setInvert(bool pInvert)
{
invert = pInvert;
}
private:
void DrawPointsMirrorCircle(Vector const& center,
Vector const& pos,
Color color);
}; };
} // namespace frame::render } // namespace frame::render

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

@@ -0,0 +1,56 @@
#include "Widget.hpp"
namespace frame::widgets
{
Widget::week_ptr Widget::getParent() const
{
return mParent;
}
void Widget::setParent(week_ptr ptr)
{
mParent = ptr;
}
bool Widget::isDirty() const
{
return mDirty;
}
void Widget::setSize(Vector size)
{
mSize = size;
ComputeChildSize(size);
}
Vector const& Widget::getSize() const
{
return mSize;
}
void Widget::setPosition(Vector size)
{
mRelativePosition = size;
}
Vector Widget::getPosition() const
{
return mRelativePosition;
}
void Widget::setDirty()
{
mDirty = true;
if(mParent)
{
mParent->setDirty();
}
}
void Widget::setClear()
{
mDirty = false;
}
} // namespace frame::widgets

View File

@@ -0,0 +1,44 @@
#pragma once
#include "../Rect.hpp"
#include "../render/RenderTarget.hpp"
#include <memory>
namespace frame::widgets
{
class Widget
{
public:
using shared_ptr = std::shared_ptr<Widget>;
using week_ptr = Widget*;
public:
virtual ~Widget() = default;
virtual void Update() = 0;
virtual void Render(render::RenderTarget& rt) = 0;
virtual void ComputeChildSize(Vector size){};
week_ptr getParent() const;
void setParent(week_ptr ptr);
void setSize(Vector size);
Vector const& getSize() const;
void setPosition(Vector size);
Vector getPosition() const;
bool isDirty() const;
void setDirty();
void setClear();
private:
week_ptr mParent = nullptr;
bool mDirty = true;
Vector mRelativePosition;
Vector mSize;
};
} // namespace frame::widgets

View File

@@ -0,0 +1,43 @@
#include "WidgetRegistry.hpp"
#include "ContainerWidget.hpp"
namespace frame::widgets
{
WidgetRegistry::WidgetRegistry()
: IService("WidgetRegistry")
{
}
Widget::shared_ptr WidgetRegistry::Create(std::string_view name) const
{
auto it = mWidgets.find(std::string{name});
if(it == mWidgets.end())
{
fmt::print("[ERROR] Widget {} not found!\n", name);
return nullptr;
}
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

View File

@@ -0,0 +1,59 @@
#pragma once
#include "../Config.hpp"
#include "../ServiceLocator.hpp"
#include "Widget.hpp"
#include <fmt/format.h>
#include <functional>
#include <string>
#include <unordered_map>
#define REGISTER_WIDGET(TYPE, NAME) \
static bool registered = \
frame::Service::get<frame::widgets::WidgetRegistry>()->Register<TYPE>( \
NAME)
namespace frame::widgets
{
class WidgetRegistry : public IService
{
using Creator = std::function<Widget::shared_ptr()>;
std::unordered_map<std::string, Creator> mWidgets;
public:
WidgetRegistry();
template<class WIDGET>
bool Register(std::string_view name);
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>
bool WidgetRegistry::Register(std::string_view name)
{
auto const name_string = std::string{name};
if(mWidgets.find(name_string) != mWidgets.end())
{
fmt::print("Widget {} already registered!\n", name);
return true;
}
mWidgets[name_string] = []() -> Widget::shared_ptr {
return WIDGET::Create();
};
fmt::print("Widget {} registered!\n", name);
return true;
}
} // namespace frame::widgets

View File

@@ -0,0 +1,180 @@
#include "Analog.hpp"
#include "../../font/FontRegistry.hpp"
#include <ctime>
#include <fmt/chrono.h>
#include <fmt/format.h>
constexpr double pi = 3.14159;
#include "../WidgetRegistry.hpp"
REGISTER_WIDGET(frame::widgets::AnalogClock, "AnalogClock");
namespace frame::widgets
{
Widget::shared_ptr AnalogClock::Create()
{
return std::make_shared<AnalogClock>();
}
AnalogClock::AnalogClock()
: ContainerWidget(2)
{
}
void AnalogClock::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;
}
UpdateWidgets();
}
void AnalogClock::Render(render::RenderTarget& rt)
{
auto const size = rt.getCurrentSize();
auto const min = std::min(size.height, size.width);
auto const radius = (min / 2) - 10;
auto const center = Vector{size.width / 2, size.height / 2};
rt.DrawCircle(center, radius, 4);
rt.DrawCircle(center, 6, 4);
auto step = 360 / 60;
for(int i = 0; i < 60; ++i)
{
double const x = std::cos(step * i * pi / 180.f);
double const y = std::sin(step * i * pi / 180.f);
auto lineLength = 10;
if(i % 5 == 0)
{
lineLength = 20;
if(i % 3 == 0)
{
lineLength = 50;
}
}
auto const fromCenter = radius - lineLength;
rt.DrawLine({int32_t(x * (radius - 1) + center.x),
int32_t(y * (radius - 1) + center.y)},
{int32_t(x * fromCenter + center.x),
int32_t(y * fromCenter + center.y)});
}
// pls c++ 20
std::time_t t = std::chrono::system_clock::to_time_t(last_time);
auto c_time = std::localtime(&t);
// Render Pointer Minute
auto const min_steps = 360 / 60;
auto const min_rad = (min_steps * c_time->tm_min - 90) * pi / 180.f;
rt.DrawLine(center,
{center.x + int32_t(std::cos(min_rad) * radius * 0.8),
center.y + int32_t(std::sin(min_rad) * radius * 0.8)});
// Render Pointer Hour
auto const hour_steps = 360 / 12;
auto hour_deg = (hour_steps * c_time->tm_hour - 90);
auto const normalied_min = (c_time->tm_min / 59.0);
hour_deg += int32_t(normalied_min * hour_steps);
auto const hour_rad = hour_deg * pi / 180.f;
rt.DrawLine(center,
{center.x + int32_t(std::cos(hour_rad) * radius * 0.6),
center.y + int32_t(std::sin(hour_rad) * radius * 0.6)});
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
/*auto font = font::GetFont("Fira Code");
if(!font)
return;
auto text = fmt::format("{:%d.%m.%Y}", fmt::localtime(t));
auto text_center = center;
text_center.y += radius / 2;
auto const text_height_h = radius / 4;
auto const text_width_h = radius / 2;
Rect text_box{text_center.y - text_height_h,
text_center.x - text_width_h,
text_width_h * 2,
text_height_h * 2};
auto font_size = font->getOptimalSize(text, text_box);
rt.setInvert(true);
rt.DrawText(text_box,
text,
*font,
font_size,
AlignHorizontal::CENTER,
AlignVertical::CENTER);
rt.setInvert(false);*/
}
} // namespace frame::widgets

View File

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

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

View File

@@ -1,7 +1,7 @@
{ {
"name": "frame", "name": "frame",
"version": "1.0", "version": "1.0",
"dependencies": ["fmt"], "dependencies": ["fmt", "nlohmann-json"],
"features": { "features": {
"virtual": { "virtual": {
"description": "Build Virtual Display", "description": "Build Virtual Display",