diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..bea97fa --- /dev/null +++ b/test.cpp @@ -0,0 +1,145 @@ +#include "tui.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace tui::text::style; +using namespace tui::text::color; + +namespace tui { + + class Box { + public: + enum class Style { Empty, Light, Heavy, Double, Dashed, Rounded }; + enum class Align { TopLeft, TopRight, BottomLeft, BottomRight, Center, Custom }; + Box(int width, int height, Style style = Style::Empty, std::string title = "") + : width(width), height(height), title(std::move(title)), style(style) { + init_box(); + } + + // void add_text(const std::string& text, Align alignment = Align::TopLeft, int x = 0, int y = 0) { + // texts.push_back({text, alignment, x, y}); + // } + + void init_box() { + const Box_chars& chars = styles.at(style); + + // Top border + + // Side borders + + // Bottom border + }; + void print() {} + + inline int get_width() const { return this->width; } + inline void set_width(unsigned width) { this->width = width; } + inline int get_height() const { return this->width; } + inline void set_height(unsigned height) { this->height = height; } + + private: + struct Box_chars { + std::string corners[4]; + std::string horizontal; + std::string vertical; + }; + + struct Text { + std::string content; + Align alignment; + int x; + int y; + }; + + struct Coord { + int x; + int y; + }; + + int width; + int height; + + Coord astart; + Coord aend; + Coord rstart; + Coord rend; + + std::string title; + + Style style; + std::vector texts; + const std::unordered_map styles = { + {Style::Light, {{"┌", "┐", "┘", "└"}, "─", "│"}}, {Style::Heavy, {{"┏", "┓", "┛", "┗"}, "━", "┃"}}, + {Style::Double, {{"╔", "╗", "╝", "╚"}, "═", "║"}}, {Style::Dashed, {{"┌", "┐", "┘", "└"}, "┄", "┆"}}, + {Style::Empty, {{" ", " ", " ", " "}, " ", " "}}, {Style::Rounded, {{"╭", "╮", "╯", "╰"}, "─", "│"}}, + }; + + // int get_align(Align alignment) const { + // switch (alignment) { + // case Align::TopLeft: + // case Align::TopRight: + // return 0; + // case Align::BottomLeft: + // case Align::BottomRight: + // return height - 3; + // case Align::Center: + // return (height - 2) / 2; + // default: + // return 0; + // } + // } + + // std::string get_text(int line, const Box_chars& chars) const { + // const Text& text = texts[0]; // Simplification: handle only the first text for now + // int padding = (width - 2 - text.content.size()) / 2; + // switch (text.alignment) { + // case Align::TopLeft: + // case Align::BottomLeft: + // return text.content + std::string(width - 2 - text.content.size(), ' '); + // case Align::TopRight: + // case Align::BottomRight: + // return std::string(width - 2 - text.content.size(), ' ') + text.content; + // case Align::Center: + // return std::string(padding, ' ') + text.content + + // std::string(width - 2 - padding - text.content.size(), ' '); + // default: + // return text.content; + // } + // } + }; +} // namespace tui + +void handle_resize(int) { + tui::screen::clear(); +} + +int main() { + tui::init_term(false); + tui::set_up_resize(handle_resize); + + try { + int width = 20; + int height = 10; + tui::Box alma(width, height, tui::Box::Style::Rounded); + std::cerr << "main box init done\n"; + alma.print(); + + char car = '\0'; + // tui::cursor::set_position(); + std::cout << "q to quit"; + while (car != 'q') { + std::cin.get(car); + } + } + catch (...) { + tui::reset_term(); + std::cout << "ran into a problem"; + return 1; + } + tui::reset_term(); + return 0; +} diff --git a/tui.hpp b/tui.hpp index 1c66d41..106b288 100644 --- a/tui.hpp +++ b/tui.hpp @@ -3,7 +3,12 @@ #ifndef TUI_H #define TUI_H +#ifdef _WIN32 // windows +#include +#include +#endif #include +#include #include #include #include @@ -13,6 +18,92 @@ namespace tui { constexpr const char PURE_ESC = '\x1B'; // actually == "ESC[" almost everything starts with this constexpr const char* const ESC = "\x1B["; + + inline void enable_raw_mode() { +#ifdef _WIN32 + // Windows-specific code + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + if (hStdin == INVALID_HANDLE_VALUE) { + std::cerr << "Error getting the standard input handle." << std::endl; + exit(1); + } + + DWORD mode; + if (!GetConsoleMode(hStdin, &mode)) { + std::cerr << "Error getting the console mode." << std::endl; + exit(1); + } + + DWORD newMode = mode; + newMode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT); + + if (!SetConsoleMode(hStdin, newMode)) { + std::cerr << "Error setting the console to raw mode." << std::endl; + exit(1); + } +#else + // Unix-like systems specific code + // struct termios term {}; + // if (tcgetattr(STDIN_FILENO, &term) == -1) { + // std::cerr << "Error getting terminal attributes." << std::endl; + // exit(1); + // } + + // struct termios raw = term; + // raw.c_lflag &= ~(ICANON | ECHO); + + // if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { + // std::cerr << "Error setting terminal to raw mode." << std::endl; + // exit(1); + // } + + system("stty raw"); + system("stty -echo"); +#endif + } + + inline void disable_raw_mode() { +#ifdef _WIN32 + // Windows-specific code + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + if (hStdin == INVALID_HANDLE_VALUE) { + std::cerr << "Error getting the standard input handle." << std::endl; + exit(1); + } + + DWORD mode; + if (!GetConsoleMode(hStdin, &mode)) { + std::cerr << "Error getting the console mode." << std::endl; + exit(1); + } + + // Restore original mode + mode |= (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT); + if (!SetConsoleMode(hStdin, mode)) { + std::cerr << "Error restoring the console mode." << std::endl; + exit(1); + } +#else + // Unix-like systems specific code + system("stty -raw"); + system("stty echo"); + + // struct termios term {}; + // if (tcgetattr(STDIN_FILENO, &term) == -1) { + // std::cerr << "Error getting terminal attributes." << std::endl; + // exit(1); + // } + + // // Restore original attributes + // term.c_lflag |= (ICANON | ECHO); + + // if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &term) == -1) { + // std::cerr << "Error restoring terminal mode." << std::endl; + // exit(1); + // } +#endif + } + namespace cursor { // moves cursor up `n` rows inline void up(unsigned n = 1) { std::cout << ESC << n << "#A"; } @@ -32,8 +123,8 @@ namespace tui { // moves cursor to home position (0;0) inline void home() { std::cout << ESC << 'H'; } - // moves cursor to (`row`;`col`) - inline void move_to(unsigned row, unsigned col) { std::cout << ESC << row << ';' << col << 'H'; } + // moves cursor to (`row`;`col`), both start at 1 + inline void set_position(unsigned row, unsigned col) { std::cout << ESC << row << ';' << col << 'H'; } // moves cursor to column `n` inline void to_column(unsigned n) { std::cout << ESC << n << "#G"; } @@ -45,8 +136,27 @@ namespace tui { // set visibility inline void visible(bool visible) { std::cout << ESC << "?25" << (visible ? 'h' : 'l'); } - // check where the cursor is - inline void request_position() { std::cout << ESC << "6n"; } + // tell the terminal to check where the cursor is + inline void query_position() { std::cout << ESC << "6n"; } + + // (rows;cols) + inline std::pair get_position() { + query_position(); + std::flush(std::cout); + // Read the response: ESC [ rows ; cols R + char ch = 0; + unsigned rows = 0; + unsigned cols = 0; + if (std::cin.get(ch) && ch == PURE_ESC && std::cin.get(ch) && ch == '[') { + std::cin >> rows; + std::cin.get(ch); // skip the ';' + std::cin >> cols; + std::cin.ignore(); // skip the 'R' + } + + return {rows, cols}; + } + } // namespace cursor namespace screen { @@ -67,6 +177,12 @@ namespace tui { inline void scroll_up(unsigned n = 1) { std::cout << ESC << n << 'S'; } inline void scroll_down(unsigned n = 1) { std::cout << ESC << n << 'T'; } + + // get the size of the terminal: (rows;cols) + inline std::pair size() { + cursor::set_position(9999, 9999); // very huge position, that won't be reached, moves to biggest + return cursor::get_position(); + } } // namespace screen namespace text { @@ -106,8 +222,8 @@ namespace tui { #define make_stylizer(STYLE) stylize(STYLE) stylize_text(STYLE) stylize(reset); - inline std::string style_and_reset(const Style& st, const std::string& s) { - return concat(style(st), s, reset_style()); + inline std::string style_and_reset(const Style& st, const std::string& text) { + return concat(style(st), text, reset_style()); } make_stylizer(bold); @@ -118,6 +234,16 @@ namespace tui { make_stylizer(inverted); make_stylizer(invisible); make_stylizer(strikethrough); + + // printf '\e]8;;http://example.com\e\\This is a link\e]8;;\e\\\n' + // printf 'ESC]8;;{link}ESC\\{text}ESC]8;;ESC\\' + inline std::string link(const std::string& link, const std::string& text) { + return concat(PURE_ESC, "]8;;", link, PURE_ESC, "\\", text, PURE_ESC, "]8;;", PURE_ESC, "\\"); + } + inline std::string link(const char* link, const char* text) { + return concat(PURE_ESC, "]8;;", link, PURE_ESC, "\\", text, PURE_ESC, "]8;;", PURE_ESC, "\\"); + } + } // namespace style namespace color { @@ -186,6 +312,7 @@ namespace tui { class tui_string : public std::string { public: tui_string() = default; + template tui_string(T s) : std::string(tui::text::concat(s)) {} tui_string(const char* s) : std::string(s) {} tui_string(const std::string& s) : std::string(s) {} @@ -217,6 +344,7 @@ namespace tui { make_color(white); make_color(basic); + inline tui_string link(const char* link) { return text::style::link(link, *this); } inline tui_string rgb(unsigned r, unsigned g, unsigned b) const { return text::color::rgb(r, g, b, true, *this); } @@ -224,6 +352,37 @@ namespace tui { return text::color::rgb(r, g, b, false, *this); } }; + + // void handle_resize(int /*sig*/) { screen::clear(); } + using fn_ptr = void (*)(int); + // needs a void function, that takes an int, doesn't yet do anything on windows + inline void set_up_resize(fn_ptr handle_resize) { +#ifdef _WIN32 + // should implement logic for windows here +#else + // Register the signal handler for SIGWINCH + struct sigaction sa {}; + sa.sa_handler = handle_resize; + sa.sa_flags = SA_RESTART; // Restart functions if interrupted by handler + sigaction(SIGWINCH, &sa, nullptr); +#endif + } + + inline void init_term(bool enable_cursor) { + tui::enable_raw_mode(); + tui::cursor::visible(enable_cursor); + tui::screen::save_screen(); + tui::screen::alternative_buffer(true); + tui::screen::clear(); + tui::cursor::home(); + } + inline void reset_term() { + tui::disable_raw_mode(); + tui::screen::alternative_buffer(false); + tui::screen::restore_screen(); + tui::cursor::visible(true); + } + } // namespace tui #endif // TUI_H