display

Layout and Rendering TUI library
git clone git://git.dimitrijedobrota.com/display.git
Log | Files | Refs | README | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING |

commit1e00242536889538bf5d6cb1b1ba8490603481cf
parent17bfa4acf63943274ef2fd63879394793aa2a55c
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateThu, 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

Diffstat:
MCMakeLists.txt|+-
Mexample/example.cpp|++++++++++++++++++++++++++++++++++++--------------------------------
Minclude/display/layout.hpp|+-
Minclude/display/layout_free.hpp|+-
Minclude/display/layout_rigid.hpp|++++++++++++++++++++---------
Minclude/display/screen.hpp|+++---
Msource/layout_free.cpp|+++---
Msource/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