From e8881f3a76afd056650fe6a82162e88e168cc677 Mon Sep 17 00:00:00 2001 From: Kittycannon Date: Mon, 20 Apr 2026 19:26:24 -0600 Subject: [PATCH] LOTS OF PROGRESS! --- .gitignore | 2 + desktoplib.code-workspace | 4 +- out.txt | 2 + src/desktoplib/main.cpp | 103 ++++++++ src/desktoplib/os/common.hpp | 24 ++ src/desktoplib/os/unix.cpp | 73 ++++++ src/desktoplib/os/windows.cpp | 70 ++++++ src/desktoplib/terminal/Terminal.cpp | 346 +++++++++++++++++++++++++++ src/desktoplib/terminal/Terminal.hpp | 161 +++++++++---- 9 files changed, 733 insertions(+), 52 deletions(-) create mode 100644 .gitignore create mode 100644 out.txt create mode 100644 src/desktoplib/main.cpp create mode 100644 src/desktoplib/os/common.hpp create mode 100644 src/desktoplib/os/unix.cpp create mode 100644 src/desktoplib/os/windows.cpp create mode 100644 src/desktoplib/terminal/Terminal.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..42a3cf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/out +/bin diff --git a/desktoplib.code-workspace b/desktoplib.code-workspace index 876a149..bbf19a1 100644 --- a/desktoplib.code-workspace +++ b/desktoplib.code-workspace @@ -4,5 +4,7 @@ "path": "." } ], - "settings": {} + "settings": { + "terminal.integrated.defaultProfile.windows": "MSYS2 UCRT", + } } \ No newline at end of file diff --git a/out.txt b/out.txt new file mode 100644 index 0000000..dd16664 --- /dev/null +++ b/out.txt @@ -0,0 +1,2 @@ +g++ -std=c++20 -O2 -Wall -Werror -Wextra -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wunused -Woverloaded-virtual -Wconversion -Wsign-conversion -Wnull-dereference -Wdouble-promotion -Wformat=2 -Wimplicit-fallthrough -Wsuggest-override -Wextra-semi -Wduplicated-cond -Wduplicated-branches -Wlogical-op -Wuseless-cast -I./src/ -c -o bin/desktoplib/terminal/Terminal.o src/desktoplib/terminal/Terminal.cpp +g++ -std=c++20 -static-libstdc++ -static-libgcc -Wl,--fatal-warnings -Wl,--warn-common -o out/out.exe bin/desktoplib/main.o bin/desktoplib/os/unix.o bin/desktoplib/os/windows.o bin/desktoplib/terminal/Terminal.o diff --git a/src/desktoplib/main.cpp b/src/desktoplib/main.cpp new file mode 100644 index 0000000..c1cafd0 --- /dev/null +++ b/src/desktoplib/main.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include + +int main() { + using namespace ckitty::terminal; + Terminal term; + + // 1. Setup the screen + term.altbuff(true) // Use alternative buffer (clean exit) + .cursor(false) // Hide cursor for cleaner UI + .cls() // Clear screen + .fill("\033[48;2;20;20;20m"); // Fill background with dark grey (RGB) + + // 2. Draw a Header using subprint and center + term << pos{1, 1} << backg::BLUE << color::WHITE << style::BOLD; + term.center(term.getSize().x, " TERMINAL LIBRARY TEST SUITE "); + term << style::RESET; + + // 3. Demonstrate Cartesian Positioning and Colors + pos center = { term.getSize().x / 2, term.getSize().y / 2 }; + + term << pos{center.x - 10, 5} << color::GREEN << "Green Text at (x-10, 5)" + << pos{center.x - 10, 6} << color::B_RED << style::ITALIC << "Bright Red Italic" + << style::RESET; + + // 4. Test RGB and HSL colors + term << pos{2, 10} << "RGB Test: "; + for (int i = 0; i < 5; ++i) { + term << color::rgb(50 * i, 255 - (50 * i), 150) << "■ "; + } + + term << pos{2, 11} << "HSL Test: "; + for (float h = 0.0f; h < 1.0f; h += 0.2f) { + term << color::hsl(h, 0.8f, 0.5f) << "■ "; + } + term << style::RESET; + + // 5. Test subprint_center + std::string long_msg = "This is a very long string that we will slice and center."; + term << pos{1, 15} << color::YELLOW; + // Centers only the word "long string" in a width of 40 + term.subprint_center(10, 21, long_msg); + term << style::RESET; + + // 6. Interaction Loop + term << pos{2, term.getSize().y - 2} << style::FAINT + << "Press 'q' to quit, or any arrow key to move the cursor..." << style::RESET; + + pos player = center; + bool running = true; + + while (running) { + // Draw "Player" + term << player << color::CYAN << "X"; + term.flush(); + + int k = term.getKey(); // Blocking wait + + // Erase old "Player" + term << player << " "; + + if (k == 'q' || k == key::ESC) { + running = false; + } else if (k == key::UP) { + player.y--; + term << color::GREEN << pos(3, 2) << "U"; + } else if (k == key::DOWN) { + player.y++; + term << color::GREEN << pos(3, 2) << "D"; + } else if (k == key::LEFT) { + player.x--; + term << color::GREEN << pos(3, 2) << "L"; + } else if (k == key::RIGHT) { + player.x++; + term << color::GREEN << pos(3, 2) << "R"; + } + term << color::RED; + term << pos(3, 3) << player.x; + term << pos(3, 4) << player.y; + + // Clamp player to screen bounds + pos sz = term.getSize(); + term << color::BLUE; + term << pos(3, 5) << sz.x; + term << pos(3, 6) << sz.y; + if (player.x < 1) player.x = 1; + if (player.y < 2) player.y = 2; // Keep below header + if (player.x > sz.x) player.x = sz.x; + if (player.y > sz.y) player.y = sz.y; + } + + // 7. Cleanup + term.altbuff(false) // Return to normal buffer + .cursor(true) // Show cursor again + .cls(); + + std::cout << "Test completed successfully.\n"; + + return 0; +} \ No newline at end of file diff --git a/src/desktoplib/os/common.hpp b/src/desktoplib/os/common.hpp new file mode 100644 index 0000000..9dc21f4 --- /dev/null +++ b/src/desktoplib/os/common.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace ckitty { + + namespace terminal { + + namespace os { + + int getKey(bool blocking); + + std::optional getChar(bool blocking); + + std::pair getSize(); + + void setRawMode(bool enable); + + } + + } + +} \ No newline at end of file diff --git a/src/desktoplib/os/unix.cpp b/src/desktoplib/os/unix.cpp new file mode 100644 index 0000000..5f2699c --- /dev/null +++ b/src/desktoplib/os/unix.cpp @@ -0,0 +1,73 @@ +#if defined(__unix__) || defined(__APPLE__) || defined(__linux__) + +#include "common.hpp" +#include +#include +#include +#include + +namespace ckitty { + namespace terminal { + namespace os { + + int getKey(bool blocking) { + if (!blocking) { + // Set non-blocking read + int oldf = fcntl(STDIN_FILENO, F_GETFL, 0); + fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); + unsigned char ch; + int n = read(STDIN_FILENO, &ch, 1); + fcntl(STDIN_FILENO, F_SETFL, oldf); + return (n > 0) ? ch : -1; + } + + unsigned char buf[3]; + int n = read(STDIN_FILENO, &buf, 3); + + if (n == 1) { + if (buf[0] == 127) return key::BACKSPACE; + return buf[0]; // Standard ASCII + } + + // Handle ANSI Escape Sequences (Arrows) + if (n == 3 && buf[0] == 0x1B && buf[1] == '[') { + switch (buf[2]) { + case 'A': return key::UP; + case 'B': return key::DOWN; + case 'C': return key::RIGHT; + case 'D': return key::LEFT; + } + } + + return key::UNKNOWN; + } + + std::optional getChar(bool blocking) { + int ch = getKey(blocking); + if (ch == -1) return std::nullopt; + return std::string(1, static_cast(ch)); + } + + std::pair getSize() { + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + return { w.ws_col, w.ws_row }; + } + + void setRawMode(bool enable) { + static struct termios oldt, newt; + if (enable) { + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); // Disable buffering and local echo + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + } else { + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + } + } + + } + } +} + +#endif \ No newline at end of file diff --git a/src/desktoplib/os/windows.cpp b/src/desktoplib/os/windows.cpp new file mode 100644 index 0000000..75ef3bc --- /dev/null +++ b/src/desktoplib/os/windows.cpp @@ -0,0 +1,70 @@ +#ifdef _WIN32 + +#include "common.hpp" +#include +#include + +#include + +namespace ckitty { + namespace terminal { + namespace os { + + int getKey(bool blocking) { + // Handle non-blocking check for the console + if (!blocking && !_kbhit()) { + return -1; + } + + // _getch() blocks by default + int ch = _getch(); + + // Check for "Extended" keys (Prefixes 0 or 224) + if (ch == 0 || ch == 224) { + int extended = _getch(); // Get the second byte + + switch (extended) { + case 72: return key::UP; + case 80: return key::DOWN; + case 75: return key::LEFT; + case 77: return key::RIGHT; + default: return key::UNKNOWN; + } + } + + // Handle standard keys and normalize them to your 'key' struct + switch (ch) { + case 13: return key::ENTER; // Windows uses CR (\r) for Enter + case 27: return key::ESC; // Escape + case 8: return key::BACKSPACE; // Backspace + default: return ch; // Standard ASCII (a, b, 1, 2, etc.) + } + } + + std::optional getChar(bool blocking) { + int ch = getKey(blocking); + if (ch == -1) return std::nullopt; + return std::string(1, static_cast(ch)); + } + + std::pair getSize() { + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); + return { csbi.srWindow.Right - csbi.srWindow.Left + 1, csbi.srWindow.Bottom - csbi.srWindow.Top + 1 }; + } + + void setRawMode(bool enable) { + // Windows CMD/Terminal usually handles _getch automatically, + // but for Virtual Terminal sequences: + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD dwMode = 0; + GetConsoleMode(hOut, &dwMode); + if (enable) dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(hOut, dwMode); + } + + } + } +} + +#endif \ No newline at end of file diff --git a/src/desktoplib/terminal/Terminal.cpp b/src/desktoplib/terminal/Terminal.cpp new file mode 100644 index 0000000..7cf12bc --- /dev/null +++ b/src/desktoplib/terminal/Terminal.cpp @@ -0,0 +1,346 @@ +#include "Terminal.hpp" + +#include +#include + +#include + +namespace ckitty { + + namespace terminal { + + static const auto CSI = "\x1B["; + + // ------ // + + pos::pos(int _x, int _y) + : x(_x), y(_y) { + } + + move::move(int _x, int _y) + : x(_x), y(_y) { + } + + // ------ // + + // Private Constructor + style::style(const std::string& c) : code(c) {} + + // Static Constants (Standard ANSI SGR codes) + const style style::RESET("\033[0m"); + const style style::BOLD("\033[1m"); + const style style::FAINT("\033[2m"); // Often called "dim" + const style style::ITALIC("\033[3m"); + const style style::STRIKE("\033[9m"); + + style::operator std::string_view() const { + return std::string_view(code); + } + + // ------ // + + // Constructor + backg::backg(const std::string& c) : code(c) {} + + // Standard Background Colors (40-47) + const backg backg::BLACK("\033[40m"); + const backg backg::RED("\033[41m"); + const backg backg::GREEN("\033[42m"); + const backg backg::YELLOW("\033[43m"); + const backg backg::BLUE("\033[44m"); + const backg backg::MAGENTA("\033[45m"); + const backg backg::CYAN("\033[46m"); + const backg backg::WHITE("\033[47m"); + + // Bright Background Variants (100-107) + const backg backg::B_BLACK("\033[100m"); + const backg backg::B_RED("\033[101m"); + const backg backg::B_GREEN("\033[102m"); + const backg backg::B_YELLOW("\033[103m"); + const backg backg::B_BLUE("\033[104m"); + const backg backg::B_MAGENTA("\033[105m"); + const backg backg::B_CYAN("\033[106m"); + const backg backg::B_WHITE("\033[107m"); + + // RGB Background (TrueColor: ESC[48;2;r;g;bm) + backg backg::rgb(int r, int g, int b) { + return backg("\033[48;2;" + std::to_string(r) + ";" + std::to_string(g) + ";" + std::to_string(b) + "m"); + } + + // HSL Background + backg backg::hsl(float h, float s, float l) { + auto hueToRgb = [](float p, float q, float t) { + if (t < 0.0f) t += 1.0f; + if (t > 1.0f) t -= 1.0f; + if (t < 1.0f / 6.0f) return p + (q - p) * 6.0f * t; + if (t < 1.0f / 2.0f) return q; + if (t < 2.0f / 3.0f) return p + (q - p) * (2.0f / 3.0f - t) * 6.0f; + return p; + }; + + float r, g, b; + if (s == 0.0f) { + r = g = b = l; + } else { + float q = l < 0.5f ? l * (1.0f + s) : l + s - l * s; + float p = 2.0f * l - q; + r = hueToRgb(p, q, h + 1.0f / 3.0f); + g = hueToRgb(p, q, h); + b = hueToRgb(p, q, h - 1.0f / 3.0f); + } + + return rgb(static_cast(r * 255), static_cast(g * 255), static_cast(b * 255)); + } + + backg::operator std::string_view() const { + return std::string_view(code); + } + + // ------ // + + // Constructor + color::color(const std::string& c) : code(c) {} + + // Standard Colors (Foreground) + const color color::BLACK("\033[30m"); + const color color::RED("\033[31m"); + const color color::GREEN("\033[32m"); + const color color::YELLOW("\033[33m"); + const color color::BLUE("\033[34m"); + const color color::MAGENTA("\033[35m"); + const color color::CYAN("\033[36m"); + const color color::WHITE("\033[37m"); + + // Bright Variants + const color color::B_BLACK("\033[90m"); + const color color::B_RED("\033[91m"); + const color color::B_GREEN("\033[92m"); + const color color::B_YELLOW("\033[93m"); + const color color::B_BLUE("\033[94m"); + const color color::B_MAGENTA("\033[95m"); + const color color::B_CYAN("\033[96m"); + const color color::B_WHITE("\033[97m"); + + // RGB Implementation (TrueColor: ESC[38;2;r;g;bm) + color color::rgb(int r, int g, int b) { + return color("\033[38;2;" + std::to_string(r) + ";" + + std::to_string(g) + ";" + + std::to_string(b) + "m"); + } + + // HSL Implementation + color color::hsl(float h, float s, float l) { + // Helper to convert HSL to RGB + auto hueToRgb = [](float p, float q, float t) { + if (t < 0.0f) t += 1.0f; + if (t > 1.0f) t -= 1.0f; + if (t < 1.0f / 6.0f) return p + (q - p) * 6.0f * t; + if (t < 1.0f / 2.0f) return q; + if (t < 2.0f / 3.0f) return p + (q - p) * (2.0f / 3.0f - t) * 6.0f; + return p; + }; + + float r, g, b; + if (s == 0.0f) { + r = g = b = l; // Achromatic + } else { + float q = l < 0.5f ? l * (1.0f + s) : l + s - l * s; + float p = 2.0f * l - q; + r = hueToRgb(p, q, h + 1.0f / 3.0f); + g = hueToRgb(p, q, h); + b = hueToRgb(p, q, h - 1.0f / 3.0f); + } + + return rgb(static_cast(r * 255), static_cast(g * 255), static_cast(b * 255)); + } + + color::operator std::string_view() const { + return std::string_view(code); + } + + // ------ // + + Terminal::Terminal() + : _os(&std::cout), _is(&std::cin), _own(false) { + } + + Terminal::~Terminal() { + if (_own) { + delete _os; + delete _is; + } + } + + // ------ // + + Terminal& Terminal::fill(const std::string_view color) { + // 1. Set background color + // 2. Clear screen (2J) + // 3. Move cursor to home (H) + (*_os) << color << CSI << "2J" << CSI << "H" << std::flush; + return *this; + } + + Terminal& Terminal::cls() { + // Clear entire screen and move cursor to 1,1 + (*_os) << CSI << "2J" << CSI << "H" << std::flush; + return *this; + } + + Terminal& Terminal::altbuff(bool enable) { + // ?1049h enables alternative buffer, ?1049l disables it + (*_os) << CSI << (enable ? "?1049h" : "?1049l") << std::flush; + return *this; + } + + Terminal& Terminal::createScrollRange(int start, int end) { + // DECSTBM: Set Top and Bottom Margins + (*_os) << CSI << start << ";" << end << "r" << std::flush; + return *this; + } + + Terminal& Terminal::undoScrollRange() { + // Reset margins to full screen + (*_os) << CSI << "r" << std::flush; + return *this; + } + + Terminal& Terminal::clearRow(int row) { + // Move to row, column 1, then clear line (2K) + (*_os) << CSI << row << ";1H" << CSI << "2K" << std::flush; + return *this; + } + + Terminal& Terminal::clearRows(int start, int end) { + for (int i = start; i <= end; ++i) { + clearRow(i); + } + return *this; + } + + Terminal& Terminal::cursor(bool show) { + // ?25h shows cursor, ?25l hides it + (*_os) << CSI << (show ? "?25h" : "?25l") << std::flush; + return *this; + } + + // ------ // + + Terminal& Terminal::wait() { + this->flush(); + this->sink(); // Clear any previous keystrokes so we wait for a NEW one + this->getKey(); // Block until a key is pressed + return *this; + } + + Terminal& Terminal::flush() { + _os->flush(); + return *this; + } + + // Updated sink() using OS-specific logic + Terminal& Terminal::sink() { + while (os::getKey(false) != -1); // Drain the buffer + return *this; + } + + // ------ // + + int Terminal::getKey() { return os::getKey(true); } + + int Terminal::getKeyNb() { return os::getKey(false); } + + std::optional Terminal::getChar() { return os::getChar(true); } + + std::optional Terminal::getCharNb() { return os::getChar(false); } + + pos Terminal::getSize() { + auto [x, y] = os::getSize(); + return { x, y }; + } + + // ------ // + + Terminal& Terminal::operator<<(const std::string_view& msg) { + (*_os) << msg; + return *this; + } + + Terminal& Terminal::operator<<(const pos p) { + // ANSI uses 1-based indexing: \033[row;colH + (*_os) << CSI << p.y << ";" << p.x << "H"; + return *this; + } + + Terminal& Terminal::operator<<(const move m) { + if (m.y < 0) (*_os) << CSI << -m.y << "A"; // Up + else if (m.y > 0) (*_os) << CSI << m.y << "B"; // Down + if (m.x > 0) (*_os) << CSI << m.x << "C"; // Forward/Right + else if (m.x < 0) (*_os) << CSI << -m.x << "D"; // Back/Left + return *this; + } + + Terminal& Terminal::operator<<(const style c) { + (*_os) << c.code; + return *this; + } + + Terminal& Terminal::operator<<(const color c) { + (*_os) << c.code; + return *this; + } + + Terminal& Terminal::operator<<(const backg c) { + (*_os) << c.code; + return *this; + } + + // ------ // + + Terminal& Terminal::center(int width, const std::string_view msg) { + if (msg.length() >= static_cast(width)) { + (*_os) << msg; + } else { + std::size_t total_padding = static_cast(width) - msg.length(); + std::size_t left_padding = total_padding / 2; + std::size_t right_padding = total_padding - left_padding; + + // Fill padding using a string constructor or a loop + // Standard strings are easiest for repeated characters + (*_os) << std::string(left_padding, ' ') + << msg + << std::string(right_padding, ' '); + } + return *this; + } + + Terminal& Terminal::subprint(int i0, int i1, const std::string_view msg) { + // Clamp indices to valid string range + std::size_t len = msg.length(); + std::size_t start = std::clamp(std::size_t(i0), std::size_t(0), len); + std::size_t end = std::clamp(std::size_t(i1), start, len); + + if (start < end) { + (*_os) << msg.substr(start, end - start); + } + return *this; + } + + Terminal& Terminal::subprint_center(int i0, int i1, const std::string_view msg) { + // 1. Calculate the slice from the original message + std::size_t len = msg.length(); + std::size_t start = std::clamp(std::size_t(i0), std::size_t(0), len); + std::size_t end = std::clamp(std::size_t(i1), start, len); + + std::string_view slice = msg.substr(start, end - start); + + // 2. The desired display width is defined by the bounds i0 and i1 + int target_width = i1 - i0; + + // 3. Hand off to the existing center logic + return center(target_width, slice); + } + + } + +} diff --git a/src/desktoplib/terminal/Terminal.hpp b/src/desktoplib/terminal/Terminal.hpp index 0acb6c6..b6fe742 100644 --- a/src/desktoplib/terminal/Terminal.hpp +++ b/src/desktoplib/terminal/Terminal.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace ckitty { @@ -21,7 +22,7 @@ namespace ckitty { struct pos { public: int x, y; - pos(int x, int y); + pos(int _x, int _y); }; /** @@ -31,7 +32,7 @@ namespace ckitty { struct move { public: int x, y; - move(int x, int y); + move(int _x, int _y); }; /** @@ -41,7 +42,7 @@ namespace ckitty { struct backg { friend class Terminal; public: - + static const backg BLACK; static const backg B_BLACK; static const backg RED; static const backg B_RED; static const backg GREEN; static const backg B_GREEN; @@ -53,8 +54,11 @@ namespace ckitty { private: - std::string_view code; - backg(const std::string_view code); + std::string code; + backg(const std::string& code); + + public: + operator std::string_view() const; public: static backg rgb(int r, int g, int b); @@ -68,7 +72,7 @@ namespace ckitty { struct color { friend class Terminal; public: - + static const color BLACK; static const color B_BLACK; static const color RED; static const color B_RED; static const color GREEN; static const color B_GREEN; @@ -79,8 +83,12 @@ namespace ckitty { static const color WHITE; static const color B_WHITE; private: - std::string_view code; - color(const std::string_view code); + std::string code; + color(const std::string& code); + + public: + operator std::string_view() const; + public: static color rgb(int r, int g, int b); static color hsl(float hue, float sat, float lum); @@ -99,8 +107,10 @@ namespace ckitty { static const style FAINT; static const style STRIKE; private: - std::string_view code; - style(const std::string_view code); + std::string code; + style(const std::string& code); + public: + operator std::string_view() const; }; /** @@ -109,15 +119,17 @@ namespace ckitty { struct key { friend class Terminal; public: - // Key Definitions (ASCII OK) // - static constexpr const char UP = 0x80; - static constexpr const char DOWN = 0x81; - static constexpr const char LEFT = 0x82; - static constexpr const char RIGHT = 0x83; - static constexpr const char ENTER = 0x84; - static constexpr const char ESC = 0x85; - static constexpr const char BACKSPACE = 0x86; - static constexpr const char UNKNOWN = 0xFF; + static constexpr int UP = 0x101; + static constexpr int DOWN = 0x102; + static constexpr int LEFT = 0x103; + static constexpr int RIGHT = 0x104; + + static constexpr int ENTER = 0x0D; + static constexpr int ESC = 0x1B; + static constexpr int BACKSPACE = 0x08; + static constexpr int TAB = 0x09; + + static constexpr int UNKNOWN = -1; private: }; @@ -147,7 +159,7 @@ namespace ckitty { /** * Fills the screen with a specific background color. - * @param color The background color constant (e.g., Terminal::BG_BLUE) + * @param color The background color constant (e.g., BG_BLUE) */ Terminal& fill(const std::string_view color); @@ -175,19 +187,6 @@ namespace ckitty { */ Terminal& clearRows(int start, int end); - /** - * Clears a specific column. - * @param col The 1-based index of the column to clear. - */ - Terminal& clearColumn(int col); - - /** - * Clears a range of columns (inclusive). - * @param start The first column. - * @param end The last column. - */ - Terminal& clearColumns(int start, int end); - /** * Shows / Hides the cursor. */ @@ -219,6 +218,9 @@ namespace ckitty { std::optional getChar(); + /** + * Get char non-blocking + */ std::optional getCharNb(); pos getSize(); @@ -237,14 +239,33 @@ namespace ckitty { Terminal& operator<<(const move c); + Terminal& operator<<(const style c); + + Terminal& operator<<(const color c); + + Terminal& operator<<(const backg c); + public: /** - * Represents an operation which will print up - * to a specific amount of characters. + * Prints a centered version of the final text, + * adding padding to center it as best as it can. */ - template - Terminal& subprint(int i0, int i1, const T& msg) {} + Terminal& center(int width, const std::string_view msg); + + /** + * Prints a slice of the message from index i0 up to (but not including) i1. + * We convert T to a string first to handle non-string types (like int or float). + */ + Terminal& subprint(int i0, int i1, const std::string_view msg); + + /** + * Combines centering logic with sub-printing. + * It takes a slice of the message, then pads it to fit the width (i1 - i0). + */ + Terminal& subprint_center(int i0, int i1, const std::string_view msg); + + public: /** * Prints a centered version of the final text, @@ -258,30 +279,68 @@ namespace ckitty { std::string s = oss.str(); // then print - if (s.length() >= isize(width)) { - std::cout << s; - } - else { - isize total_padding = isize(width) - s.length(); - isize left_padding = total_padding / 2; - std::cout << std::string(left_padding, ' '); - std::cout << s; - std::cout << std::string(total_padding - left_padding, ' '); + if (s.length() >= std::size_t(width)) { + (*_os) << s; + } else { + std::size_t total_padding = std::size_t(width) - s.length(); + std::size_t left_padding = total_padding / 2; + (*_os) << std::string(left_padding, ' '); + (*_os) << s; + (*_os) << std::string(total_padding - left_padding, ' '); } return *this; } - + /** - * Centered version of the subprint version. + * Prints a slice of the message from index i0 up to (but not including) i1. + * We convert T to a string first to handle non-string types (like int or float). */ template - Terminal& subprint_center(int i0, int i1, const T& msg) {} + Terminal& subprint(int i0, int i1, const T& msg) { + std::ostringstream oss; + oss << msg; + std::string s = oss.str(); + + // Convert to string_view for easy slicing and safety + std::string_view sv(s); + + // Bounds checking to prevent crashes + std::size_t start = std::max(std::size_t(0), std::size_t(i0)); + std::size_t end = std::min(std::size_t(sv.length()), std::size_t(i1)); + + if (start < end) { + (*_os) << sv.substr(start, end - start); + } + + return *this; + } + + /** + * Combines centering logic with sub-printing. + * It takes a slice of the message, then pads it to fit the width (i1 - i0). + */ + template + Terminal& subprint_center(int i0, int i1, const T& msg) { + std::ostringstream oss; + oss << msg; + std::string s = oss.str(); + std::string_view sv(s); + + // Get the actual slice we want to center + std::size_t start = std::max(std::size_t(0), std::size_t(i0)); + std::size_t end = std::min(std::size_t(sv.length()), std::size_t(i1)); + std::string_view slice = (start < end) ? sv.substr(start, end - start) : ""; + + // The target width for centering is the range defined by i0 and i1 + std::size_t target_width = std::size_t(i1 - i0); + return center(int(target_width), slice); + } private: - + template Terminal& read(T& var) { - std::cin >> var; + (*_is) >> var; return *this; }