display

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

commitd2fc74268ffffdbb80e5f8caeed5ff0adcaa02d1
parent39c67ae3279400b6b07d9659edcc39008f3885b3
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateTue, 11 Feb 2025 17:06:36 +0100

Reshape to fit the dynamic nature * Layout based class suitable for deriving and runtime polymorphism * Screen accepts any derivative of Layout * Window based class now suitable for deriving and runtime polymorphism * LayoutFree accepts any type of Window * Streamline resize and render interfaces on all objects * Relative positions get converted to absolute in render

Diffstat:
M.clang-tidy|+-
MCMakeLists.txt|+-
Mexample/example.cpp|+++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Minclude/display/display.hpp|++
Minclude/display/layout.hpp|+++++++++++++++++++++++++++++++++++++------------
Minclude/display/screen.hpp|+++++++++++++++++++-------
Minclude/display/types.hpp|++++++++++++++++++++++++++++++++
Minclude/display/window.hpp|++++++++++++---
Msource/display.cpp|+++++
Msource/layout.cpp|+++++++++++++++++++++++++++++++-----------------
Msource/screen.cpp|+++++++---------------
Msource/window.cpp|++++--

12 files changed, 204 insertions(+), 87 deletions(-)


diff --git a/.clang-tidy b/.clang-tidy

@@ -55,7 +55,7 @@ CheckOptions:

value: 'true'
# These seem to be the most common identifier styles
- key: 'readability-identifier-naming.AbstractClassCase'
value: 'lower_case'
value: 'CamelCase'
- key: 'readability-identifier-naming.ClassCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.ClassConstantCase'

diff --git a/CMakeLists.txt b/CMakeLists.txt

@@ -4,7 +4,7 @@ include(cmake/prelude.cmake)

project(
display
VERSION 0.1.13
VERSION 0.1.14
DESCRIPTION "TUI library"
HOMEPAGE_URL "https://example.com/"
LANGUAGES CXX

diff --git a/example/example.cpp b/example/example.cpp

@@ -7,7 +7,8 @@

namespace
{
int renderer(const display::Window& win, display::place_t plc)
void renderer(const display::Window& win, display::place_t plc)
{
using display::place_t;

@@ -25,25 +26,59 @@ int renderer(const display::Window& win, display::place_t plc)

std::cout << std::flush;
(void)win;
}
return 0;
void recalculator1(display::LayoutFree& layout)
{
const auto [width, height] = layout.dim();
const display::sz_t midw = width / 2;
const display::sz_t midh = height / 2;
layout[4]->pos() = {0, 0};
layout[5]->pos() = {midw, 0};
layout[6]->pos() = {width, 0};
layout[7]->pos() = {width, midh};
layout[0]->pos() = {width, height};
layout[1]->pos() = {midw, height};
layout[2]->pos() = {0, height};
layout[3]->pos() = {0, midh};
layout[8]->pos() = {midw, midh};
}
void recalculator(display::LayoutFree& layout)
void recalculator2(display::LayoutFree& layout)
{
const auto [width, height] = layout.dim();
const display::sz_t midw = width / 2;
const display::sz_t midh = height / 2;
layout[4].pos() = {0, 0};
layout[5].pos() = {midw, 0};
layout[6].pos() = {width, 0};
layout[7].pos() = {width, midh};
layout[0].pos() = {width, height};
layout[1].pos() = {midw, height};
layout[2].pos() = {0, height};
layout[3].pos() = {0, midh};
layout[8].pos() = {midw, midh};
layout[0]->pos() = {0, 0};
layout[1]->pos() = {midw, 0};
layout[2]->pos() = {width, 0};
layout[3]->pos() = {width, midh};
layout[4]->pos() = {width, height};
layout[5]->pos() = {midw, height};
layout[6]->pos() = {0, height};
layout[7]->pos() = {0, midh};
layout[8]->pos() = {midw, midh};
}
void fill(display::LayoutFree& layout)
{
using display::pos_t, display::dim_t, display::piv_t;
using display::PvtX, display::PvtY;
using display::Window;
// clang-format off
layout.append<Window>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Left, PvtY::Top));
layout.append<Window>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Center, PvtY::Top));
layout.append<Window>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Right, PvtY::Top));
layout.append<Window>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Right, PvtY::Center));
layout.append<Window>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Right, PvtY::Bottom));
layout.append<Window>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Center, PvtY::Bottom));
layout.append<Window>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Left, PvtY::Bottom));
layout.append<Window>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Left, PvtY::Center));
layout.append<Window>(renderer, pos_t(), dim_t(10, 5), piv_t(PvtX::Center, PvtY::Center));
// clang-format on
}
} // namespace

@@ -51,30 +86,19 @@ void recalculator(display::LayoutFree& layout)

