Files
desktoplib/src/desktoplib/terminal/Terminal.cpp
2026-04-20 19:26:24 -06:00

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);
}
}
}