summaryrefslogblamecommitdiffstats
path: root/include/straw.hpp
blob: c147b0670e38224939428584a2848896756611a8 (plain) (tree)























































































































































































































































































































                                                                                                                                                                 
#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