int main()
{
try {
using display::Display;
using display::PvtX, display::PvtY;
using namespace display; // NOLINT
auto& display = Display::display();
auto& screen = display.screen();
auto& layout = screen.set_layout({recalculator});
layout.append({renderer, {}, {20, 10}, {PvtX::Left, PvtY::Top}});
layout.append({renderer, {}, {20, 10}, {PvtX::Center, PvtY::Top}});
layout.append({renderer, {}, {20, 10}, {PvtX::Right, PvtY::Top}});
layout.append({renderer, {}, {20, 10}, {PvtX::Right, PvtY::Center}});
layout.append({renderer, {}, {20, 10}, {PvtX::Right, PvtY::Bottom}});
layout.append({renderer, {}, {20, 10}, {PvtX::Center, PvtY::Bottom}});
layout.append({renderer, {}, {20, 10}, {PvtX::Left, PvtY::Bottom}});
layout.append({renderer, {}, {20, 10}, {PvtX::Left, PvtY::Center}});
layout.append({renderer, {}, {20, 10}, {PvtX::Center, PvtY::Center}});
for (display.set_resized(); true;) {
using display::event;
auto& layout = display.screen().set_layout<LayoutRigid>(nullptr);
fill(layout.screen1().set_layout<LayoutFree>(recalculator1));
fill(layout.screen2().set_layout<LayoutFree>(recalculator2));
for (display.set_resized(); true;) {
const auto evnt = display.get_event();
if (evnt.type() == event::Type::RESIZE) {
std::cout << alec::erase_display_v<alec::Motion::WHOLE>;
screen.render();
display.render();
continue;
}

diff --git a/include/display/display.hpp b/include/display/display.hpp

@@ -27,6 +27,8 @@ public:

void set_resized();
void reset_resized();
void render();
private:
Display();
~Display();

diff --git a/include/display/layout.hpp b/include/display/layout.hpp

@@ -1,13 +1,16 @@

#pragma once
#include <memory>
#include <vector>
#include "display/screen.hpp"
#include "display/types.hpp"
#include "display/window.hpp"
namespace display
{
class LayoutFree
class LayoutFree : public Layout
{
public:
using recalc_f = void(LayoutFree&);

@@ -17,29 +20,51 @@ public:

{
}
const pos_t& pos() const { return m_pos; }
const dim_t& dim() const { return m_dim; }
Window* operator[](std::size_t idx) { return m_windows[idx].get(); }
const Window* operator[](std::size_t idx) const
{
return m_windows[idx].get();
}
const auto& operator[](std::size_t idx) const { return m_windows[idx]; }
auto& operator[](std::size_t idx) { return m_windows[idx]; }
void append(Window window);
template<typename T, class... Args>
T& append(Args&&... args)
{
m_windows.emplace_back(std::make_unique<T>(std::forward<Args>(args)...));
m_is_sorted = false;
return *dynamic_cast<T*>(m_windows.back().get());
}
void resize(pos_t pos, dim_t dim);
int render() const;
void resize(dim_t dim) override;
int render(pos_t pos) const override;
private:
recalc_f* m_recalc;
pos_t m_pos;
dim_t m_dim;
std::vector<Window> m_windows;
std::vector<std::unique_ptr<Window>> m_windows;
mutable bool m_is_sorted = true;
};
class LayoutRigid
class LayoutRigid : public Layout
{
public:
using recalc_f = void(LayoutRigid&);
LayoutRigid(recalc_f f_recalc) // NOLINT
: m_recalc(f_recalc)
{
}
auto& screen1() { return m_screen1; }
auto& screen2() { return m_screen2; }
void resize(dim_t dim) override;
int render(pos_t pos) const override;
private:
recalc_f* m_recalc;
Screen m_screen1;
Screen m_screen2;
};
} // namespace display

diff --git a/include/display/screen.hpp b/include/display/screen.hpp

@@ -1,8 +1,7 @@

#pragma once
#include <optional>
#include <memory>
#include "display/layout.hpp"
#include "display/types.hpp"
namespace display

@@ -11,22 +10,35 @@ namespace display

class Screen
{
public:
Screen(dim_t dim) // NOLINT
Screen(dim_t dim = {}) // NOLINT
: m_dim(dim)
{
}
Screen(const Screen&) = delete;
Screen(Screen&&) = delete;
Screen& operator=(const Screen&) = delete;
Screen& operator=(Screen&&) = delete;
~Screen() = default;
const auto& dim() const { return m_dim; }
LayoutFree& set_layout(LayoutFree layout);
template<typename T, class... Args>
T& set_layout(Args&&... args)
{
m_layout = std::make_unique<T>(std::forward<Args>(args)...);
return *dynamic_cast<T*>(m_layout.get());
}
void resize(dim_t new_dim);
void render() const;
void resize(dim_t dim);
void render(pos_t pos) const;
private:
dim_t m_dim;
std::optional<LayoutFree> m_layout;
std::unique_ptr<Layout> m_layout;
};
} // namespace display

diff --git a/include/display/types.hpp b/include/display/types.hpp

@@ -35,6 +35,13 @@ struct pos_t

{
}
pos_t operator+(pos_t rhs) const
{
return {static_cast<std::uint16_t>(x + rhs.x),
static_cast<std::uint16_t>(y + rhs.y),
static_cast<std::uint16_t>(z + rhs.z)};
}
sz_t x;
sz_t y;
sz_t z;

@@ -48,6 +55,8 @@ struct place_t

{
}
place_t operator+(pos_t rhs) const { return {start + rhs, end + rhs}; }
pos_t start;
pos_t end;
};

@@ -78,4 +87,27 @@ struct piv_t

PvtY y;
};
class Layout
{
public:
Layout() = default;
Layout(const Layout&) = delete;
Layout& operator=(const Layout&) = delete;
Layout(Layout&&) = delete;
Layout& operator=(Layout&&) = delete;
virtual ~Layout() = default;
const dim_t& dim() const { return m_dim; }
dim_t& dim() { return m_dim; }
virtual void resize(dim_t dim) = 0;
virtual int render(pos_t pos) const = 0;
private:
dim_t m_dim;
};
} // namespace display

