summaryrefslogtreecommitdiffstats
path: root/include/straw.hpp
blob: c147b0670e38224939428584a2848896756611a8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
#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