gigaTerminal text editor | 
          
| git clone git://git.dimitrijedobrota.com/giga.git | 
| Log | Files | Refs | README | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | 
| commit | db345023355929e573412087179e5516f228d04f | 
| parent | b85d2582224dc41e9e2f35ac5084a893e9004c74 | 
| author | Dimitrije Dobrota < mail@dimitrijedobrota.com > | 
| date | Thu, 6 Mar 2025 11:31:44 +0100 | 
Rewrite LayoutDynamic using std::variant
| M | .clang-tidy | | | + | 
| M | CMakeLists.txt | | | + - | 
| M | source/layout_dynamic.cpp | | | ++++ ------------------------------------------------------------------------------ | 
| M | source/layout_dynamic.hpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --------------- | 
4 files changed, 333 insertions(+), 300 deletions(-)
diff --git a/ .clang-tidy b/ .clang-tidy
          @@ -21,6 +21,7 @@ 
          Checks: "*,\
        
        
            -modernize-use-ranges,\
            -misc-include-cleaner,\
            -misc-non-private-member-variables-in-classes,\
            -misc-no-recursion,\
            -readability-magic-numbers
          "
          WarningsAsErrors: ''
        
        diff --git a/ CMakeLists.txt b/ CMakeLists.txt
          @@ -4,7 +4,7 @@ 
          include(cmake/prelude.cmake)
        
        
          project(
              giga
              VERSION 0.1.10
              VERSION 0.1.11
              DESCRIPTION "Terminal text editor"
              HOMEPAGE_URL "https://git.dimitrijedobrota.com/giga.git"
              LANGUAGES CXX
        
        diff --git a/ source/layout_dynamic.cpp b/ source/layout_dynamic.cpp
@@ -1,5 +1,3 @@
#include <stdexcept>
          #include "layout_dynamic.hpp"
          namespace display
        
        
          @@ -11,16 +9,16 @@ 
          void LayoutDynamic::input(event& evnt)
        
        
          {
            if (evnt.type() == event::Type::KEY) {
              if (evnt.key() == 'e') {
                m_sel = m_sel->split_horizontal();
                m_sel->emplace_child();
                m_sel = m_sel->split<Type::Horizontal>();
                m_sel->layout().emplace_child();
                render();
                evnt.type() = event::Type::NONE;
                return;
              }
              if (evnt.key() == 'r') {
                m_sel = m_sel->split_vertical();
                m_sel->emplace_child();
                m_sel = m_sel->split<Type::Vertical>();
                m_sel->layout().emplace_child();
                render();
                evnt.type() = event::Type::NONE;
                return;
        
        
          @@ -36,254 +34,35 @@ 
          void LayoutDynamic::input(event& evnt)
        
        
              }
              if (evnt.key() == 'w') {
                m_sel = m_layout.select(m_sel, Panel::Direction::Up);
                m_sel = m_container.select(m_sel, Direction::Up);
                render();
                evnt.type() = event::Type::NONE;
                return;
              }
              if (evnt.key() == 'a') {
                m_sel = m_layout.select(m_sel, Panel::Direction::Left);
                m_sel = m_container.select(m_sel, Direction::Left);
                render();
                evnt.type() = event::Type::NONE;
                return;
              }
              if (evnt.key() == 's') {
                m_sel = m_layout.select(m_sel, Panel::Direction::Down);
                m_sel = m_container.select(m_sel, Direction::Down);
                render();
                evnt.type() = event::Type::NONE;
                return;
              }
              if (evnt.key() == 'd') {
                m_sel = m_layout.select(m_sel, Panel::Direction::Right);
                m_sel = m_container.select(m_sel, Direction::Right);
                render();
                evnt.type() = event::Type::NONE;
                return;
              }
            }
            m_sel->input(evnt);
          }
          void LayoutDynamic::Panel::render() const  // NOLINT
          {
            if (m_type == Type::Single) {
              Layout::render();
              return;
            }
            for (const auto& panel : m_splits) {
              panel->render();
            }
          }
          void LayoutDynamic::Panel::resize(plc_t aplc)  // NOLINT
          {
            Layout::resize(aplc);
            switch (m_type) {
              case Type::Single:
                // Already done
                break;
              case Type::Horizontal: {
                resize_horizontal(m_splits.size());
                break;
              }
              case Type::Vertical: {
                resize_vertical(m_splits.size());
                break;
              }
            }
          }
          LayoutDynamic::Panel* LayoutDynamic::Panel::split_horizontal()
          {
            Panel* panel = this;
            std::size_t idx = 1;
            if (m_parent != nullptr && m_parent->m_type == Type::Horizontal) {
              const auto itr = m_parent->find_child(this);
              const auto& splits = m_parent->m_splits;
              idx = static_cast<std::size_t>(std::distance(splits.begin(), itr) + 1);
              panel = m_parent;
            } else if (m_type == Type::Vertical) {
              throw std::runtime_error("Can't to horizontal split [Layout vertical]");
            } else if (m_type == Type::Single) {
              m_type = Type::Horizontal;
              const auto plc = plc_t(apos(), {awth() / 2, ahgt()});
              m_splits.emplace_back(std::make_unique<Panel>(this, plc, release_child()));
            }
            const auto size = panel->m_splits.size() + 1;
            panel->m_splits.emplace(
                panel->m_splits.begin() + static_cast<std::ptrdiff_t>(idx),
                std::make_unique<Panel>(panel, panel->place_horizontal(idx, size)));
            panel->resize_horizontal(size);
            return panel->m_splits[idx].get();
          }
          LayoutDynamic::Panel* LayoutDynamic::Panel::split_vertical()
          {
            Panel* panel = this;
            std::size_t idx = 1;
            if (m_parent != nullptr && m_parent->m_type == Type::Vertical) {
              const auto itr = m_parent->find_child(this);
              const auto& splits = m_parent->m_splits;
              idx = static_cast<std::size_t>(std::distance(splits.begin(), itr) + 1);
              panel = m_parent;
            } else if (m_type == Type::Vertical) {
              throw std::runtime_error("Can't to vertical split [Layout horizontal]");
            } else if (m_type == Type::Single) {
              m_type = Type::Vertical;
              const auto plc = plc_t(apos(), {awth() / 2, ahgt()});
              m_splits.emplace_back(std::make_unique<Panel>(this, plc, release_child()));
            }
            const auto size = panel->m_splits.size() + 1;
            panel->m_splits.emplace(
                panel->m_splits.begin() + static_cast<std::ptrdiff_t>(idx),
                std::make_unique<Panel>(panel, panel->place_vertical(idx, size)));
            panel->resize_vertical(size);
            return panel->m_splits[idx].get();
          }
          LayoutDynamic::Panel* LayoutDynamic::Panel::close()
          {
            if (m_type != Type::Single) {
              throw std::runtime_error("Can't close non leaf panel");
            }
            // I am the last one
            if (m_parent == nullptr) {
              return nullptr;
            }
            // I'll be deleted
            const auto pos = apos();
            auto* parent = m_parent;
            auto& splits = parent->m_splits;
            if (splits.size() != 2) {
              splits.erase(parent->find_child(this));
            } else {
              auto& base = splits[0].get() == this ? *splits[1] : *splits[0];
              parent->m_type = base.m_type;
              if (parent->m_type == Type::Single) {
                parent->set_child(base.release_child());
                parent->m_splits.clear();
              } else {
                parent->m_splits = std::move(base.m_splits);
                for (auto& panel : parent->m_splits) {
                  panel->m_parent = parent;
                }
              }
            }
            parent->resize(parent->aplc());
            return parent->select(pos);
          }
          plc_t LayoutDynamic::Panel::place_horizontal(std::size_t idx,
                                                       std::size_t size) const
          {
            if (idx + 1 == size) {
              const auto unit = (awth() / size) * (size - 1);
              const auto wth = awth() - unit;
              const auto xpos = axpos() + unit;
              return {{xpos, aypos()}, {wth, ahgt()}};
            }
            const auto wth = awth() / m_splits.size();
            const auto xpos = axpos() + wth * idx;
            return {{xpos, aypos()}, {wth, ahgt()}};
          }
          plc_t LayoutDynamic::Panel::place_vertical(std::size_t idx,
                                                     std::size_t size) const
          {
            if (idx + 1 == size) {
              const auto unit = (ahgt() / size) * (size - 1);
              const auto ypos = aypos() + unit;
              const auto hgt = ahgt() - unit;
              return {{axpos(), ypos}, {awth(), hgt}};
            }
            const auto hgt = ahgt() / m_splits.size();
            const auto ypos = aypos() + hgt * idx;
            return {{axpos(), ypos}, {awth(), hgt}};
          }
          void LayoutDynamic::Panel::resize_horizontal(std::size_t size)  // NOLINT
          {
            for (std::size_t i = 0; i < m_splits.size(); i++) {
              m_splits[i]->resize(place_horizontal(i, size));
            }
          }
          void LayoutDynamic::Panel::resize_vertical(std::size_t size)  // NOLINT
          {
            for (std::size_t i = 0; i < m_splits.size(); i++) {
              m_splits[i]->resize(place_vertical(i, size));
            }
          }
          LayoutDynamic::Panel* LayoutDynamic::Panel::select(Panel* crnt, Direction dir)
          {
            Panel* sel = nullptr;
            switch (dir) {
              case Direction::Up:
                sel = select(crnt->apos() + pos_t(0, -1));
                break;
              case Direction::Left:
                sel = select(crnt->apos() + pos_t(-1, 0));
                break;
              case Direction::Down:
                sel = select(crnt->apos() + pos_t(0UL, crnt->ahgt().value()));
                break;
              case Direction::Right:
                sel = select(crnt->apos() + pos_t(crnt->awth().value(), 0UL));
                break;
            }
            return sel != nullptr ? sel : crnt;
          }
          LayoutDynamic::Panel* LayoutDynamic::Panel::select(pos_t pos)
          {
            Panel* crnt = this;
            while (crnt->m_type != Type::Single) {
              for (const auto& panel : crnt->m_splits) {
                if (panel->apos() <= pos && pos < panel->apos() + panel->adim()) {
                  crnt = panel.get();
                  goto next;
                }
              }
              return nullptr;
            next:;
            }
            return crnt;
            m_sel->layout().input(evnt);
          }
          }  // namespace display
        
        diff --git a/ source/layout_dynamic.hpp b/ source/layout_dynamic.hpp