diff --git a/include/display/window.hpp b/include/display/window.hpp

@@ -4,12 +4,13 @@

#include "display/types.hpp"
namespace display {
namespace display
{
class Window
{
public:
using render_f = int(const Window&, place_t place);
using render_f = void(const Window&, place_t place);
Window(render_f frender, pos_t pos, dim_t dim, piv_t piv = {})
: m_renderer(frender)

@@ -19,6 +20,14 @@ public:

{
}
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
Window(Window&&) = default;
Window& operator=(Window&&) = default;
virtual ~Window() = default;
const auto& pos() const { return m_pos; }
auto& pos() { return m_pos; }

@@ -28,7 +37,7 @@ public:

const auto& piv() const { return m_piv; }
auto& piv() { return m_piv; }
int render(place_t place) const;
virtual void render(place_t place) const;
std::optional<place_t> place(dim_t bounds) const;

diff --git a/source/display.cpp b/source/display.cpp

@@ -86,4 +86,9 @@ bool Display::get_resized() const

return m_is_resized;
}
void Display::render()
{
m_screen.render({0, 0});
}
} // namespace display

diff --git a/source/layout.cpp b/source/layout.cpp

@@ -8,20 +8,16 @@

namespace display
{
void LayoutFree::append(Window window)
void LayoutFree::resize(dim_t dim)
{
m_windows.push_back(window);
m_is_sorted = false;
}
this->dim() = dim;
void LayoutFree::resize(pos_t pos, dim_t dim)
{
m_pos = pos;
m_dim = dim;
m_recalc(*this);
if (m_recalc != nullptr) {
m_recalc(*this);
}
}
int LayoutFree::render() const
int LayoutFree::render(pos_t pos) const
{
static std::vector<std::uint8_t> idxs;

@@ -32,24 +28,42 @@ int LayoutFree::render() const

idxs.begin(),
idxs.end(),
[&](auto left, auto right)
{ return m_windows[left].pos().z < m_windows[right].pos().z; });
{ return m_windows[left]->pos().z < m_windows[right]->pos().z; });
m_is_sorted = true;
}
for (const auto idx : idxs) {
const auto win = m_windows[idx];
const auto plc = win.place(m_dim);
const auto& win = m_windows[idx];
const auto plc = win->place(this->dim());
if (!plc.has_value()) {
continue;
}
const auto res = win.render(plc.value());
if (res != 0) {
return res;
}
win->render(plc.value() + pos);
}
return 0;
}
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});
if (m_recalc != nullptr) {
m_recalc(*this);
}
}
int 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;
}

diff --git a/source/screen.cpp b/source/screen.cpp

@@ -1,28 +1,20 @@

#include "display/screen.hpp"
#include "display/layout.hpp"
namespace display
{
LayoutFree& Screen::set_layout(LayoutFree layout)
{
m_layout.emplace(std::move(layout));
return m_layout.value();
}
void Screen::resize(dim_t new_dim)
void Screen::resize(dim_t dim)
{
m_dim = new_dim;
if (m_layout.has_value()) {
m_layout->resize({}, m_dim);
m_dim = dim;
if (m_layout != nullptr) {
m_layout->resize(m_dim);
}
}
void Screen::render() const
void Screen::render(pos_t pos) const
{
if (m_layout.has_value()) {
m_layout->render();
if (m_layout != nullptr) {
m_layout->render(pos);
}
}

diff --git a/source/window.cpp b/source/window.cpp

@@ -5,9 +5,11 @@

namespace display
{
int Window::render(place_t place) const
void Window::render(place_t place) const
{
return m_renderer(*this, place);
if (m_renderer != nullptr) {
m_renderer(*this, place);
}
}
std::optional<place_t> Window::place(dim_t bounds) const