From 243bf41e903ed1e90ce27a39ead19296728391af Mon Sep 17 00:00:00 2001 From: Simon Hardt Date: Sun, 6 Mar 2022 03:04:32 +0100 Subject: [PATCH] add: Simple widget system --- frame/CMakeLists.txt | 23 +++++++ frame/src/ScreenManager.cpp | 64 +++++++++++++++++++ frame/src/ScreenManager.hpp | 36 +++++++++++ frame/src/display/IDisplay.hpp | 12 ++++ frame/src/display/VirtualDisplay.cpp | 4 ++ frame/src/main.cpp | 23 ++++++- frame/src/render/RenderTarget.cpp | 50 ++++++++++++--- frame/src/render/RenderTarget.hpp | 10 +++ frame/src/widgets/Widget.cpp | 56 +++++++++++++++++ frame/src/widgets/Widget.hpp | 44 +++++++++++++ frame/src/widgets/clock/Analog.cpp | 94 ++++++++++++++++++++++++++++ frame/src/widgets/clock/Analog.hpp | 22 +++++++ frame/src/widgets/clock/Digital.cpp | 0 frame/src/widgets/clock/Digital.hpp | 0 14 files changed, 430 insertions(+), 8 deletions(-) create mode 100644 frame/src/ScreenManager.cpp create mode 100644 frame/src/ScreenManager.hpp create mode 100644 frame/src/widgets/Widget.cpp create mode 100644 frame/src/widgets/Widget.hpp create mode 100644 frame/src/widgets/clock/Analog.cpp create mode 100644 frame/src/widgets/clock/Analog.hpp create mode 100644 frame/src/widgets/clock/Digital.cpp create mode 100644 frame/src/widgets/clock/Digital.hpp diff --git a/frame/CMakeLists.txt b/frame/CMakeLists.txt index ef15bf6..a5eb74c 100644 --- a/frame/CMakeLists.txt +++ b/frame/CMakeLists.txt @@ -8,17 +8,40 @@ configure_file ( set(src src/main.cpp + + src/Size.hpp + src/Vector.hpp + src/Rect.hpp + src/Image.hpp src/Image.cpp + + src/ScreenManager.hpp + src/ScreenManager.cpp + src/display/IDisplay.hpp src/display/Display.hpp src/display/Display.cpp + src/render/RenderTarget.hpp src/render/RenderTarget.cpp + src/font/Font.hpp src/font/Font.cpp src/font/Glyph.hpp src/font/Glyph.cpp + + src/widgets/Widget.hpp + src/widgets/Widget.cpp + src/widgets/clock/Digital.hpp + src/widgets/clock/Digital.cpp + src/widgets/clock/Analog.hpp + src/widgets/clock/Analog.cpp + + + + + ) if (BUILD_EPD) diff --git a/frame/src/ScreenManager.cpp b/frame/src/ScreenManager.cpp new file mode 100644 index 0000000..833eabc --- /dev/null +++ b/frame/src/ScreenManager.cpp @@ -0,0 +1,64 @@ +#include "ScreenManager.hpp" + +#include + +namespace frame +{ + + ScreenManager::ScreenManager(std::unique_ptr 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->Render(mRenderTarget); + if(mDisplay) + { + 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 diff --git a/frame/src/ScreenManager.hpp b/frame/src/ScreenManager.hpp new file mode 100644 index 0000000..6274482 --- /dev/null +++ b/frame/src/ScreenManager.hpp @@ -0,0 +1,36 @@ +#pragma once +#include "display/IDisplay.hpp" +#include "render/RenderTarget.hpp" +#include "widgets/Widget.hpp" + +#include +#include + +using namespace std::chrono_literals; + +namespace frame +{ + + class ScreenManager + { + std::unique_ptr mDisplay; + + render::RenderTarget mRenderTarget; + widgets::Widget::shared_ptr mRoot; + + std::chrono::duration mUpdateInterval = 0.5s; + + bool mIsRunning = true; + + public: + ScreenManager(std::unique_ptr pDisplay); + + void Update(); + void Render(); + + void MainLoop(); + + void setRoot(widgets::Widget::shared_ptr widget); + }; + +} // namespace frame diff --git a/frame/src/display/IDisplay.hpp b/frame/src/display/IDisplay.hpp index 36e19bf..5571db5 100644 --- a/frame/src/display/IDisplay.hpp +++ b/frame/src/display/IDisplay.hpp @@ -12,6 +12,8 @@ namespace frame::display std::string mName; Size mSize; + bool mIsOpen = true; + public: IDisplay(std::string_view pName, Size pSize) : mName(pName) @@ -33,5 +35,15 @@ namespace frame::display { return mSize; } + + bool isOpen() const + { + return mIsOpen; + } + + void Close() + { + mIsOpen = false; + } }; } // namespace frame::display diff --git a/frame/src/display/VirtualDisplay.cpp b/frame/src/display/VirtualDisplay.cpp index 0d2758e..26bde25 100644 --- a/frame/src/display/VirtualDisplay.cpp +++ b/frame/src/display/VirtualDisplay.cpp @@ -55,6 +55,10 @@ namespace frame::display sf::Event ev; while(window.pollEvent(ev)) { + if(ev.type == sf::Event::Closed) + { + Close(); + } } } diff --git a/frame/src/main.cpp b/frame/src/main.cpp index 9f573cd..9065f03 100644 --- a/frame/src/main.cpp +++ b/frame/src/main.cpp @@ -6,10 +6,31 @@ using namespace std::chrono_literals; +#include "ScreenManager.hpp" #include "display/Display.hpp" #include "font/Font.hpp" #include "render/RenderTarget.hpp" +#include "widgets/clock/Analog.hpp" +int main() +{ + auto display = frame::display::Create(); + + if(!display) + { + return 0; + } + + display->Init(); + + frame::ScreenManager screen(std::move(display)); + + screen.setRoot(frame::widgets::AnalogClock::Create()); + + screen.MainLoop(); +} + +/* int main() { constexpr double pi = 3.14159; @@ -167,7 +188,7 @@ int main() display->Clear(frame::display::Color::WHITE); } - +*/ /* #include diff --git a/frame/src/render/RenderTarget.cpp b/frame/src/render/RenderTarget.cpp index 057b29f..d4406eb 100644 --- a/frame/src/render/RenderTarget.cpp +++ b/frame/src/render/RenderTarget.cpp @@ -7,15 +7,20 @@ namespace frame::render RenderTarget::RenderTarget(Size size) : image(size) + , root_size{0, 0, (int)size.width, (int)size.height} { } void RenderTarget::DrawPixel(Vector const& position, Color color) { - if(position.x < 0 || position.y < 0 || position.x >= image.getWidth() - || position.y >= image.getHeight()) + auto current = getCurrentSize(); + + 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; - image.at(position.x, position.y) = color; + image.at(position.x + current.left, position.y + current.top) = color; } void RenderTarget::DrawLine(Vector const& start, @@ -77,12 +82,16 @@ namespace frame::render void RenderTarget::DrawCircle(Vector const& center, int32_t radius, + int32_t line, Color color) { int32_t const r_2 = radius * 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)) { @@ -102,7 +111,10 @@ namespace frame::render 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); + } } } @@ -199,7 +211,7 @@ namespace frame::render switch(alignVertical) { - case AlignVertical::TOP: start.y += lineSpacing_3 * 2; break; + case AlignVertical::TOP: start.y -= y_offset; break; case AlignVertical::CENTER: start.y += rect.height / 2; start.y += text_height / 2; @@ -207,7 +219,7 @@ namespace frame::render break; case AlignVertical::BOTTOM: start.y += rect.height; - start.y += text_height + y_offset; + start.y -= text_height + y_offset; break; } @@ -249,6 +261,30 @@ namespace frame::render 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, Vector const& pos, Color color) diff --git a/frame/src/render/RenderTarget.hpp b/frame/src/render/RenderTarget.hpp index 91713a2..40a8112 100644 --- a/frame/src/render/RenderTarget.hpp +++ b/frame/src/render/RenderTarget.hpp @@ -6,6 +6,8 @@ #include "../Vector.hpp" #include "../font/Font.hpp" +#include + namespace frame { enum class AlignVertical { TOP, CENTER, BOTTOM }; @@ -19,6 +21,9 @@ namespace frame::render { Image image; + Rect root_size; + std::stack Scissor; + public: RenderTarget(Size size); @@ -30,6 +35,7 @@ namespace frame::render void DrawCircle(Vector const& center, int32_t radius, + int32_t line, Color color = BLACK); void DrawRectFilled(Vector const& TopLeft, @@ -71,6 +77,10 @@ namespace frame::render return image; } + Rect getCurrentSize() const; + void pushViewport(Rect rect); + void popViewport(); + private: void DrawPointsMirrorCircle(Vector const& center, Vector const& pos, diff --git a/frame/src/widgets/Widget.cpp b/frame/src/widgets/Widget.cpp new file mode 100644 index 0000000..b6f7357 --- /dev/null +++ b/frame/src/widgets/Widget.cpp @@ -0,0 +1,56 @@ +#include "Widget.hpp" + +namespace frame::widgets +{ + + Widget::shared_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 \ No newline at end of file diff --git a/frame/src/widgets/Widget.hpp b/frame/src/widgets/Widget.hpp new file mode 100644 index 0000000..1bea7f1 --- /dev/null +++ b/frame/src/widgets/Widget.hpp @@ -0,0 +1,44 @@ +#pragma once +#include "../Rect.hpp" +#include "../render/RenderTarget.hpp" + +#include + +namespace frame::widgets +{ + + class Widget + { + public: + using shared_ptr = std::shared_ptr; + using week_ptr = std::shared_ptr; + + public: + virtual ~Widget() = default; + + virtual void Update() = 0; + virtual void Render(render::RenderTarget& rt) = 0; + virtual void ComputeChildSize(Vector size){}; + + shared_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; + bool mDirty = true; + + Vector mRelativePosition; + Vector mSize; + }; + +} // namespace frame::widgets diff --git a/frame/src/widgets/clock/Analog.cpp b/frame/src/widgets/clock/Analog.cpp new file mode 100644 index 0000000..946b119 --- /dev/null +++ b/frame/src/widgets/clock/Analog.cpp @@ -0,0 +1,94 @@ +#include "Analog.hpp" + +#include +#include + +constexpr double pi = 3.14159; + +namespace frame::widgets +{ + + Widget::shared_ptr AnalogClock::Create() + { + return std::make_shared(); + } + + void AnalogClock::Update() + { + auto const now = std::chrono::system_clock::now(); + auto const min = std::chrono::floor(now); + + if(last_time != min) + { + setDirty(); + last_time = min; + } + } + + 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), + center.y + int32_t(std::sin(min_rad) * radius)}); + + // 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(); + } + +} // namespace frame::widgets \ No newline at end of file diff --git a/frame/src/widgets/clock/Analog.hpp b/frame/src/widgets/clock/Analog.hpp new file mode 100644 index 0000000..e8af0b3 --- /dev/null +++ b/frame/src/widgets/clock/Analog.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "../Widget.hpp" + +#include + +namespace frame::widgets +{ + + class AnalogClock : public Widget + { + using Time = std::chrono::time_point; + + Time last_time; + + public: + static Widget::shared_ptr Create(); + + void Update() override; + void Render(render::RenderTarget& rt) override; + }; +} // namespace frame::widgets \ No newline at end of file diff --git a/frame/src/widgets/clock/Digital.cpp b/frame/src/widgets/clock/Digital.cpp new file mode 100644 index 0000000..e69de29 diff --git a/frame/src/widgets/clock/Digital.hpp b/frame/src/widgets/clock/Digital.hpp new file mode 100644 index 0000000..e69de29