display

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

commit5bc32ab08d18730c06503b91484cc3571d2a43da
parent714563ba3fb520f00631d52a401213ddd47e887b
authorDimitrije Dobrota <mail@dimitrijedobrota.com>
dateFri, 21 Feb 2025 17:18:05 +0100

Big refactor for type safety * val_t template struct that holds a single value (x, y, width, height) * No implicit constructions or conversions * All operations are well defined and intuitive * Bugprone parts must be very explicit * place_t renamed to plc_t, padd_t to pad_t * layout_rigid constructor split into two functions

Diffstat:
MCMakeLists.txt|+-
Mexample/example.cpp|++++++++++++++++++++++++++++++++++++---------------------------------
Mexample/navig/navig.cpp|+++----
Minclude/display/element.hpp|+++++++++++++-----------
Minclude/display/layout.hpp|+++++-----
Minclude/display/layout_rigid.hpp|+++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Minclude/display/types.hpp|++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Minclude/display/utility.hpp|------
Minclude/display/window.hpp|++++++++++---------
Minclude/display/window_pivot.hpp|+++---
Msource/display.cpp|++--
Msource/element.cpp|++++++++++++++---------
Msource/window.cpp|++++++++++++++++++------------------
Msource/window_pivot.cpp|+++++++++++++++++------------------------
Mtest/source/utility_test.cpp|+++--

15 files changed, 340 insertions(+), 237 deletions(-)


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

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

