347 lines
11 KiB
C++
347 lines
11 KiB
C++
#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);
|
|
}
|
|
|
|
}
|
|
|
|
}
|