LOTS OF PROGRESS!
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/out
|
||||||
|
/bin
|
||||||
@@ -4,5 +4,7 @@
|
|||||||
"path": "."
|
"path": "."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {}
|
"settings": {
|
||||||
|
"terminal.integrated.defaultProfile.windows": "MSYS2 UCRT",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
2
out.txt
Normal file
2
out.txt
Normal file
@@ -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
|
||||||
103
src/desktoplib/main.cpp
Normal file
103
src/desktoplib/main.cpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#include <desktoplib/terminal/Terminal.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
24
src/desktoplib/os/common.hpp
Normal file
24
src/desktoplib/os/common.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ckitty {
|
||||||
|
|
||||||
|
namespace terminal {
|
||||||
|
|
||||||
|
namespace os {
|
||||||
|
|
||||||
|
int getKey(bool blocking);
|
||||||
|
|
||||||
|
std::optional<std::string> getChar(bool blocking);
|
||||||
|
|
||||||
|
std::pair<int, int> getSize();
|
||||||
|
|
||||||
|
void setRawMode(bool enable);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
73
src/desktoplib/os/unix.cpp
Normal file
73
src/desktoplib/os/unix.cpp
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
|
||||||
|
|
||||||
|
#include "common.hpp"
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
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<std::string> getChar(bool blocking) {
|
||||||
|
int ch = getKey(blocking);
|
||||||
|
if (ch == -1) return std::nullopt;
|
||||||
|
return std::string(1, static_cast<char>(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> 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
|
||||||
70
src/desktoplib/os/windows.cpp
Normal file
70
src/desktoplib/os/windows.cpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#include "common.hpp"
|
||||||
|
#include <windows.h>
|
||||||
|
#include <conio.h>
|
||||||
|
|
||||||
|
#include <desktoplib/terminal/Terminal.hpp>
|
||||||
|
|
||||||
|
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<std::string> getChar(bool blocking) {
|
||||||
|
int ch = getKey(blocking);
|
||||||
|
if (ch == -1) return std::nullopt;
|
||||||
|
return std::string(1, static_cast<char>(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> 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
|
||||||
346
src/desktoplib/terminal/Terminal.cpp
Normal file
346
src/desktoplib/terminal/Terminal.cpp
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
#include "Terminal.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <desktoplib/os/common.hpp>
|
||||||
|
|
||||||
|
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<int>(r * 255), static_cast<int>(g * 255), static_cast<int>(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<int>(r * 255), static_cast<int>(g * 255), static_cast<int>(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<std::string> Terminal::getChar() { return os::getChar(true); }
|
||||||
|
|
||||||
|
std::optional<std::string> 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<std::size_t>(width)) {
|
||||||
|
(*_os) << msg;
|
||||||
|
} else {
|
||||||
|
std::size_t total_padding = static_cast<std::size_t>(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace ckitty {
|
namespace ckitty {
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ namespace ckitty {
|
|||||||
struct pos {
|
struct pos {
|
||||||
public:
|
public:
|
||||||
int x, y;
|
int x, y;
|
||||||
pos(int x, int y);
|
pos(int _x, int _y);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,7 +32,7 @@ namespace ckitty {
|
|||||||
struct move {
|
struct move {
|
||||||
public:
|
public:
|
||||||
int x, y;
|
int x, y;
|
||||||
move(int x, int y);
|
move(int _x, int _y);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,7 +42,7 @@ namespace ckitty {
|
|||||||
struct backg {
|
struct backg {
|
||||||
friend class Terminal;
|
friend class Terminal;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static const backg BLACK; static const backg B_BLACK;
|
static const backg BLACK; static const backg B_BLACK;
|
||||||
static const backg RED; static const backg B_RED;
|
static const backg RED; static const backg B_RED;
|
||||||
static const backg GREEN; static const backg B_GREEN;
|
static const backg GREEN; static const backg B_GREEN;
|
||||||
@@ -53,8 +54,11 @@ namespace ckitty {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
std::string_view code;
|
std::string code;
|
||||||
backg(const std::string_view code);
|
backg(const std::string& code);
|
||||||
|
|
||||||
|
public:
|
||||||
|
operator std::string_view() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static backg rgb(int r, int g, int b);
|
static backg rgb(int r, int g, int b);
|
||||||
@@ -68,7 +72,7 @@ namespace ckitty {
|
|||||||
struct color {
|
struct color {
|
||||||
friend class Terminal;
|
friend class Terminal;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static const color BLACK; static const color B_BLACK;
|
static const color BLACK; static const color B_BLACK;
|
||||||
static const color RED; static const color B_RED;
|
static const color RED; static const color B_RED;
|
||||||
static const color GREEN; static const color B_GREEN;
|
static const color GREEN; static const color B_GREEN;
|
||||||
@@ -79,8 +83,12 @@ namespace ckitty {
|
|||||||
static const color WHITE; static const color B_WHITE;
|
static const color WHITE; static const color B_WHITE;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string_view code;
|
std::string code;
|
||||||
color(const std::string_view code);
|
color(const std::string& code);
|
||||||
|
|
||||||
|
public:
|
||||||
|
operator std::string_view() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static color rgb(int r, int g, int b);
|
static color rgb(int r, int g, int b);
|
||||||
static color hsl(float hue, float sat, float lum);
|
static color hsl(float hue, float sat, float lum);
|
||||||
@@ -99,8 +107,10 @@ namespace ckitty {
|
|||||||
static const style FAINT;
|
static const style FAINT;
|
||||||
static const style STRIKE;
|
static const style STRIKE;
|
||||||
private:
|
private:
|
||||||
std::string_view code;
|
std::string code;
|
||||||
style(const std::string_view code);
|
style(const std::string& code);
|
||||||
|
public:
|
||||||
|
operator std::string_view() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,15 +119,17 @@ namespace ckitty {
|
|||||||
struct key {
|
struct key {
|
||||||
friend class Terminal;
|
friend class Terminal;
|
||||||
public:
|
public:
|
||||||
// Key Definitions (ASCII OK) //
|
static constexpr int UP = 0x101;
|
||||||
static constexpr const char UP = 0x80;
|
static constexpr int DOWN = 0x102;
|
||||||
static constexpr const char DOWN = 0x81;
|
static constexpr int LEFT = 0x103;
|
||||||
static constexpr const char LEFT = 0x82;
|
static constexpr int RIGHT = 0x104;
|
||||||
static constexpr const char RIGHT = 0x83;
|
|
||||||
static constexpr const char ENTER = 0x84;
|
static constexpr int ENTER = 0x0D;
|
||||||
static constexpr const char ESC = 0x85;
|
static constexpr int ESC = 0x1B;
|
||||||
static constexpr const char BACKSPACE = 0x86;
|
static constexpr int BACKSPACE = 0x08;
|
||||||
static constexpr const char UNKNOWN = 0xFF;
|
static constexpr int TAB = 0x09;
|
||||||
|
|
||||||
|
static constexpr int UNKNOWN = -1;
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -147,7 +159,7 @@ namespace ckitty {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fills the screen with a specific background color.
|
* 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);
|
Terminal& fill(const std::string_view color);
|
||||||
|
|
||||||
@@ -175,19 +187,6 @@ namespace ckitty {
|
|||||||
*/
|
*/
|
||||||
Terminal& clearRows(int start, int end);
|
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.
|
* Shows / Hides the cursor.
|
||||||
*/
|
*/
|
||||||
@@ -219,6 +218,9 @@ namespace ckitty {
|
|||||||
|
|
||||||
std::optional<std::string> getChar();
|
std::optional<std::string> getChar();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get char non-blocking
|
||||||
|
*/
|
||||||
std::optional<std::string> getCharNb();
|
std::optional<std::string> getCharNb();
|
||||||
|
|
||||||
pos getSize();
|
pos getSize();
|
||||||
@@ -237,14 +239,33 @@ namespace ckitty {
|
|||||||
|
|
||||||
Terminal& operator<<(const move c);
|
Terminal& operator<<(const move c);
|
||||||
|
|
||||||
|
Terminal& operator<<(const style c);
|
||||||
|
|
||||||
|
Terminal& operator<<(const color c);
|
||||||
|
|
||||||
|
Terminal& operator<<(const backg c);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an operation which will print up
|
* Prints a centered version of the final text,
|
||||||
* to a specific amount of characters.
|
* adding padding to center it as best as it can.
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
Terminal& center(int width, const std::string_view msg);
|
||||||
Terminal& subprint(int i0, int i1, const T& 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,
|
* Prints a centered version of the final text,
|
||||||
@@ -258,30 +279,68 @@ namespace ckitty {
|
|||||||
std::string s = oss.str();
|
std::string s = oss.str();
|
||||||
|
|
||||||
// then print
|
// then print
|
||||||
if (s.length() >= isize(width)) {
|
if (s.length() >= std::size_t(width)) {
|
||||||
std::cout << s;
|
(*_os) << s;
|
||||||
}
|
} else {
|
||||||
else {
|
std::size_t total_padding = std::size_t(width) - s.length();
|
||||||
isize total_padding = isize(width) - s.length();
|
std::size_t left_padding = total_padding / 2;
|
||||||
isize left_padding = total_padding / 2;
|
(*_os) << std::string(left_padding, ' ');
|
||||||
std::cout << std::string(left_padding, ' ');
|
(*_os) << s;
|
||||||
std::cout << s;
|
(*_os) << std::string(total_padding - left_padding, ' ');
|
||||||
std::cout << std::string(total_padding - left_padding, ' ');
|
|
||||||
}
|
}
|
||||||
return *this;
|
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 <typename T>
|
template <typename T>
|
||||||
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 <typename T>
|
||||||
|
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:
|
private:
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
Terminal& read(T& var) {
|
Terminal& read(T& var) {
|
||||||
std::cin >> var;
|
(*_is) >> var;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user