@@ -1,6 +1,9 @@
#pragma once
          #include <memory>
          #include <stack>
          #include <stdexcept>
          #include <variant>
          #include <vector>
          #include "display/element.hpp"
        
        
          @@ -29,116 +32,366 @@ 
          public:
        
        
            int m_num = ++counter;
          };
          class LayoutDynamic : public Element
          enum class Type : std::uint8_t
          {
            Horizontal,
            Vertical,
          };
          enum class Direction : std::uint8_t
          {
            Up,
            Left,
            Down,
            Right
          };
          class Container;
          template<Type T>
          class PanelContainer : public Element
          {
          public:
            explicit LayoutDynamic(plc_t aplc)
            explicit PanelContainer(plc_t aplc)
                : Element(aplc)
                , m_layout(aplc)
            {
            }
            bool is_finished() const { return m_sel == nullptr; }
            static constexpr Type type() { return T; }
            void render() const override
            const auto& panels() const { return m_panels; }
            auto& panels() { return m_panels; }
            plc_t place(std::size_t idx, std::size_t size) const;
            auto find_child(Container* child)
            {
              Element::clear();
              m_layout.render();
              std::cout << alec::foreground_v<alec::Color::RED>;
              m_sel->render_border();
              std::cout << alec::foreground_v<alec::Color::DEFAULT>;
              std::cout << std::flush;
              return std::find_if(m_panels.begin(),
                                  m_panels.end(),
                                  [&](const auto& ptr) { return ptr.get() == child; });
            }
            void resize(plc_t aplc) override
          private:
            std::vector<std::unique_ptr<Container>> m_panels;
          };
          using PanelHorizontal = PanelContainer<Type::Horizontal>;  // NOLINT
          using PanelVertical = PanelContainer<Type::Vertical>;  // NOLINT
          template<>
          inline plc_t PanelHorizontal::place(std::size_t idx, std::size_t size) const
          {
            if (idx + 1 == size) {
              const auto unit = (awth() / size) * (size - 1);
              const auto wth = awth() - unit;
              const auto xpos = axpos() + unit;
              return {{xpos, aypos()}, {wth, ahgt()}};
            }
            const auto wth = awth() / size;
            const auto xpos = axpos() + wth * idx;
            return {{xpos, aypos()}, {wth, ahgt()}};
          }
          template<>
          inline plc_t PanelVertical::place(std::size_t idx, std::size_t size) const
          {
            if (idx + 1 == size) {
              const auto unit = (ahgt() / size) * (size - 1);
              const auto ypos = aypos() + unit;
              const auto hgt = ahgt() - unit;
              return {{axpos(), ypos}, {awth(), hgt}};
            }
            const auto hgt = ahgt() / size;
            const auto ypos = aypos() + hgt * idx;
            return {{axpos(), ypos}, {awth(), hgt}};
          }
          class Container
          {
          public:
            using layout_t = Layout<WindowTest>;
            Container(Container* parent, plc_t aplc, layout_t::ptr_t&& child)
                : m_parent(parent)
                , m_payload(layout_t(aplc, std::move(child)))
            {
              Element::resize(aplc);
              m_layout.resize(aplc);
            }
            void input(event& evnt) override;
            explicit Container(Container* parent, plc_t aplc)
                : m_parent(parent)
                , m_payload(layout_t(aplc))
            {
            }
          private:
            class Panel : public Layout<WindowTest>
            auto& layout() { return std::get<layout_t>(m_payload); }
            void resize(plc_t aplc)
            {
            public:
              explicit Panel(plc_t aplc)
                  : Layout(aplc)
                  , m_parent(nullptr)
              {
                emplace_child();
              std::stack<std::pair<plc_t, Container*>> stk;
              stk.emplace(aplc, this);
              while (!stk.empty()) {
                auto [plc, crnt] = stk.top();
                stk.pop();
                std::visit(
                    [&]<typename M>(M& var)
                    {
                      var.resize(plc);
                      if constexpr (!std::is_same_v<M, layout_t>) {
                        auto& panels = var.panels();
                        for (std::size_t i = 0; i < panels.size(); i++) {
                          stk.emplace(var.place(i, panels.size()), panels[i].get());
                        }
                      }
                    },
                    crnt->m_payload);
              }
            }
              explicit Panel(Panel* parent, plc_t aplc)
                  : Layout(aplc)
                  , m_parent(parent)
              {
            void render() const
            {
              std::stack<const Container*> stk;
              stk.emplace(this);
              while (!stk.empty()) {
                const auto* crnt = stk.top();
                stk.pop();
                std::visit(
                    [&]<typename M>(const M& var)
                    {
                      if constexpr (std::is_same_v<M, layout_t>) {
                        var.render();
                      }
                      if constexpr (!std::is_same_v<M, layout_t>) {
                        for (const auto& panel : var.panels()) {
                          stk.emplace(panel.get());
                        }
                      }
                    },
                    crnt->m_payload);
              }
            }
              Panel(Panel* parent, plc_t aplc, Layout<WindowTest>::ptr_t&& payload)
                  : Layout(aplc, std::move(payload))
                  , m_parent(parent)
            template<Type T>
            Container* split()
            {
              const auto visit_parent = [&]<typename M>(M& pcont) -> Container*
              {
              }
                if constexpr (std::is_same_v<M, layout_t>) {
                  throw std::runtime_error("Invalid node handle [split() layout_t]");
                }
              void render() const override;
              void resize(plc_t aplc) override;
                if constexpr (!std::is_same_v<M, layout_t>) {
                  if constexpr (M::type() != T) {
                    return nullptr;
                  }
              Panel* split_horizontal();
              Panel* split_vertical();
                  auto itr = pcont.panels().emplace(
                      pcont.find_child(this) + 1,
                      std::make_unique<Container>(m_parent, plc_t({0, 0}, {0, 0})));
              Panel* close();
                  m_parent->resize(pcont.aplc());
              enum class Direction : std::uint8_t
              {
                Up,
                Left,
                Down,
                Right,
                  return itr->get();
                }
              };
              Panel* select(Panel* crnt, Direction dir);
              // can it by handled by the parent?
              if (m_parent != nullptr) {
                auto* ret = std::visit(visit_parent, m_parent->m_payload);
                if (ret != nullptr) {
                  return ret;
                }
              }
              // Nope I'll do it myself
              const auto aplc = std::get<layout_t>(m_payload).aplc();
              auto child = std::get<layout_t>(m_payload).release_child();
            private:
              using panel_ptr = std::unique_ptr<Panel>;
              auto& con = m_payload.emplace<PanelContainer<T>>(aplc);
              // var is not to be used anymore
              enum class Type : std::uint8_t
              con.panels().emplace_back(
                  std::make_unique<Container>(this, con.place(0, 2), std::move(child)));
              con.panels().emplace_back(
                  std::make_unique<Container>(this, con.place(1, 2)));
              return con.panels().back().get();
            }
            Container* close()
            {
              const auto visit_parent = [&]<typename M>(M& pcont) -> Container*
              {
                Single,
                Horizontal,
                Vertical,
                if constexpr (std::is_same_v<M, layout_t>) {
                  throw std::runtime_error("Invalid node handle [close() layout_t]");
                }
                if constexpr (!std::is_same_v<M, layout_t>) {
                  // need this here because 'this' is going to deleted
                  auto* parent = m_parent;
                  const auto pos = get_elem().apos();
                  pcont.panels().erase(pcont.find_child(this));
                  // 'this' is not to be used anymore
                  if (pcont.panels().size() > 1) {
                    parent->resize(pcont.aplc());
                    return parent->select(pos);
                  }
                  // one child left, no need for Panel anymore
                  // need this here because this is going to overridden
                  auto payload = std::move(pcont.panels()[0]->m_payload);
                  const auto aplc = pcont.aplc();
                  parent->m_payload = std::move(payload);
                  // pcnot is not to be used anymore
                  // re-parent if needed
                  std::visit(
                      [&]<typename L>(L& val)
                      {
                        if constexpr (!std::is_same_v<L, layout_t>) {
                          for (auto& panel : val.panels()) {
                            panel->m_parent = parent;
                          }
                        }
                      },
                      parent->m_payload);
                  parent->resize(aplc);
                  return parent->select(aplc.pos);
                }
              };
              plc_t place_horizontal(std::size_t idx, std::size_t size) const;
              plc_t place_vertical(std::size_t idx, std::size_t size) const;
              if (m_parent == nullptr) {
                // I am the last one
                return nullptr;
              }
              void resize_horizontal(std::size_t size);
              void resize_vertical(std::size_t size);
              return std::visit(visit_parent, m_parent->m_payload);
            }
              Panel* select(pos_t pos);
            Container* select(Container* sel, Direction dir)
            {
              const auto& sele = sel->get_elem();
              auto find_child(const Panel* panel) const
              {
                const auto itr =
                    std::find_if(m_splits.begin(),
                                 m_splits.end(),
                                 [&](auto& uptr) { return uptr.get() == panel; });
              pos_t tpos = {0, 0};
              switch (dir) {
                case Direction::Up:
                  tpos = sele.apos() + pos_t(0, -1);
                  break;
                case Direction::Left:
                  tpos = sele.apos() + pos_t(-1, 0);
                  break;
                case Direction::Down:
                  tpos = sele.apos() + pos_t(0UL, sele.ahgt().value());
                  break;
                case Direction::Right:
                  tpos = sele.apos() + pos_t(sele.awth().value(), 0UL);
                  break;
              }
              const auto& roote = get_elem();
              if (!(roote.apos() <= tpos && tpos < roote.apos() + roote.adim())) {
                return sel;
              }
              return select(tpos);
            }
          private:
            const Element& get_elem() const
            {
              return std::visit([](const auto& val) -> const Element& { return val; },
                                m_payload);
            }
            Container* select(pos_t tpos)
            {
              Container* crnt = this;
              while (true) {
                auto* next = std::visit(
                    [&]<typename M>(const M& val) -> Container*
                    {
                      if constexpr (std::is_same_v<M, layout_t>) {
                        return nullptr;
                      }
                if (itr == m_splits.end()) {
                  throw std::runtime_error("Can't find child");
                      if constexpr (!std::is_same_v<M, layout_t>) {
                        for (const auto& panel : val.panels()) {
                          const auto plc = panel->get_elem().aplc();
                          if (plc.pos <= tpos && tpos < plc.pos + plc.dim) {
                            return panel.get();
                          }
                        }
                        throw std::runtime_error("Something's wrong, I can feel it!");
                      }
                    },
                    crnt->m_payload);
                if (next == nullptr) {
                  return crnt;
                }
                return itr;
                crnt = next;
              }
            }
            using payload_t = std::variant<layout_t, PanelHorizontal, PanelVertical>;
            Container* m_parent;
            payload_t m_payload;
          };
          class LayoutDynamic : public Element
          {
          public:
            explicit LayoutDynamic(plc_t aplc)
                : Element(aplc)
                , m_container(nullptr, aplc)
            {
              m_sel->layout().emplace_child();
            }
            bool is_finished() const { return m_sel == nullptr; }
              Panel* m_parent;
              Type m_type = Type::Single;
            void render() const override
            {
              Element::clear();
              m_container.render();
              std::cout << alec::foreground_v<alec::Color::RED>;
              m_sel->layout().get_child().render_border();
              std::cout << alec::foreground_v<alec::Color::DEFAULT>;
              std::cout << std::flush;
            }
              std::vector<panel_ptr> m_splits;
            };
            void resize(plc_t aplc) override
            {
              Element::resize(aplc);
              m_container.resize(aplc);
            }
            Panel m_layout;
            Panel* m_sel = &m_layout;
            void input(event& evnt) override;
          private:
            Container m_container;
            Container* m_sel = &m_container;
          };
          }  // namespace display