project(
display
VERSION 0.1.36
VERSION 0.1.37
DESCRIPTION "TUI library"
HOMEPAGE_URL "git://git.dimitrijedobrota.com/display.git"
LANGUAGES CXX

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

@@ -14,8 +14,8 @@ using namespace display; // NOLINT

class WindowCustom : public WindowPivot
{
public:
explicit WindowCustom(place_t aplc, piv_t piv, dim_t dim)
: WindowPivot(aplc, {0, 0}, piv, dim)
explicit WindowCustom(plc_t aplc, piv_t piv, dim_t dim)
: WindowPivot(aplc, pad_t(0, 0), piv, dim)
{
}

@@ -27,7 +27,7 @@ public:

std::cout << alec::background(color_red, 65, 65);
line_reset();
for (std::size_t i = 0; i < hgt(); i++) {
for (auto i = hgt_t(0); i < hgt(); i++) {
line_empty();
}

@@ -39,7 +39,7 @@ public:

class LayoutCustom : public LayoutRigid<Layout<WindowCustom>>
{
public:
explicit LayoutCustom(place_t aplc)
explicit LayoutCustom(plc_t aplc)
: LayoutRigid(aplc, {{0, 1, 2}, {7, 8, 3}, {6, 5, 4}})
{
append().set_child(piv_t(PvtX::Left, PvtY::Top), dim_t(12, 4));

@@ -57,19 +57,19 @@ public:

class LayoutRigidBorder : public LayoutRigid<LayoutCustom>
{
public:
LayoutRigidBorder(place_t aplc, const layout_t& layout)
LayoutRigidBorder(plc_t aplc, const layout_t& layout)
: LayoutRigid<LayoutCustom>(aplc, layout)
{
auto [m, n] = get_grid();
const auto [m, n] = get_grid();
const auto valid = [&](std::size_t xpos, std::size_t ypos)
{ return xpos >= 0 && xpos < n && ypos >= 0 && ypos < m; };
{ return xpos >= 0 && xpos < n.value() && ypos >= 0 && ypos < m.value(); };
const auto get = [&](std::size_t xpos, std::size_t ypos) -> std::uint8_t
{ return valid(xpos, ypos) ? layout[xpos][ypos] : 0xFF; };
for (std::size_t i = 0; i <= n; i++) {
for (std::size_t j = 0; j <= m; j++) {
for (std::size_t i = 0; i <= n.value(); i++) {
for (std::size_t j = 0; j <= m.value(); j++) {
const std::uint8_t ptl = get(i - 1, j - 1);
const std::uint8_t ptr = get(i - 1, j);
const std::uint8_t pbl = get(i, j - 1);

@@ -81,7 +81,7 @@ public:

mask |= ((pbr != pbl) ? 1U : 0U) << 2U; // Bottom
mask |= ((pbl != ptl) ? 1U : 0U) << 3U; // Left
m_corners.emplace_back(mask, i, j);
m_corners.emplace_back(mask, wth_t(j), hgt_t(i));
}
}
}

@@ -93,7 +93,7 @@ public:

place(size()), std::forward<Args>(args)...);
}
void resize(place_t aplc) override
void resize(plc_t aplc) override
{
LayoutRigid<LayoutCustom>::resize(aplc);

@@ -108,41 +108,44 @@ public:

for (std::size_t i = 0; i < size(); i++) {
const auto [pos, dim] = LayoutRigid<LayoutCustom>::place(i);
set_cursor(pos.y, pos.x + 1);
for (sz_t j = 1; j < dim.width; j++) {
// Top of each element
set_cursor(pos.x + 1, pos.y);
for (auto j = wth_t(1); j < dim.width; j++) {
std::cout << "─";
}
for (sz_t j = pos.y + 1; j < pos.y + dim.height; j++) {
set_cursor(j, pos.x) << "│";
// Left of each element
for (auto j = pos.y + 1; j < pos.y + dim.height; j++) {
set_cursor(pos.x, j) << "│";
}
}
for (sz_t i = aypos() + 1; i < aypos() + ahgt(); i++) {
set_cursor(i, axpos() + awth() - 1) << "│";
// Right of the layout
for (auto j = aypos() + 1; j < aypos() + ahgt(); j++) {
set_cursor(axpos() + awth() - 1, j) << "│";
}
set_cursor(aypos() + ahgt() - 1, axpos() + 1);
for (sz_t i = 2; i < awth(); i++) {
// Bottom of the layout
set_cursor(axpos() + 1, aypos() + ahgt() - 1);
for (auto i = wth_t(2); i < awth(); i++) {
std::cout << "─";
}
auto [m, n] = get_grid();
const auto [m, n] = get_grid();
const auto [w, h] = adim();
const sz_t unw = w / m;
const sz_t unh = h / n;
for (const auto [mask, xpos, ypos] : m_corners) {
const sz_t xloc = xpos != n ? xpos * unw : axpos() + w;
const sz_t yloc = ypos != m ? ypos * unh : aypos() + h;
set_cursor(yloc, xloc) << corner_t::lookup[mask]; // NOLINT
const auto unw = w / m;
const auto unh = h / n;
for (const auto [mask, wth, hgt] : m_corners) {
const auto xloc = wth != m ? xpos_t((wth * unw).value()) : axpos() + w;
const auto yloc = hgt != n ? ypos_t((hgt * unh).value()) : aypos() + h;
set_cursor(xloc, yloc) << corner_t::lookup[mask]; // NOLINT
};
std::cout << std::flush;
}
private:
place_t place(std::size_t idx)
plc_t place(std::size_t idx)
{
const auto [pos, dim] = LayoutRigid<LayoutCustom>::place(idx);
dim_t sub = {1, 1};

@@ -167,16 +170,16 @@ private:

};
// clang-format on
corner_t(std::uint8_t mask, sz_t ypos, sz_t xpos) // NOLINT
corner_t(std::uint8_t mask, wth_t wigth, hgt_t height) // NOLINT
: mask(mask)
, x(xpos)
, y(ypos)
, wigth(wigth)
, height(height)
{
}
std::uint8_t mask;
sz_t x;
sz_t y;
wth_t wigth;
hgt_t height;
};
std::vector<corner_t> m_corners;

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

@@ -19,10 +19,10 @@ using display::WindowPivot;

class WindowCustom : public WindowPivot
{
public:
WindowCustom(display::place_t aplc,
WindowCustom(display::plc_t aplc,
display::piv_t piv,
const example::menu_t& menu)
: WindowPivot(aplc, {4, 5, 2, 4}, piv, calc_dim(menu))
: WindowPivot(aplc, {4, 2, 5, 4}, piv, calc_dim(menu))
, m_menu(menu)
{
}

@@ -98,8 +98,7 @@ private:

width = std::max(width, item.prompt.size());
}
return {static_cast<display::sz_t>(width),
static_cast<display::sz_t>(menu.items.size() + 2)};
return {display::wth_t(width), display::hgt_t(menu.items.size() + 2)};
}
example::menu_t m_menu;

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

@@ -7,7 +7,7 @@ namespace display

class Element
{
public:
explicit Element(place_t aplc)
explicit Element(plc_t aplc)
: m_aplc(aplc)
{
}

@@ -20,24 +20,26 @@ public:

virtual ~Element() = default;
virtual void resize(place_t aplc) { m_aplc = aplc; }
virtual void resize(plc_t aplc) { m_aplc = aplc; }
virtual void render() const = 0;
virtual void clear() const = 0;
virtual void input(event& evnt) = 0;
static std::ostream& set_cursor(sz_t posy, sz_t posx);
static std::ostream& set_cursor(xpos_t xpos, ypos_t ypos);
static std::ostream& set_cursor(pos_t pos);
void render_border() const;
const auto& aplc() const { return m_aplc; }
const auto& apos() const { return aplc().pos; }
const auto& adim() const { return aplc().dim; }
const auto& axpos() const { return apos().x; }
const auto& aypos() const { return apos().y; }
const auto& awth() const { return adim().width; }
const auto& ahgt() const { return adim().height; }
plc_t aplc() const { return m_aplc; }
pos_t apos() const { return aplc().pos; }
dim_t adim() const { return aplc().dim; }
xpos_t axpos() const { return apos().x; }
ypos_t aypos() const { return apos().y; }
wth_t awth() const { return adim().width; }
hgt_t ahgt() const { return adim().height; }
private:
place_t m_aplc;
plc_t m_aplc;
};
} // namespace display

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

@@ -16,12 +16,12 @@ class Layout : public Element

public:
using ptr_t = std::unique_ptr<T>;
explicit Layout(place_t aplc)
explicit Layout(plc_t aplc)
: Element(aplc)
{
}
void resize(place_t aplc) override
void resize(plc_t aplc) override
{
Element::resize(aplc);

@@ -87,12 +87,12 @@ class LayoutMulti : public Element

public:
using ptr_t = std::unique_ptr<T>;
explicit LayoutMulti(place_t aplc)
explicit LayoutMulti(plc_t aplc)
: Element(aplc)
{
}
void resize(place_t aplc) override
void resize(plc_t aplc) override
{
Element::resize(aplc);

@@ -151,7 +151,7 @@ public:

protected:
template<typename M = T, class... Args>
requires(std::is_base_of_v<T, M>)
M& append(place_t aplc, Args&&... args)
M& append(plc_t aplc, Args&&... args)
{
m_children.emplace_back(
std::make_unique<M>(aplc, std::forward<Args>(args)...));

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

@@ -16,7 +16,7 @@ class LayoutRigid : public LayoutMulti<T>

public:
using layout_t = std::vector<std::vector<std::uint8_t>>;
LayoutRigid(place_t aplc, layout_t layout);
LayoutRigid(plc_t aplc, layout_t layout);
template<typename M = T, class... Args>
requires(std::is_base_of_v<T, M>)

@@ -26,7 +26,7 @@ public:

std::forward<Args>(args)...);
}
void resize(place_t aplc) override
void resize(plc_t aplc) override
{
LayoutMulti<T>::resize(aplc);

@@ -36,21 +36,25 @@ public:

}
protected:
place_t place(std::size_t idx) const
plc_t place(std::size_t idx) const
{
const auto [m, n] = m_grid;
const auto [w, h] = this->adim();
const sz_t unw = w / m;
const sz_t unh = h / n;
const auto unw = w / m;
const auto unh = h / n;
const auto calc = [&]<typename R>(const R share, bool addw, bool addh) -> R
{
const auto [sw, sh] = share;
const sz_t width = addw ? w - (unw * (m - sw)) : unw * sw;
const sz_t height = addh ? h - (unh * (n - sh)) : unh * sh;
const auto wth = addw ? w - (unw * (m - sw.value())) : unw * sw.value();
const auto hgt = addh ? h - (unh * (n - sh.value())) : unh * sh.value();
return {width, height};
if constexpr (std::is_same_v<R, pos_t>) {
return {xpos_t(wth.value()), ypos_t(hgt.value())};
} else {
return {wth, hgt};
}
};
const auto start = calc(m_recs[idx].start, false, false);

@@ -63,47 +67,65 @@ protected:

struct record_t
{
pos_t start;
dim_t dim;
pos_t start = {0, 0};
dim_t dim = {0, 0};
bool addw = false;
bool addh = false;
};
private:
std::size_t count_and_pad(layout_t& layout) const;
void handle_cols(const layout_t& layout);
void handle_rows(const layout_t& layout);
dim_t m_grid;
std::vector<record_t> m_recs;
};
template<typename T>
LayoutRigid<T>::LayoutRigid(place_t aplc, layout_t layout)
LayoutRigid<T>::LayoutRigid(plc_t aplc, layout_t layout)
: LayoutMulti<T>(aplc)
, m_grid(static_cast<sz_t>(layout[0].size()),
static_cast<sz_t>(layout.size()))
, m_grid(wth_t(layout[0].size()), hgt_t(layout.size()))
, m_recs(count_and_pad(layout))
{
static const auto& insert = [](sz_t& count, sz_t cnt, sz_t& pos, sz_t total)
{
if (count != 0 && (pos != total || count != cnt)) {
throw std::runtime_error("Invalid layout [Shape]");
handle_cols(layout);
handle_rows(layout);
}
template<typename T>
std::size_t LayoutRigid<T>::count_and_pad(layout_t& layout) const
{
std::unordered_set<std::uint8_t> ust;
for (std::size_t i = 0U; i < m_grid.height.value(); i++) {
for (std::size_t j = 0U; j < m_grid.width.value(); j++) {
ust.insert(layout[i][j]);
}
layout[i].emplace_back(0xFF);
}
layout.emplace_back(m_grid.width.value(), 0xFF);
if (count != 0) {
return;
for (std::size_t i = 0U; i < m_grid.height.value(); i++) {
for (std::size_t j = 0U; j < m_grid.width.value(); j++) {
if (layout[i][j] >= ust.size()) {
throw std::runtime_error("Invalid layout [Number]");
}
}
}
pos = total;
count = cnt;
};
return ust.size();
}
static const sz_t one = 1;
template<typename T>
void LayoutRigid<T>::handle_cols(const layout_t& layout)
{
const auto [m, n] = get_grid();
for (sz_t i = 0U; i < m_grid.height; i++) {
sz_t cnt = 1;
for (std::size_t i = 0U; i < n.value(); i++) {
m_recs[layout[i][m.value() - 1]].addw = true;
m_recs[layout[i][m_grid.width - 1]].addw = true;
for (sz_t j = 0U; j < m_grid.width; j++) {
auto cnt = wth_t(1);
for (std::size_t j = 0; j < m.value(); j++) {
const auto crnt = layout[i][j];
if (crnt == layout[i][j + 1]) {

@@ -111,16 +133,34 @@ LayoutRigid<T>::LayoutRigid(place_t aplc, layout_t layout)

continue;
}
insert(m_recs[crnt].dim.width, cnt, m_recs[crnt].start.x, j - cnt + one);
cnt = 1;
auto& count = m_recs[crnt].dim.width;
auto& pos = m_recs[crnt].start.x;
const auto total = xpos_t(j) - cnt + 1;
if (count.value() != 0) {
if (pos != total || count != cnt) {
throw std::runtime_error("Invalid layout [Shape Col]");
}
} else {
pos = total;
count = cnt;
}
cnt = wth_t(1);
}
}
}
template<typename T>
void LayoutRigid<T>::handle_rows(const layout_t& layout)
{
const auto [m, n] = get_grid();
for (sz_t j = 0U; j < m_grid.width; j++) {
sz_t cnt = 1;
for (std::size_t j = 0U; j < m.value(); j++) {
m_recs[layout[n.value() - 1][j]].addh = true;
m_recs[layout[m_grid.height - 1][j]].addh = true;
for (sz_t i = 0U; i < m_grid.height; i++) {
auto cnt = hgt_t(1);
for (std::size_t i = 0; i < n.value(); i++) {
const auto crnt = layout[i][j];
if (crnt == layout[i + 1][j]) {

@@ -128,34 +168,22 @@ LayoutRigid<T>::LayoutRigid(place_t aplc, layout_t layout)

continue;
}
insert(m_recs[crnt].dim.height, cnt, m_recs[crnt].start.y, i - cnt + one);
cnt = 1;
}
}
}
template<typename T>
std::size_t LayoutRigid<T>::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]");
auto& count = m_recs[crnt].dim.height;
auto& pos = m_recs[crnt].start.y;
const auto total = ypos_t(i) - cnt + 1;
if (count.value() != 0) {
if (pos != total || count != cnt) {
throw std::runtime_error("Invalid layout [Shape Row]");
}
} else {
pos = total;
count = cnt;
}
cnt = hgt_t(1);
}
}
return ust.size();
}
} // namespace display

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

@@ -1,6 +1,7 @@

#pragma once
#include <cstdint>
#include <iostream>
#include <utility>
#include <alec/alec.hpp>

@@ -10,51 +11,95 @@ namespace display

using event = alec::event;
using sz_t = std::uint16_t;
struct padd_t
template<char C>
struct val_t
{
padd_t()
: padd_t(0, 0, 0, 0)
using sz_t = std::uint16_t;
explicit val_t(std::size_t xpos)
: v(static_cast<sz_t>(xpos))
{
}
padd_t(sz_t width, sz_t height) // NOLINT
: padd_t(width, width, height, height)
explicit val_t(int xpos)
: v(static_cast<sz_t>(xpos))
{
}
padd_t(sz_t leftpos, sz_t rightpos, sz_t toppos, sz_t bottompos) // NOLINT
: left(leftpos)
, right(rightpos)
, top(toppos)
, bottom(bottompos)
explicit val_t(sz_t xpos) // NOLINT
: v(xpos)
{
}
sz_t width() const { return left + right; }
sz_t height() const { return top + bottom; }
auto value() const { return v; }
auto& value() { return v; }
// clang-format off
val_t operator+(val_t rhs) const { return val_t(static_cast<sz_t>(v + rhs.v)); }
val_t operator-(val_t rhs) const { return val_t(static_cast<sz_t>(v - rhs.v)); }
val_t operator*(val_t rhs) const { return val_t(static_cast<sz_t>(v * rhs.v)); }
val_t operator/(val_t rhs) const { return val_t(static_cast<sz_t>(v / rhs.v)); }
val_t operator+(int rhs) const { return val_t(static_cast<sz_t>(v + rhs)); }
val_t operator-(int rhs) const { return val_t(static_cast<sz_t>(v - rhs)); }
val_t operator*(int rhs) const { return val_t(static_cast<sz_t>(v * rhs)); }
val_t operator/(int rhs) const { return val_t(static_cast<sz_t>(v / rhs)); }
auto& operator+=(val_t rhs) { v += rhs.v; return *this; }
auto& operator-=(val_t rhs) { v -= rhs.v; return *this; }
auto& operator*=(val_t rhs) { v *= rhs.v; return *this; }
auto& operator/=(val_t rhs) { v /= rhs.v; return *this; }
auto& operator+=(int rhs) { v += rhs; return *this; }
auto& operator-=(int rhs) { v -= rhs; return *this; }
auto& operator*=(int rhs) { v *= rhs; return *this; }
auto& operator/=(int rhs) { v /= rhs; return *this; }
auto& operator++() { return *this += 1; }
auto& operator--() { return *this -= 1; }
val_t operator++(int) { return val_t(v++); }
val_t operator--(int) { return val_t(v--); }
auto operator<=>(const val_t&) const = default;
sz_t left;
sz_t right;
sz_t top;
sz_t bottom;
friend std::ostream& operator<<(std::ostream& ost, val_t cord) {
return ost << cord.value();
}
// clang-format on
private:
sz_t v; // NOLINT
};
using xpos_t = val_t<'x'>;
using ypos_t = val_t<'y'>;
using wth_t = val_t<'w'>;
using hgt_t = val_t<'h'>;
// clang-format off
inline xpos_t operator+(xpos_t lhs, wth_t rhs) { return xpos_t(lhs.value() + rhs.value()); }
inline xpos_t operator-(xpos_t lhs, wth_t rhs) { return xpos_t(lhs.value() - rhs.value()); }
inline ypos_t operator+(ypos_t lhs, hgt_t rhs) { return ypos_t(lhs.value() + rhs.value()); }
inline ypos_t operator-(ypos_t lhs, hgt_t rhs) { return ypos_t(lhs.value() - rhs.value()); }
// clang-format on
struct dim_t
{
dim_t()
: dim_t(0, 0)
dim_t(int wdth, int hght) // NOLINT
: width(wdth)
, height(hght)
{
}
dim_t(sz_t wdth, sz_t hght) // NOLINT
dim_t(wth_t wdth, hgt_t hght) // NOLINT
: width(wdth)
, height(hght)
{
}
dim_t(std::pair<sz_t, sz_t> pair) // NOLINT
dim_t(std::pair<std::uint16_t, std::uint16_t> pair) // NOLINT
: width(std::get<0>(pair))
, height(std::get<1>(pair))
{

@@ -62,36 +107,70 @@ struct dim_t

dim_t operator+(dim_t rhs) const
{
return {
static_cast<sz_t>(width + rhs.width),
static_cast<sz_t>(height + rhs.height),
};
return {width + rhs.width, height + rhs.height};
}
dim_t operator-(dim_t rhs) const
{
return {
static_cast<sz_t>(width - rhs.width),
static_cast<sz_t>(height - rhs.height),
};
return {width - rhs.width, height - rhs.height};
}
wth_t width;
hgt_t height;
};
struct pad_t
{
pad_t(int width, int height) // NOLINT
: pad_t(width, height, width, height)
{
}
dim_t operator+(padd_t rhs) const
pad_t(int leftpos, // NOLINT
int toppos,
int rightpos,
int bottompos)
: left(leftpos)
, right(rightpos)
, top(toppos)
, bottom(bottompos)
{
return {
static_cast<sz_t>(width + rhs.width()),
static_cast<sz_t>(height + rhs.height()),
};
}
sz_t width;
sz_t height;
pad_t(wth_t width, hgt_t height) // NOLINT
: pad_t(width, height, width, height)
{
}
pad_t(wth_t leftpos, hgt_t toppos, wth_t rightpos, hgt_t bottompos)
: left(leftpos)
, right(rightpos)
, top(toppos)
, bottom(bottompos)
{
}
wth_t width() const { return left + right; }
hgt_t height() const { return top + bottom; }
friend dim_t operator+(dim_t dim, pad_t rhs)
{
return {dim.width + rhs.width(), dim.height + rhs.height()};
}
wth_t left;
wth_t right;
hgt_t top;
hgt_t bottom;
};
struct pos_t
{
pos_t()
: pos_t(0, 0)
using sz_t = std::uint16_t;
pos_t(int xpos, int ypos)
: x(xpos)
, y(ypos)
{
}

@@ -101,29 +180,27 @@ struct pos_t

{
}
pos_t operator+(pos_t rhs) const
pos_t(xpos_t xpos, ypos_t ypos)
: x(xpos)
, y(ypos)
{
return {
static_cast<sz_t>(x + rhs.x),
static_cast<sz_t>(y + rhs.y),
};
}
pos_t operator+(pos_t rhs) const { return {x + rhs.x, y + rhs.y}; }
dim_t operator-(pos_t rhs) const
{
return {
static_cast<sz_t>(x - rhs.x),
static_cast<sz_t>(y - rhs.y),
};
return {wth_t((x - rhs.x).value()), hgt_t((y - rhs.y).value())};
}
sz_t x;
sz_t y;
pos_t operator+(dim_t rhs) const { return {x + rhs.width, y + rhs.height}; }
xpos_t x;
ypos_t y;
};
struct place_t
struct plc_t
{
place_t(pos_t posval, dim_t dimval)
plc_t(pos_t posval, dim_t dimval)
: pos(posval)
, dim(dimval)
{

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

@@ -6,42 +6,36 @@ namespace display

{
template<typename T>
requires std::is_unsigned_v<T>
constexpr bool is_overflow_lim(T val, T add, T lim)
{
return val > lim || add > lim || val > lim - add;
}
template<typename T>
requires std::is_unsigned_v<T>
constexpr bool is_underflow_lim(T val, T sub, T lim)
{
return val < lim || sub < lim || val < lim + sub;
}
template<typename T>
requires std::is_unsigned_v<T>
constexpr bool is_overflow(T val, T add)
{
return val > std::numeric_limits<T>::max() - add;
}
template<typename T>
requires std::is_unsigned_v<T>
constexpr bool is_underflow(T val, T sub)
{
return val < std::numeric_limits<T>::min() + sub;
}
template<typename T>
requires std::is_unsigned_v<T>
constexpr T add_lim(T val, T add, T lim)
{
return !is_overflow_lim(val, add, lim) ? val + add : lim;
}
template<typename T>
requires std::is_unsigned_v<T>
constexpr T sub_lim(T val, T sub, T lim)
{
return !is_underflow_lim(val, sub, lim) ? val - sub : lim;

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

@@ -5,10 +5,11 @@

namespace display
{
class Window : public Element
{
public:
explicit Window(place_t aplc, padd_t padd)
explicit Window(plc_t aplc, pad_t padd)
: Element(aplc)
, m_padd(padd)
{

@@ -19,7 +20,7 @@ public:

void input(event& /* unused */) override {}
protected:
padd_t padd() const { return m_padd; }
pad_t padd() const { return m_padd; }
std::ostream& line_next() const;

@@ -29,13 +30,13 @@ protected:

void line_center(const std::string& text) const;
void line_right(const std::string& text) const;
place_t plc() const { return {pos(), dim()}; }
plc_t plc() const { return {pos(), dim()}; }
pos_t pos() const { return {xpos(), ypos()}; }
dim_t dim() const { return {wth(), hgt()}; }
sz_t xpos() const { return axpos() + m_padd.left; }
sz_t ypos() const { return aypos() + m_padd.top; }
sz_t wth() const { return awth() - m_padd.width(); }
sz_t hgt() const { return ahgt() - m_padd.height(); }
xpos_t xpos() const { return axpos() + m_padd.left; }
ypos_t ypos() const { return aypos() + m_padd.top; }
wth_t wth() const { return awth() - m_padd.width(); }
hgt_t hgt() const { return ahgt() - m_padd.height(); }
private:
using Element::adim;

@@ -46,9 +47,9 @@ private:

using Element::axpos;
using Element::aypos;
padd_t m_padd;
pad_t m_padd;
mutable display::sz_t m_ypos = 0;
mutable ypos_t m_ypos = ypos_t(0);
};
} // namespace display

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

@@ -8,20 +8,20 @@ namespace display

class WindowPivot : public Window
{
public:
WindowPivot(place_t aplc, padd_t padd, piv_t piv, dim_t dim)
WindowPivot(plc_t aplc, pad_t padd, piv_t piv, dim_t dim)
: Window(place(aplc, piv, dim + padd), padd)
, m_piv(piv)
, m_dim(dim)
{
}
void resize(place_t aplc) override
void resize(plc_t aplc) override
{
Window::resize(place(aplc, m_piv, m_dim + padd()));
}
private:
static place_t place(place_t aplc, piv_t piv, dim_t dim);
static plc_t place(plc_t aplc, piv_t piv, dim_t dim);
piv_t m_piv;
dim_t m_dim;

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

@@ -28,7 +28,7 @@ Display& Display::display()

}
Display::Display()
: m_layout(place_t(pos_t(0, 0), alec::get_screen_size()))
: m_layout(plc_t(pos_t(0, 0), alec::get_screen_size()))
{
struct sigaction old_sig_action = {};
sigaction(SIGWINCH, nullptr, &old_sig_action);

@@ -90,7 +90,7 @@ bool Display::get_resized() const

void Display::resize()
{
m_layout.resize(place_t(pos_t(0, 0), alec::get_screen_size()));
m_layout.resize(plc_t(pos_t(0, 0), alec::get_screen_size()));
}
void Display::render() const

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

@@ -5,29 +5,34 @@

namespace display
{
std::ostream& Element::set_cursor(sz_t posy, sz_t posx)
std::ostream& Element::set_cursor(xpos_t xpos, ypos_t ypos)
{
return std::cout << alec::cursor_position(posy + 1, posx + 1);
return std::cout << alec::cursor_position(ypos.value() + 1, xpos.value() + 1);
}
std::ostream& Element::set_cursor(pos_t pos)
{
return set_cursor(pos.x, pos.y);
}
void Element::render_border() const
{
set_cursor(aypos(), axpos());
set_cursor(axpos(), aypos());
std::cout << "┌";
for (sz_t i = 2; i < awth(); i++) {
for (auto i = wth_t(2); i < awth(); i++) {
std::cout << "─";
}
std::cout << "┐";
for (sz_t i = aypos() + 1; i < aypos() + ahgt(); i++) {
set_cursor(i, axpos()) << "│";
set_cursor(i, axpos() + awth() - 1) << "│";
for (ypos_t j = aypos() + 1; j < aypos() + ahgt(); j++) {
set_cursor(axpos(), j) << "│";
set_cursor(axpos() + awth() - 1, j) << "│";
}
set_cursor(aypos() + ahgt() - 1, axpos());
set_cursor(axpos(), aypos() + ahgt() - 1);
std::cout << "└";
for (sz_t i = 2; i < awth(); i++) {
for (auto i = wth_t(2); i < awth(); i++) {
std::cout << "─";
}
std::cout << "┘";

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

@@ -9,14 +9,14 @@ namespace display

void Window::render() const
{
const auto space = std::string(awth(), ' ');
const auto space = std::string(awth().value(), ' ');
for (sz_t i = aypos(); i < aypos() + m_padd.top; i++) {
set_cursor(i, axpos()) << space;
for (auto j = aypos(); j < aypos() + m_padd.top; j++) {
set_cursor(axpos(), j) << space;
}
for (sz_t i = m_ypos; i < aypos() + ahgt(); i++) {
set_cursor(i, axpos()) << space;
for (auto j = m_ypos; j < aypos() + ahgt(); j++) {
set_cursor(axpos(), j) << space;
}
std::cout << std::flush;
}

@@ -26,8 +26,8 @@ void Window::clear() const

std::cout << alec::background_v<alec::Color::DEFAULT>;
std::cout << alec::foreground_v<alec::Color::DEFAULT>;
for (sz_t i = 0; i < aypos() + ahgt(); i++) {
set_cursor(i, axpos()) << std::string(awth(), ' ');
for (auto j = ypos_t(0); j < aypos() + ahgt(); j++) {
set_cursor(axpos(), j) << std::string(awth().value(), ' ');
}
std::cout << std::flush;

@@ -46,36 +46,36 @@ std::ostream& Window::line_next() const

return null;
}
return set_cursor(m_ypos++, axpos());
return set_cursor(axpos(), m_ypos++);
}
void Window::line_empty() const
{
line_next() << std::string(awth(), ' ');
line_next() << std::string(awth().value(), ' ');
}
void Window::line_left(const std::string& text) const
{
const auto left = std::string(m_padd.left, ' ');
const auto right = std::string(m_padd.right, ' ');
const auto left = std::string(m_padd.left.value(), ' ');
const auto right = std::string(m_padd.right.value(), ' ');
line_next() << left << std::format("{:<{}}", text, wth()) << right;
line_next() << left << std::format("{:<{}}", text, wth().value()) << right;
}
void Window::line_center(const std::string& text) const
{
const auto left = std::string(m_padd.left, ' ');
const auto right = std::string(m_padd.right, ' ');
const auto left = std::string(m_padd.left.value(), ' ');
const auto right = std::string(m_padd.right.value(), ' ');
line_next() << left << std::format("{:^{}}", text, wth()) << right;
line_next() << left << std::format("{:^{}}", text, wth().value()) << right;
}
void Window::line_right(const std::string& text) const
{
const auto left = std::string(m_padd.left, ' ');
const auto right = std::string(m_padd.right, ' ');
const auto left = std::string(m_padd.left.value(), ' ');
const auto right = std::string(m_padd.right.value(), ' ');
line_next() << left << std::format("{:>{}}", text, wth()) << right;
line_next() << left << std::format("{:>{}}", text, wth().value()) << right;
}
} // namespace display

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

@@ -5,50 +5,43 @@

namespace display
{
place_t WindowPivot::place(place_t aplc, piv_t piv, dim_t dim)
plc_t WindowPivot::place(plc_t aplc, piv_t piv, dim_t dim)
{
const auto [cols, rows] = aplc.dim;
const sz_t colsh = cols / 2;
const sz_t rowsh = rows / 2;
const auto [awth, ahth] = aplc.dim;
const auto [wth, hgt] = dim;
const auto [wdth, hght] = dim;
const sz_t wdthm = wdth / 2;
const sz_t hghtm = hght / 2;
const sz_t zero = 0;
pos_t start;
pos_t end;
dim_t start(0, 0);
dim_t end(0, 0);
using display::add_lim, display::sub_lim;
switch (piv.x) {
case PvtX::Left:
start.x = 0;
end.x = add_lim(start.x, wdth, cols);
start.width = wth_t(0);
end.width = add_lim(start.width, wth, awth);
break;
case PvtX::Center:
start.x = sub_lim(colsh, wdthm, zero);
end.x = add_lim(start.x, wdth, cols);
start.width = sub_lim((awth / 2), (wth / 2), wth_t(0));
end.width = add_lim(start.width, wth, awth);
break;
case PvtX::Right:
end.x = cols;
start.x = sub_lim(end.x, wdth, zero);
end.width = awth;
start.width = sub_lim(end.width, wth, wth_t(0));
break;
}
switch (piv.y) {
case PvtY::Top:
start.y = 0;
end.y = add_lim(start.y, hght, rows);
start.height = hgt_t(0);
end.height = add_lim(start.height, hgt, ahth);
break;
case PvtY::Center:
start.y = sub_lim(rowsh, hghtm, zero);
end.y = add_lim(start.y, hght, rows);
start.height = sub_lim((ahth / 2), (hgt / 2), hgt_t(0));
end.height = add_lim(start.height, hgt, ahth);
break;
case PvtY::Bottom:
end.y = rows;
start.y = sub_lim(end.y, hght, zero);
end.height = ahth;
start.height = sub_lim(end.height, hgt, hgt_t(0));
break;
}

diff --git a/test/source/utility_test.cpp b/test/source/utility_test.cpp

@@ -1,12 +1,13 @@

#include <cstdint>
#include <limits>
#include "display/utility.hpp"
#include "display/types.hpp"
int main()
{
using namespace display; // NOLINT
using sz_t = std::uint16_t;
using lim = std::numeric_limits<sz_t>;
static constexpr const sz_t zero = 0;