displayLayout and Rendering TUI library | 
          
| git clone git://git.dimitrijedobrota.com/display.git | 
| Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | 
| commit | 1e00242536889538bf5d6cb1b1ba8490603481cf | 
| parent | 17bfa4acf63943274ef2fd63879394793aa2a55c | 
| author | Dimitrije Dobrota < mail@dimitrijedobrota.com > | 
| date | Thu, 13 Feb 2025 11:51:31 +0100 | 
Proper rigid layout with a drawing matrix
* Fix widget rendering since coordinates start at 1
* Strong input validity checking
| M | CMakeLists.txt | | | + - | 
| M | example/example.cpp | | | ++++++++++++++++++++++++++++++++++++ -------------------------------- | 
| M | include/display/layout.hpp | | | + - | 
| M | include/display/layout_free.hpp | | | + - | 
| M | include/display/layout_rigid.hpp | | | ++++++++++++++++++++ --------- | 
| M | include/display/screen.hpp | | | +++ --- | 
| M | source/layout_free.cpp | | | +++ --- | 
| M | source/layout_rigid.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ------- | 
8 files changed, 165 insertions(+), 58 deletions(-)
diff --git a/ CMakeLists.txt b/ CMakeLists.txt
          @@ -4,7 +4,7 @@ 
          include(cmake/prelude.cmake)
        
        
          project(
              display
              VERSION 0.1.17
              VERSION 0.1.18
              DESCRIPTION "TUI library"
              HOMEPAGE_URL "https://example.com/"
              LANGUAGES CXX
        
        diff --git a/ example/example.cpp b/ example/example.cpp
@@ -1,4 +1,3 @@
#include <functional>
          #include <iostream>
          #include <string>
          
          @@ -21,9 +20,9 @@ 
          void renderer(const display::WindowPivot& win, display::place_t plc)
        
        
            const auto [start, end] = plc;
            std::cout << alec::background(color_red, 65, 65);
            for (auto ypos = start.y; ypos <= end.y; ypos++) {
              std::cout << alec::cursor_position(ypos, start.x);
              std::cout << std::string(end.x - start.x + 1U, ' ');
            for (auto ypos = start.y; ypos < end.y; ypos++) {
              std::cout << alec::cursor_position(ypos + 1, start.x + 1);
              std::cout << std::string(end.x - start.x, ' ');
            }
            std::cout << alec::background_v<alec::Color::DEFAULT>;
            std::cout << std::flush;
        
        
          @@ -38,15 +37,15 @@ 
          void fill(display::LayoutFree& layout)
        
        
            using display::WindowPivot;
            // clang-format off
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Left, PvtY::Top));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Center, PvtY::Top));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Right, PvtY::Top));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Right, PvtY::Center));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Right, PvtY::Bottom));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Center, PvtY::Bottom));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Left, PvtY::Bottom));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Left, PvtY::Center));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Center, PvtY::Center));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(12, 4), piv_t(PvtX::Left, PvtY::Top));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(12, 4), piv_t(PvtX::Center, PvtY::Top));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(12, 4), piv_t(PvtX::Right, PvtY::Top));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(12, 4), piv_t(PvtX::Right, PvtY::Center));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(12, 4), piv_t(PvtX::Right, PvtY::Bottom));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(12, 4), piv_t(PvtX::Center, PvtY::Bottom));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(12, 4), piv_t(PvtX::Left, PvtY::Bottom));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(12, 4), piv_t(PvtX::Left, PvtY::Center));
            layout.append<WindowPivot>(renderer, pos_t(), dim_t(12, 4), piv_t(PvtX::Center, PvtY::Center));
            // clang-format on
          }
          
          @@ -58,34 +57,39 @@ 
          int main()
        
        
              using namespace std::placeholders;  // NOLINT
              using namespace display;  // NOLINT
              auto& display = Display::display();
              const auto recalc = [](std::size_t start, LayoutFree& layout)
              const auto recalc = [](LayoutFree& layout)
              {
                const auto [width, height] = layout.dim();
                const display::sz_t midw = width / 2;
                const display::sz_t midh = height / 2;
                layout.get<WindowPivot>((start + 0) % 8).pos() = {0, 0};
                layout.get<WindowPivot>((start + 1) % 8).pos() = {midw, 0};
                layout.get<WindowPivot>((start + 2) % 8).pos() = {width, 0};
                layout.get<WindowPivot>((start + 3) % 8).pos() = {width, midh};
                layout.get<WindowPivot>((start + 4) % 8).pos() = {width, height};
                layout.get<WindowPivot>((start + 5) % 8).pos() = {midw, height};
                layout.get<WindowPivot>((start + 6) % 8).pos() = {0, height};
                layout.get<WindowPivot>((start + 7) % 8).pos() = {0, midh};
                layout.get<WindowPivot>(0).pos() = {0, 0};
                layout.get<WindowPivot>(1).pos() = {midw, 0};
                layout.get<WindowPivot>(2).pos() = {width, 0};
                layout.get<WindowPivot>(3).pos() = {width, midh};
                layout.get<WindowPivot>(4).pos() = {width, height};
                layout.get<WindowPivot>(5).pos() = {midw, height};
                layout.get<WindowPivot>(6).pos() = {0, height};
                layout.get<WindowPivot>(7).pos() = {0, midh};
                layout.get<WindowPivot>(8).pos() = {midw, midh};
              };
              auto& layout = display.screen().set_layout<LayoutRigid>(nullptr);
              auto& layout1 =
                  layout.screen1().set_layout<LayoutFree>(std::bind(recalc, 4U, _1));
              fill(layout1);
              auto& display = Display::display();
              auto& layout2 =
                  layout.screen2().set_layout<LayoutFree>(std::bind(recalc, 0U, _1));
              fill(layout2);
              // clang-format off
              const LayoutRigid::layout_t split = {
                  {1, 1, 2},
                  {0, 3, 2},
                  {4, 3, 2},
              };
              // clang-format on
              auto& layout = display.screen().set_layout<LayoutRigid>(split, nullptr);
              fill(layout[0].set_layout<LayoutFree>(recalc));
              fill(layout[1].set_layout<LayoutFree>(recalc));
              fill(layout[2].set_layout<LayoutFree>(recalc));
              fill(layout[3].set_layout<LayoutFree>(recalc));
              fill(layout[4].set_layout<LayoutFree>(recalc));
              for (display.set_resized(); true;) {
                const auto evnt = display.get_event();
        
        diff --git a/ include/display/layout.hpp b/ include/display/layout.hpp
          @@ -22,7 +22,7 @@ 
          public:
        
        
            dim_t& dim() { return m_dim; }
            virtual void resize(dim_t dim) = 0;
            virtual int render(pos_t pos) const = 0;
            virtual void render(pos_t pos) const = 0;
          private:
            dim_t m_dim;
        
        diff --git a/ include/display/layout_free.hpp b/ include/display/layout_free.hpp
          @@ -44,7 +44,7 @@ 
          public:
        
        
            }
            void resize(dim_t dim) override;
            int render(pos_t pos) const override;
            void render(pos_t pos) const override;
          private:
            recalc_f m_recalc;
        
        diff --git a/ include/display/layout_rigid.hpp b/ include/display/layout_rigid.hpp
