2023-12-25 16:26:13 +00:00
|
|
|
#include "terml_private.h"
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include "terml_windows.h"
|
|
|
|
#else
|
|
|
|
#include "terml_linux.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <cstdio>
|
|
|
|
#include <cstring>
|
|
|
|
|
2024-03-08 20:44:27 +00:00
|
|
|
namespace
|
|
|
|
{
|
2024-03-09 18:17:03 +00:00
|
|
|
constexpr int BYTE_ONE_MASK = 0b000000000000000000111111;
|
|
|
|
constexpr int BYTE_TWO_MASK = 0b000000000000111111000000;
|
|
|
|
constexpr int BYTE_THREE_MASK = 0b000000111111000000000000;
|
|
|
|
constexpr int BYTE_FOUR_MASK = 0b000111000000000000000000;
|
2024-03-08 20:44:27 +00:00
|
|
|
|
|
|
|
constexpr int BYTE_ONE_MASK_OFFSET = 0;
|
|
|
|
constexpr int BYTE_TWO_MASK_OFFSET = BYTE_ONE_MASK_OFFSET + 2;
|
|
|
|
constexpr int BYTE_THREE_MASK_OFFSET = BYTE_TWO_MASK_OFFSET + 2;
|
|
|
|
constexpr int BYTE_FOUR_MASK_OFFSET = BYTE_THREE_MASK_OFFSET + 2;
|
|
|
|
|
2024-03-09 18:17:03 +00:00
|
|
|
constexpr int ONE_BYTE_FILL = 0b10000000;
|
|
|
|
constexpr int TWO_BYTE_FILL = 0b1100000010000000;
|
|
|
|
constexpr int THREE_BYTE_FILL = 0b111000001000000010000000;
|
|
|
|
constexpr int FOUR_BYTE_FILL = 0b11110000100000001000000010000000;
|
2024-03-08 20:44:27 +00:00
|
|
|
|
2024-03-19 20:02:39 +00:00
|
|
|
void print_cell_impl(tcell cell)
|
2024-03-08 20:44:27 +00:00
|
|
|
{
|
|
|
|
// one-byte codepoints
|
|
|
|
if (cell.codepoint < 0x80)
|
|
|
|
{
|
|
|
|
const char str[] = { cell.codepoint, '\0' };
|
|
|
|
printf("%s", str);
|
|
|
|
}
|
|
|
|
// two-byte codepoints
|
|
|
|
else if (cell.codepoint < 0x800)
|
|
|
|
{
|
|
|
|
const int byte_one = (cell.codepoint & BYTE_ONE_MASK) << BYTE_ONE_MASK_OFFSET;
|
|
|
|
const int byte_two = (cell.codepoint & BYTE_TWO_MASK) << BYTE_TWO_MASK_OFFSET;
|
|
|
|
const int utf8 = TWO_BYTE_FILL | byte_one | byte_two;
|
|
|
|
|
|
|
|
const char str[] = { (utf8 & 0xFF00) >> 8, utf8 & 0xFF, '\0' };
|
|
|
|
printf("%s", str);
|
|
|
|
}
|
|
|
|
// three-byte codepoints
|
|
|
|
else if (cell.codepoint < 0x10000)
|
|
|
|
{
|
|
|
|
const int byte_one = (cell.codepoint & BYTE_ONE_MASK) << BYTE_ONE_MASK_OFFSET;
|
|
|
|
const int byte_two = (cell.codepoint & BYTE_TWO_MASK) << BYTE_TWO_MASK_OFFSET;
|
|
|
|
const int byte_three = (cell.codepoint & BYTE_THREE_MASK) << BYTE_THREE_MASK_OFFSET;
|
|
|
|
const int utf8 = THREE_BYTE_FILL | byte_one | byte_two | byte_three;
|
|
|
|
|
|
|
|
const char str[] = { (utf8 & 0xFF0000) >> 16, (utf8 & 0xFF00) >> 8, utf8 & 0xFF, '\0' };
|
|
|
|
printf("%s", str);
|
|
|
|
}
|
|
|
|
// four-byte codepoints
|
|
|
|
else if (cell.codepoint < 0x110000)
|
|
|
|
{
|
|
|
|
const int byte_one = (cell.codepoint & BYTE_ONE_MASK) << BYTE_ONE_MASK_OFFSET;
|
|
|
|
const int byte_two = (cell.codepoint & BYTE_TWO_MASK) << BYTE_TWO_MASK_OFFSET;
|
|
|
|
const int byte_three = (cell.codepoint & BYTE_THREE_MASK) << BYTE_THREE_MASK_OFFSET;
|
|
|
|
const int byte_four = (cell.codepoint & BYTE_FOUR_MASK) << BYTE_FOUR_MASK_OFFSET;
|
|
|
|
const int utf8 = FOUR_BYTE_FILL | byte_one | byte_two | byte_three | byte_four;
|
|
|
|
|
|
|
|
const char str[] = { (utf8 & 0xFF000000) >> 24, (utf8 & 0xFF0000) >> 16, (utf8 & 0xFF00) >> 8, utf8 & 0xFF, '\0' };
|
|
|
|
printf("%s", str);
|
|
|
|
}
|
|
|
|
}
|
2024-03-19 18:59:05 +00:00
|
|
|
|
2024-04-15 21:59:59 +00:00
|
|
|
void print_cell(tcell cell, const tcell* last = nullptr)
|
2024-03-19 20:02:39 +00:00
|
|
|
{
|
2024-04-15 21:59:59 +00:00
|
|
|
if (!last || cell.foreground != last->foreground)
|
2024-03-19 18:59:05 +00:00
|
|
|
{
|
2024-03-19 20:03:08 +00:00
|
|
|
printf(FG(%d, %d, %d), (cell.foreground & 0xFF0000) >> 16, (cell.foreground & 0xFF00) >> 8, cell.foreground & 0xFF);
|
2024-03-19 18:59:05 +00:00
|
|
|
}
|
2024-04-15 21:59:59 +00:00
|
|
|
if (!last || cell.background != last->background)
|
2024-03-19 18:59:05 +00:00
|
|
|
{
|
2024-03-19 20:03:08 +00:00
|
|
|
printf(BG(%d, %d, %d), (cell.background & 0xFF0000) >> 16, (cell.background & 0xFF00) >> 8, cell.background & 0xFF);
|
2024-03-19 18:59:05 +00:00
|
|
|
}
|
|
|
|
|
2024-03-19 20:02:39 +00:00
|
|
|
print_cell_impl(cell);
|
2024-03-19 18:59:05 +00:00
|
|
|
}
|
2024-04-15 21:59:59 +00:00
|
|
|
|
|
|
|
bool operator==(const tcell& a, const tcell& b)
|
|
|
|
{
|
|
|
|
return
|
|
|
|
a.background == b.background &&
|
|
|
|
a.foreground == b.foreground &&
|
|
|
|
a.codepoint == b.codepoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator!=(const tcell& a, const tcell& b)
|
|
|
|
{
|
|
|
|
return
|
|
|
|
a.background != b.background ||
|
|
|
|
a.foreground != b.foreground ||
|
|
|
|
a.codepoint != b.codepoint;
|
|
|
|
}
|
2024-03-08 20:44:27 +00:00
|
|
|
}
|
|
|
|
|
2023-12-25 16:26:13 +00:00
|
|
|
terml::terml() :
|
2024-03-08 20:44:27 +00:00
|
|
|
cells(nullptr),
|
|
|
|
width(0),
|
|
|
|
height(0),
|
2023-12-25 16:26:13 +00:00
|
|
|
main(nullptr),
|
|
|
|
quit(nullptr),
|
|
|
|
key(nullptr),
|
2023-12-25 22:38:44 +00:00
|
|
|
resize(nullptr),
|
2024-03-08 20:44:27 +00:00
|
|
|
should_quit(false),
|
|
|
|
really_should_quit(false)
|
2023-12-25 16:26:13 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
terml::~terml()
|
|
|
|
{
|
2024-03-08 20:44:27 +00:00
|
|
|
if (cells) { delete[] cells; }
|
2023-12-25 16:26:13 +00:00
|
|
|
}
|
|
|
|
|
2024-05-03 14:15:35 +00:00
|
|
|
tcell terml::get(unsigned int x, unsigned int y) const
|
2023-12-25 16:26:13 +00:00
|
|
|
{
|
2024-04-15 21:59:59 +00:00
|
|
|
return cells[x + y * width].cell;
|
2023-12-25 16:26:13 +00:00
|
|
|
}
|
|
|
|
|
2024-03-08 20:44:27 +00:00
|
|
|
void terml::set(unsigned int x, unsigned int y, tcell cell)
|
2023-12-25 16:26:13 +00:00
|
|
|
{
|
2024-04-15 21:59:59 +00:00
|
|
|
cells[x + y * width].dirty = (cell != cells[x + y * width].cell);
|
|
|
|
cells[x + y * width].cell = cell;
|
2023-12-25 16:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void terml::flush() const
|
|
|
|
{
|
2024-04-15 21:59:59 +00:00
|
|
|
const tcell* last = nullptr;
|
|
|
|
if (cells[0].dirty)
|
|
|
|
{
|
|
|
|
printf(CUP(1, 1));
|
|
|
|
print_cell(cells[0].cell);
|
|
|
|
cells[0].dirty = false;
|
|
|
|
|
|
|
|
last = &cells[0].cell;
|
|
|
|
}
|
2024-03-08 20:44:27 +00:00
|
|
|
for (int i = 1; i < width * height; i++)
|
|
|
|
{
|
2024-03-19 18:59:05 +00:00
|
|
|
const unsigned int x = i % width;
|
|
|
|
const unsigned int y = i / width;
|
|
|
|
|
2024-04-15 21:59:59 +00:00
|
|
|
if (cells[i].dirty)
|
|
|
|
{
|
|
|
|
printf(CUP(%d, %d), y + 1, x + 1);
|
|
|
|
print_cell(cells[i].cell, last);
|
|
|
|
cells[i].dirty = false;
|
|
|
|
|
|
|
|
last = &cells[i].cell;
|
|
|
|
}
|
2024-03-08 20:44:27 +00:00
|
|
|
}
|
2024-03-19 18:59:05 +00:00
|
|
|
|
2023-12-25 16:26:13 +00:00
|
|
|
fflush(stdout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml::set_main_callback(terml_main_callback callback)
|
|
|
|
{
|
|
|
|
main = callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml::set_quit_callback(terml_quit_callback callback)
|
|
|
|
{
|
|
|
|
quit = callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml::set_key_callback(terml_key_callback callback)
|
|
|
|
{
|
|
|
|
key = callback;
|
|
|
|
}
|
|
|
|
|
2023-12-25 22:38:44 +00:00
|
|
|
void terml::set_resize_callback(terml_resize_callback callback)
|
|
|
|
{
|
|
|
|
resize = callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml::key_event(char code) const
|
2023-12-25 16:26:13 +00:00
|
|
|
{
|
|
|
|
if (key)
|
|
|
|
{
|
|
|
|
key(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml::mainloop()
|
|
|
|
{
|
|
|
|
should_quit = false;
|
|
|
|
really_should_quit = false;
|
|
|
|
|
2024-03-08 20:44:27 +00:00
|
|
|
const unsigned long long wait_time = timer_frequency() / 60;
|
2023-12-25 16:26:13 +00:00
|
|
|
unsigned long long last_time = timer();
|
|
|
|
|
|
|
|
while (!really_should_quit)
|
|
|
|
{
|
|
|
|
unsigned long long current_time = timer();
|
|
|
|
|
|
|
|
while (current_time >= last_time + wait_time)
|
|
|
|
{
|
2023-12-25 22:38:44 +00:00
|
|
|
if (main)
|
|
|
|
{
|
|
|
|
main();
|
|
|
|
}
|
2023-12-25 16:26:13 +00:00
|
|
|
|
|
|
|
process_events();
|
|
|
|
|
2024-02-28 19:14:46 +00:00
|
|
|
last_time = current_time;
|
2023-12-25 16:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (should_quit)
|
|
|
|
{
|
|
|
|
if (!quit || quit())
|
|
|
|
{
|
|
|
|
really_should_quit = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
should_quit = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml::stop()
|
|
|
|
{
|
|
|
|
should_quit = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int terml::get_width() const
|
|
|
|
{
|
|
|
|
return width;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int terml::get_height() const
|
|
|
|
{
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml::setup_buffer()
|
|
|
|
{
|
|
|
|
printf(CUP(999, 999) REPORT_CUSROR_POSITION());
|
|
|
|
fflush(stdout);
|
|
|
|
|
|
|
|
constexpr const unsigned int STDIN_BUFFER_SIZE = 16;
|
|
|
|
char stdin_buffer[STDIN_BUFFER_SIZE + 1];
|
|
|
|
read_stdin(stdin_buffer, STDIN_BUFFER_SIZE);
|
|
|
|
|
|
|
|
stdin_buffer[STDIN_BUFFER_SIZE] = '\0';
|
|
|
|
|
2024-03-07 16:47:41 +00:00
|
|
|
const unsigned int new_width = width;
|
|
|
|
const unsigned int new_height = height;
|
2023-12-25 16:26:13 +00:00
|
|
|
const int scanned = sscanf(stdin_buffer, CURSOR_POSITION_FORMAT(), &new_height, &new_width);
|
|
|
|
if (scanned != 2)
|
|
|
|
{
|
|
|
|
throw "Failed to determine screen size.";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (width != new_width || height != new_height)
|
|
|
|
{
|
2023-12-25 22:38:44 +00:00
|
|
|
const unsigned int old_width = width;
|
|
|
|
const unsigned int old_height = height;
|
|
|
|
|
2023-12-25 16:26:13 +00:00
|
|
|
width = new_width;
|
|
|
|
height = new_height;
|
|
|
|
|
2024-03-08 20:44:27 +00:00
|
|
|
if (cells) { delete[] cells; }
|
2024-04-15 21:59:59 +00:00
|
|
|
cells = new tcelld[width * height];
|
|
|
|
memset(cells, 0, sizeof(tcelld) * width * height);
|
2023-12-25 22:38:44 +00:00
|
|
|
|
|
|
|
if (resize)
|
|
|
|
{
|
2024-03-07 16:47:41 +00:00
|
|
|
resize(old_width, old_height);
|
2023-12-25 22:38:44 +00:00
|
|
|
}
|
2023-12-25 16:26:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-08 20:44:27 +00:00
|
|
|
void terml::set_console_settings()
|
|
|
|
{
|
|
|
|
setvbuf(stdout, nullptr, _IOFBF, BUFSIZ * BUFSIZ);
|
2024-03-19 18:59:05 +00:00
|
|
|
printf(ALT_BUF() HIDE_CURSOR());
|
2024-03-08 20:44:27 +00:00
|
|
|
fflush(stdout);
|
2024-03-19 18:59:05 +00:00
|
|
|
|
2024-03-08 20:44:27 +00:00
|
|
|
set_console_settings_impl();
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml::reset_console_settings()
|
|
|
|
{
|
|
|
|
setvbuf(stdout, nullptr, _IOLBF, BUFSIZ);
|
|
|
|
printf(REG_BUF() SHOW_CURSOR());
|
|
|
|
fflush(stdout);
|
|
|
|
|
|
|
|
reset_console_settings_impl();
|
|
|
|
}
|
|
|
|
|
2024-05-03 14:15:35 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
terml* TERML_G;
|
|
|
|
const char* LAST_ERROR = nullptr;
|
|
|
|
}
|
2023-12-25 16:26:13 +00:00
|
|
|
|
|
|
|
extern "C"
|
|
|
|
{
|
|
|
|
int terml_init()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (!TERML_G)
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
TERML_G = new terml_windows;
|
|
|
|
#else
|
|
|
|
TERML_G = new terml_linux;
|
|
|
|
#endif
|
|
|
|
}
|
2024-03-08 20:44:27 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
throw "terml already initialized.";
|
|
|
|
}
|
2023-12-25 16:26:13 +00:00
|
|
|
|
|
|
|
TERML_G->set_console_settings();
|
|
|
|
TERML_G->setup_buffer();
|
|
|
|
|
2024-03-08 20:44:27 +00:00
|
|
|
return 0;
|
2023-12-25 16:26:13 +00:00
|
|
|
}
|
|
|
|
catch (const char* c)
|
|
|
|
{
|
|
|
|
LAST_ERROR = c;
|
2024-03-08 20:44:27 +00:00
|
|
|
return 1;
|
2023-12-25 16:26:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int terml_deinit()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (TERML_G)
|
|
|
|
{
|
|
|
|
TERML_G->reset_console_settings();
|
|
|
|
delete TERML_G;
|
|
|
|
}
|
|
|
|
|
2024-03-08 20:44:27 +00:00
|
|
|
return 0;
|
2023-12-25 16:26:13 +00:00
|
|
|
}
|
|
|
|
catch (const char* c)
|
|
|
|
{
|
|
|
|
LAST_ERROR = c;
|
2024-03-08 20:44:27 +00:00
|
|
|
return 1;
|
2023-12-25 16:26:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int terml_get_width()
|
|
|
|
{
|
|
|
|
return TERML_G->get_width();
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int terml_get_height()
|
|
|
|
{
|
|
|
|
return TERML_G->get_height();
|
|
|
|
}
|
|
|
|
|
2024-05-03 14:15:35 +00:00
|
|
|
tcell terml_get(unsigned int x, unsigned int y)
|
2023-12-25 16:26:13 +00:00
|
|
|
{
|
2024-03-08 20:44:27 +00:00
|
|
|
if (x >= TERML_G->get_width() || y >= TERML_G->get_height())
|
|
|
|
{
|
|
|
|
LAST_ERROR = "Coordinates out of bounds.";
|
2024-05-03 14:15:35 +00:00
|
|
|
return { 0, 0, 0 };
|
2024-03-08 20:44:27 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-05-03 14:15:35 +00:00
|
|
|
return TERML_G->get(x, y);
|
2024-03-08 20:44:27 +00:00
|
|
|
}
|
2023-12-25 16:26:13 +00:00
|
|
|
}
|
|
|
|
|
2024-05-03 14:15:35 +00:00
|
|
|
void terml_set(unsigned int x, unsigned int y, tcell cell)
|
2023-12-25 16:26:13 +00:00
|
|
|
{
|
2024-03-08 20:44:27 +00:00
|
|
|
if (x >= TERML_G->get_width() || y >= TERML_G->get_height())
|
|
|
|
{
|
|
|
|
LAST_ERROR = "Coordinates out of bounds.";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TERML_G->set(x, y, cell);
|
|
|
|
}
|
2023-12-25 16:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void terml_flush()
|
|
|
|
{
|
|
|
|
TERML_G->flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml_set_main_callback(terml_main_callback callback)
|
|
|
|
{
|
|
|
|
TERML_G->set_main_callback(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml_set_quit_callback(terml_quit_callback callback)
|
|
|
|
{
|
|
|
|
TERML_G->set_quit_callback(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml_set_key_callback(terml_key_callback callback)
|
|
|
|
{
|
|
|
|
TERML_G->set_key_callback(callback);
|
|
|
|
}
|
|
|
|
|
2023-12-25 22:38:44 +00:00
|
|
|
void terml_set_resize_callback(terml_resize_callback callback)
|
|
|
|
{
|
|
|
|
TERML_G->set_resize_callback(callback);
|
|
|
|
}
|
|
|
|
|
2023-12-25 16:26:13 +00:00
|
|
|
void terml_start()
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
TERML_G->mainloop();
|
|
|
|
}
|
|
|
|
catch (const char* c)
|
|
|
|
{
|
|
|
|
LAST_ERROR = c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void terml_stop()
|
|
|
|
{
|
|
|
|
TERML_G->stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* terml_get_error()
|
|
|
|
{
|
|
|
|
const char* err = LAST_ERROR;
|
|
|
|
LAST_ERROR = nullptr;
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|