#ifndef STRAW_HPP #define STRAW_HPP 1 #include #include #include #include #include #include #include #include #include #include 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; }; 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 concept char_type = std::integral; template 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;" << c.r << ';' << c.g << ';' << c.b << 'm' << std::flush; } static void ANSI_COLOR_BG(color c) { std::cout << ANSI_ESCAPE << "48;2;" << c.r << ';' << c.g << ';' << c.b << 'm' << std::flush; } class screen_command_base {}; template 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 setcursorbold(bool bold) { m_cursorAttribs.bold = bold; } constexpr void setcursorunderline(bool underline) { m_cursorAttribs.underline = underline; } constexpr unsigned getcursorx() { return m_cursorY; } 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 scroll() { m_cursorY = m_height - 1; m_front = std::vector>(m_front.begin() + m_width, m_front.end()); 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 &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(0, 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() { for(unsigned y = 0; y < m_height; y++) { auto backspan = std::span>( m_back.begin() + (y * m_width), m_back.begin() + (y * m_width) + m_width); auto frontspan = (*this)[y]; 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, y); if(c.attr != backspan[x].attr) { ANSI_COLOR_FG(c.attr.fg); ANSI_COLOR_BG(c.attr.bg); } std::cout << c.chr; } std::cout.clear(); } m_back = m_front; } std::span> operator[](std::size_t i) { assert(i < m_width * m_height); return std::span>( m_front.begin() + (i * m_width), m_front.begin() + (i * m_width) + m_width); } template requires(!std::is_base_of::value) friend screen &operator<<(screen &o, const T &rhs) { std::basic_stringstream ss; ss << rhs; o.puts(ss.str()); return o; } private: unsigned m_x{}, m_y{}; unsigned m_width{}, m_height{}; std::vector> m_front; std::vector> 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 friend screen &operator<<(screen &o, const screen_command_flush &cmd) { (void)cmd; o.flush(); return o; } }; [[nodiscard]] static screen_command_flush flush() { return screen_command_flush{}; } template struct screen_command_clear : public screen_command_base { chartype c; explicit screen_command_clear(chartype C) : c(C) {} friend screen &operator<<(screen &o, const screen_command_clear &cmd) { o.clear(cmd.c); return o; } }; template [[nodiscard]] static screen_command_clear clear() { return screen_command_clear{chartype{}}; } template [[nodiscard]] static screen_command_clear 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 friend screen &operator<<(screen &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 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 &operator<<(screen &o, const screen_command_plot &cmd) { o.setc(cmd.x, cmd.y, cmd.c); return o; } }; template [[nodiscard]] static screen_command_plot 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 friend screen &operator<<(screen &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}; } }; #endif