@@ -1,6 +1,7 @@
#pragma once
          #include <functional>
          #include <vector>
          #include "display/screen.hpp"
          #include "display/types.hpp"
        
        
          @@ -12,23 +13,33 @@ 
          class LayoutRigid : public Layout
        
        
          {
          public:
            using recalc_f = std::function<void(LayoutRigid&)>;
            using layout_t = std::vector<std::vector<std::uint8_t>>;
            LayoutRigid(recalc_f f_recalc)  // NOLINT
                : m_recalc(std::move(f_recalc))
            {
            }
            LayoutRigid(layout_t layout, recalc_f f_recalc);
            auto& screen1() { return m_screen1; }
            auto& screen2() { return m_screen2; }
            const auto& operator[](std::size_t idx) const { return m_recs[idx].screen; }
            auto& operator[](std::size_t idx) { return m_recs[idx].screen; }
            void resize(dim_t dim) override;
            int render(pos_t pos) const override;
            void render(pos_t pos) const override;
          private:
            auto calc_width(dim_t share) const;
            auto calc_height(dim_t share) const;
            std::size_t count_and_pad(layout_t& layout) const;
            recalc_f m_recalc;
            dim_t m_grid;
            struct record_t
            {
              Screen screen;
              dim_t start = {0xFFFF, 0xFFFF};
              dim_t dim;
            };
            Screen m_screen1;
            Screen m_screen2;
            std::vector<record_t> m_recs;
          };
          }  // namespace display
        
        diff --git a/ include/display/screen.hpp b/ include/display/screen.hpp
          @@ -17,10 +17,10 @@ 
          public:
        
        
            }
            Screen(const Screen&) = delete;
            Screen(Screen&&) = delete;
            Screen& operator=(const Screen&) = delete;
            Screen& operator=(Screen&&) = delete;
            Screen(Screen&&) = default;
            Screen& operator=(Screen&&) = default;
            ~Screen() = default;
          diff --git a/ source/layout_free.cpp b/ source/layout_free.cpp
