#ifndef STRAW_HPP
#define STRAW_HPP 1
#include <sstream>
#include <cstdint>
#include <cstddef>
#include <vector>
#include <span>
#include <string>
#include <concepts>
#include <cassert>
#include <iostream>
#include <algorithm>
namespace straw
{
struct color {
std::uint8_t r{}, g{}, b{};
constexpr color(std::uint8_t R, std::uint8_t G, std::uint8_t B) : r(R), g(G), b(B) {}
constexpr color(std::uint8_t A) : r(A), g(A), b(A) {}
constexpr uint32_t single() { return (uint32_t)r << 16 | (uint32_t)g << 8 | (uint32_t)b; }
constexpr bool operator==(const color &o) const = default;
};
static constexpr color WHITE{255, 255, 255};
static constexpr color BLACK{0, 0, 0};
struct attribs {
color bg{0, 0, 0}, fg{255, 255, 255};
bool bold, underline;
constexpr attribs() = default;
constexpr attribs(color Fg) : fg(Fg) {}
constexpr attribs(color Bg, color Fg) : bg(Bg), fg(Fg) {}
constexpr attribs(color Bg, color Fg, bool B, bool U) : bg(Bg), fg(Fg), bold(B), underline(U) {}
constexpr bool operator==(const attribs &o) const = default;
};
template<typename T>
concept char_type = std::integral<T>;
template<char_type chartype>
struct cell {
chartype chr{};
attribs attr{};
constexpr cell() = default;
constexpr cell(chartype Chr) : chr(Chr) {}
constexpr cell(chartype Chr, color Fg) : chr(Chr), attr(Fg) {}
constexpr cell(chartype Chr, color Bg, color Fg) : chr(Chr), attr(Bg, Fg) {}
constexpr cell(chartype Chr, color Bg, color Fg, bool B, bool U) : chr(Chr), attr(Bg, Fg, B, U) {}
constexpr cell(chartype Chr, attribs Attr) : chr(Chr), attr(Attr) {}
constexpr bool operator==(const cell &o) const = default;
};
const std::string ANSI_ESCAPE = "\E[";
static void ANSI_MOVE(unsigned x, unsigned y) { std::cout << ANSI_ESCAPE << y+1 << ';' << x+1 << 'H' << std::flush; }
static void ANSI_COLOR_FG(color c) { std::cout << ANSI_ESCAPE << "38;2;" << (unsigned)c.r << ';' << (unsigned)c.g << ';' << (unsigned)c.b << 'm' << std::flush; }
static void ANSI_COLOR_BG(color c) { std::cout << ANSI_ESCAPE << "48;2;" << (unsigned)c.r << ';' << (unsigned)c.g << ';' << (unsigned)c.b << 'm' << std::flush; }
class screen_command_base {};
template<char_type chartype>
class screen {
public:
explicit screen(unsigned X, unsigned Y, unsigned W, unsigned H, chartype C, color B, color F) :
m_x(X), m_y(Y), m_width(W), m_height(H), m_front(W * H, cell{C, B, F}), m_back(m_front),
m_cursorAttribs(B,F), m_fillChar(C)
{
redraw();
}
explicit screen(unsigned X, unsigned Y, unsigned W, unsigned H, chartype C) :
screen(X, Y, W, H, C, color{0, 0, 0}, color{255, 255, 255}) {}
explicit screen(unsigned X, unsigned Y, unsigned W, unsigned H) :
screen(X, Y, W, H, ' ') {}
constexpr void setcursorxy(unsigned x, unsigned y) { m_cursorX = x; m_cursorY = y; }
constexpr void setcursorfg(uint8_t r, uint8_t g, uint8_t b) { m_cursorAttribs.fg = color{r, g, b}; }
constexpr void setcursorbg(uint8_t r, uint8_t g, uint8_t b) { m_cursorAttribs.bg = color{r, g, b}; }
constexpr void setcursorfg(color c) { m_cursorAttribs.fg = c; }
constexpr void setcursorbg(color c) { m_cursorAttribs.bg = c; }
constexpr void setcursorbold(bool bold) { m_cursorAttribs.bold = bold; }
constexpr void setcursorunderline(bool underline) { m_cursorAttribs.underline = underline; }
constexpr unsigned getcursorx() { return m_cursorX; }
constexpr unsigned getcursory() { return m_cursorY; }
constexpr unsigned getx() { return m_x; }
constexpr unsigned gety() { return m_y; }
constexpr unsigned getwidth() { return m_width; }
constexpr unsigned getheight() { return m_height; }
void clear(const chartype c) { std::fill(m_front.begin(), m_front.end(), cell{c, m_cursorAttribs}); }
void clearrow(unsigned y, const chartype c) {
assert(y < m_height);
std::fill(m_front.begin() + (y * m_width),
m_front.begin() + (y * m_width) + m_width,
cell{c, m_cursorAttribs});
}
void scroll() {
m_cursorY = m_height - 1;
std::copy(m_front.begin() + m_width, m_front.end(), m_front.begin());
std::fill(m_front.end() - m_width, m_front.end(), cell{m_fillChar, m_cursorAttribs});
}
void setc(unsigned x, unsigned y, chartype c) {
assert(x < m_width);
assert(y < m_height);
m_front[x + (y * m_width)] = cell{c, m_cursorAttribs};
}
void putc(chartype c){
if(m_cursorY == m_height) {
scroll();
}
switch(c) {
case '\n':
m_cursorX = 0; m_cursorY++;
break;
default:
(*this)[m_cursorY][m_cursorX++] =
cell{c, m_cursorAttribs};
break;
}
if(m_cursorX == m_width) {
m_cursorY++;
m_cursorX = 0;
}
}
void puts(const std::basic_string<chartype> &s){
for(const chartype &c : s) this->putc(c);
}
void redraw() {
attribs cattr = (*this)[0][0].attr;
ANSI_COLOR_BG(cattr.bg);
ANSI_COLOR_FG(cattr.fg);
for(unsigned y = 0; y < m_height; y++) {
ANSI_MOVE(m_x, m_y + y);
for(cell c : (*this)[y]) {
if(cattr != c.attr) {
cattr = c.attr;
ANSI_COLOR_FG(cattr.fg);
ANSI_COLOR_BG(cattr.bg);
}
std::cout << c.chr;
}
std::cout.clear();
}
m_back = m_front;
}
void flush() {
attribs currAttr = m_front[0].attr;
ANSI_COLOR_FG(currAttr.fg);
ANSI_COLOR_BG(currAttr.bg);
for(unsigned y = 0; y < m_height; y++) {
auto backspan = std::span<cell<chartype>>(
m_back.begin() + (y * m_width),
m_back.begin() + (y * m_width) + m_width);
auto frontspan = (*this)[y];
bool change = false;
if(std::equal(backspan.begin(), backspan.end(), frontspan.begin(), frontspan.end())) continue;
for(unsigned x = 0; x < m_width; x++) {
cell c = frontspan[x];
if(c == backspan[x]) continue;
ANSI_MOVE(x + m_x, y + m_y);
if(c.attr != currAttr) {
ANSI_COLOR_FG(c.attr.fg);
ANSI_COLOR_BG(c.attr.bg);
currAttr = c.attr;
}
change = true;
std::cout << c.chr;
}
if(change) std::cout.clear();
}
m_back = m_front;
}
std::span<cell<chartype>> operator[](std::size_t i) {
assert(i < m_width * m_height);
return std::span<cell<chartype>>(
m_front.begin() + (i * m_width),
m_front.begin() + (i * m_width) + m_width);
}
template<typename T>
requires(!std::is_base_of<screen_command_base, T>::value)
friend screen &operator<<(screen &o, const T &rhs) {
std::basic_stringstream<chartype> ss;
ss << rhs;
o.puts(ss.str());
return o;
}
private:
unsigned m_x{}, m_y{};
unsigned m_width{}, m_height{};
std::vector<cell<chartype>> m_front;
std::vector<cell<chartype>> m_back;
unsigned m_cursorX{}, m_cursorY{};
attribs m_cursorAttribs;
chartype m_fillChar;
};
struct screen_command_flush : public screen_command_base {
explicit screen_command_flush() = default;
template<char_type T>
friend screen<T> &operator<<(screen<T> &o, const screen_command_flush &cmd) {
(void)cmd;
o.flush();
return o;
}
};
[[nodiscard]] static screen_command_flush flush() { return screen_command_flush{}; }
struct screen_command_redraw : public screen_command_base {
explicit screen_command_redraw() = default;
template<char_type T>
friend screen<T> &operator<<(screen<T> &o, const screen_command_redraw &cmd) {
(void)cmd;
o.redraw();
return o;
}
};
[[nodiscard]] static screen_command_redraw redraw() { return screen_command_redraw{}; }
template<char_type chartype>
struct screen_command_clear : public screen_command_base {
chartype c;
explicit screen_command_clear(chartype C) : c(C) {}
friend screen<chartype> &operator<<(screen<chartype> &o, const screen_command_clear &cmd) {
o.clear(cmd.c);
return o;
}
};
template<char_type chartype>
[[nodiscard]] static screen_command_clear<chartype> clear() { return screen_command_clear{chartype{}}; }
template<char_type chartype>
[[nodiscard]] static screen_command_clear<chartype> clear(chartype c) { return screen_command_clear{c}; }
struct screen_command_move : public screen_command_base {
unsigned x, y;
explicit screen_command_move(unsigned X, unsigned Y) : x(X), y(Y) {}
template<char_type T>
friend screen<T> &operator<<(screen<T> &o, const screen_command_move &cmd) {
o.setcursorxy(cmd.x, cmd.y);
return o;
}
};
[[nodiscard]] static screen_command_move move(unsigned x, unsigned y) { return screen_command_move{x, y}; }
template<char_type chartype>
struct screen_command_plot : public screen_command_base {
chartype c;
unsigned x, y;
explicit screen_command_plot(unsigned X, unsigned Y, chartype C) : c(C), x(X), y(Y) {}
friend screen<chartype> &operator<<(screen<chartype> &o, const screen_command_plot &cmd) {
o.setc(cmd.x, cmd.y, cmd.c);
return o;
}
};
template<char_type T>
[[nodiscard]] static screen_command_plot<T> plot(unsigned x, unsigned y, T c) { return screen_command_plot{x, y, c}; }
struct screen_command_recolor : public screen_command_base {
color fg, bg;
bool rfg, rbg;
explicit screen_command_recolor(color Fg, bool side) : fg(Fg), bg(Fg), rfg(side ? true : false), rbg(side ? false : true) {}
explicit screen_command_recolor(color Fg, color Bg) : fg(Fg), bg(Bg), rfg(true), rbg(true) {}
template<char_type T>
friend screen<T> &operator<<(screen<T> &o, const screen_command_recolor &cmd) {
if(cmd.rbg) o.setcursorbg(cmd.bg);
if(cmd.rfg) o.setcursorfg(cmd.fg);
return o;
}
};
[[nodiscard]] static screen_command_recolor setfg(uint8_t r, uint8_t g, uint8_t b) { return screen_command_recolor{color{r, g, b}, true}; }
[[nodiscard]] static screen_command_recolor setbg(uint8_t r, uint8_t g, uint8_t b) { return screen_command_recolor{color{r, g, b}, false}; }
[[nodiscard]] static screen_command_recolor setcolor(color fg, color bg) { return screen_command_recolor{fg, bg}; }
[[nodiscard]] static screen_command_recolor resetcolor() { return screen_command_recolor{WHITE, BLACK}; }
template<char_type chartype>
struct screen_command_fillrow : public screen_command_base {
unsigned row;
chartype c;
friend screen<chartype> &operator<<(screen<chartype> &o, const screen_command_fillrow &cmd) {
o.clearrow(cmd.row, cmd.c);
return o;
}
};
template<char_type T>
[[nodiscard]] static screen_command_fillrow<T> fillrow(unsigned row) { return screen_command_fillrow<T>{.row = row, .c = ' '}; }
template<char_type T>
[[nodiscard]] static screen_command_fillrow<T> fillrow(unsigned row, T c) { return screen_command_fillrow<T>{.row = row, .c = c}; }
}
#endif