@@ -2,6 +2,7 @@
namespace display
          {
          void LayoutFree::resize(dim_t dim)
          {
            this->dim() = dim;
        
        
          @@ -11,7 +12,7 @@ 
          void LayoutFree::resize(dim_t dim)
        
        
            }
          }
          int LayoutFree::render(pos_t pos) const
          void LayoutFree::render(pos_t pos) const
          {
            for (const auto& win : m_wins) {
              const auto plc = win->place(this->dim());
        
        
          @@ -22,7 +23,6 @@ 
          int LayoutFree::render(pos_t pos) const
        
        
              win->render(plc.value() + pos);
            }
            return 0;
          }
          }  // namespace display
        
        diff --git a/ source/layout_rigid.cpp b/ source/layout_rigid.cpp
@@ -1,27 +1,119 @@
#include <stdexcept>
          #include <unordered_set>
          #include "display/layout_rigid.hpp"
          namespace display
          {
          LayoutRigid::LayoutRigid(layout_t layout, recalc_f f_recalc)
              : m_recalc(std::move(f_recalc))
              , m_grid(static_cast<sz_t>(layout[0].size()),
                       static_cast<sz_t>(layout.size()))
              , m_recs(count_and_pad(layout))
          {
            static const auto& insert =
                [](sz_t& count, std::uint8_t cnt, sz_t& pos, std::uint8_t total)
            {
              if (count != 0 && (pos != total || count != cnt)) {
                throw std::runtime_error("Invalid layout [Shape]");
              }
              if (count != 0) {
                return;
              }
              pos = total;
              count = cnt;
            };
            for (std::size_t i = 0U; i < m_grid.height; i++) {
              uint8_t total = 0;
              uint8_t cnt = 1;
              for (std::size_t j = 0U; j < m_grid.width; j++, cnt++) {
                const auto crnt = layout[i][j];
                if (crnt == layout[i][j + 1]) {
                  continue;
                }
                insert(m_recs[crnt].dim.width, cnt, m_recs[crnt].start.width, total);
                total += cnt, cnt = 0;
              }
            }
            for (std::size_t j = 0U; j < m_grid.width; j++) {
              uint8_t total = 0;
              uint8_t cnt = 1;
              for (std::size_t i = 0U; i < m_grid.height; i++, cnt++) {
                const auto crnt = layout[i][j];
                if (crnt == layout[i + 1][j]) {
                  continue;
                }
                insert(m_recs[crnt].dim.height, cnt, m_recs[crnt].start.height, total);
                total += cnt, cnt = 0;
              }
            }
          }
          std::size_t LayoutRigid::count_and_pad(layout_t& layout) const
          {
            std::unordered_set<std::uint8_t> ust;
            for (std::size_t i = 0U; i < m_grid.height; i++) {
              for (std::size_t j = 0U; j < m_grid.width; j++) {
                ust.insert(layout[i][j]);
              }
              layout[i].emplace_back(0xFF);
            }
            layout.emplace_back(m_grid.width, 0xFF);
            for (std::size_t i = 0U; i < m_grid.height; i++) {
              for (std::size_t j = 0U; j < m_grid.width; j++) {
                if (layout[i][j] >= ust.size()) {
                  throw std::runtime_error("Invalid layout [Number]");
                }
              }
            }
            return ust.size();
          }
          auto LayoutRigid::calc_width(dim_t share) const
          {
            return static_cast<sz_t>(dim().width / m_grid.width * share.width);
          }
          auto LayoutRigid::calc_height(dim_t share) const
          {
            return static_cast<sz_t>(dim().height / m_grid.height * share.height);
          }
          void LayoutRigid::resize(dim_t dim)
          {
            this->dim() = dim;
            const sz_t mid = this->dim().height / 2;
            m_screen1.resize({dim.width, mid});
            m_screen2.resize({dim.width, mid});
            for (auto& [screen, _, rdim] : m_recs) {
              const dim_t size = {calc_width(rdim), calc_height(rdim)};
              screen.resize(size);
            }
            if (m_recalc != nullptr) {
              m_recalc(*this);
            }
          }
          int LayoutRigid::render(pos_t pos) const
          void LayoutRigid::render(pos_t pos) const
          {
            const sz_t mid = this->dim().height / 2;
            m_screen1.render(pos + pos_t(0, 0));
            m_screen2.render(pos + pos_t(0, mid + 1));
            return 0;
            for (const auto& [screen, start, _] : m_recs) {
              const pos_t rel = {calc_width(start), calc_height(start)};
              const pos_t abs = pos + rel;
              screen.render(abs);
            }
          }
          }  // namespace display