TUI
This commit is contained in:
parent
a081d4d7e8
commit
ecef2f41af
@ -31,8 +31,9 @@ set(SOURCES
|
||||
xenith/ollama_api/handler.h
|
||||
xenith/ollama_api/server.h
|
||||
xenith/ollama_api/server.cpp
|
||||
xenith/ui/tui.cpp
|
||||
xenith/ui/tui/tui.cpp
|
||||
xenith/ui/tui.h
|
||||
xenith/ui/tui/mouse.cpp
|
||||
)
|
||||
|
||||
# Заголовочные файлы (для удобства в IDE)
|
||||
|
||||
BIN
bin/Xenith
BIN
bin/Xenith
Binary file not shown.
1
main.cpp
1
main.cpp
@ -363,6 +363,7 @@ static int cmd_tui(const Args& a)
|
||||
{
|
||||
tui.update();
|
||||
}
|
||||
std::cout << "\033[2J\033[H" << std::flush;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@ TrainStats train_model(Params& p, const Tokenizer& tok, const std::string& corpu
|
||||
val_ids.assign(ids.end() - tc.val_tokens, ids.end());
|
||||
ids.resize(ids.size() - tc.val_tokens);
|
||||
has_val = true;
|
||||
fprintf(stderr, "валидация: %zu токенов\n", val_ids.size());
|
||||
fprintf(stderr, "валидация: %zu токенов\n\033[?25l", val_ids.size());
|
||||
}
|
||||
|
||||
std::vector<int> xb(B * block), yb(B * block);
|
||||
|
||||
@ -1,149 +0,0 @@
|
||||
#include "tui.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
Tui::Tui()
|
||||
{
|
||||
ConsoleSize cs = get_console_size();
|
||||
int panel_w = (cs.width / 100) * 27;
|
||||
std::cout << "Ширина (колонок): " << cs.width << "\n";
|
||||
std::cout << "Высота (строк): " << cs.height << "\n";
|
||||
clear();
|
||||
hide_cursor();
|
||||
set_foreground(Color::White);
|
||||
apply_color();
|
||||
draw_border((cs.width / 100) * 27, cs.height, border_clip);
|
||||
draw_border(cs.width - ((cs.width / 100) * 27), cs.height, border_clip, (cs.width / 100) * 27);
|
||||
draw_button(2, 2, panel_w - 2, 3, "Chat", border_clip);
|
||||
draw_button(2, 2 + 3, panel_w - 2, 3, "Models", border_clip);
|
||||
draw_button(2, 2 + 6, panel_w - 2, 3, "Settings", border_clip);
|
||||
draw_button(2, 2 + 9, panel_w - 2, 3, "Server", border_clip);
|
||||
//std::cout << "\033[?1000h\033[?1002h\033[?1006h" << std::flush;
|
||||
}
|
||||
|
||||
Tui::~Tui()
|
||||
= default;
|
||||
|
||||
void Tui::update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool Tui::is_running() const
|
||||
{
|
||||
return running;
|
||||
}
|
||||
|
||||
Tui::ConsoleSize Tui::get_console_size()
|
||||
{
|
||||
ConsoleSize size;
|
||||
winsize w;
|
||||
|
||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) {
|
||||
size.width = w.ws_col;
|
||||
size.height = w.ws_row;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void Tui::apply_color() const
|
||||
{
|
||||
std::cout << "\033["<< foreground << ";" << background <<"m" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::move_cursor(int x, int y)
|
||||
{
|
||||
std::cout << "\033[" << y << ";" << x << "H" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::clear()
|
||||
{
|
||||
std::cout << "\033c" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::draw_border(int w, int h, border_charset charset, int x, int y)
|
||||
{
|
||||
move_cursor(x, y);
|
||||
std::cout << charset.top_left;
|
||||
for (int q = 0; q < w - 2; q++) std::cout << charset.top;
|
||||
std::cout << charset.top_right <<"\n";
|
||||
move_cursor_x(x);
|
||||
for (int i = 0; i < h - 2; i++)
|
||||
{
|
||||
std::cout << charset.vertical;
|
||||
for (int q = 0; q < w - 2; q++) std::cout << charset.bg;
|
||||
std::cout << charset.vertical;
|
||||
std::cout << "\n";
|
||||
move_cursor_x(x);
|
||||
}
|
||||
std::cout << charset.bottom_left;
|
||||
for (int q = 0; q < w - 2; q++) std::cout << charset.bottom;
|
||||
std::cout << charset.bottom_right << std::flush;
|
||||
}
|
||||
|
||||
void Tui::hide_cursor()
|
||||
{
|
||||
std::cout << "\033[?25l" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::show_cursor()
|
||||
{
|
||||
std::cout << "\033[?25h" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::set_background(Color clr)
|
||||
{
|
||||
background = static_cast<int>(clr);
|
||||
}
|
||||
|
||||
void Tui::set_foreground(Color clr)
|
||||
{
|
||||
foreground = static_cast<int>(clr);
|
||||
}
|
||||
|
||||
void Tui::move_cursor_right(int n)
|
||||
{
|
||||
if (n > 0) {
|
||||
std::cout << "\033[" << n << "C" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
void Tui::move_cursor_x(int col)
|
||||
{
|
||||
if (col > 0) {
|
||||
std::cout << "\033[" << col << "G" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
void Tui::move_cursor_y(int row)
|
||||
{
|
||||
if (row > 0) {
|
||||
std::cout << "\033[" << row << "d" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
void Tui::invert_color()
|
||||
{
|
||||
std::cout << "\033[7m" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::draw_button(int x, int y, int w, int h, std::string_view label, border_charset charset)
|
||||
{
|
||||
draw_border(w, h, charset, x, y);
|
||||
int text_y = y + (h / 2);
|
||||
int text_x = x + ((w - static_cast<int>(label.length())) / 2);
|
||||
move_cursor(text_x, text_y);
|
||||
std::cout << label;
|
||||
std::cout << std::flush;
|
||||
}
|
||||
|
||||
void Tui::invert_color_off()
|
||||
{
|
||||
std::cout << "\033[27m" << std::flush;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
141
xenith/ui/tui.h
141
xenith/ui/tui.h
@ -1,92 +1,110 @@
|
||||
#ifndef XENITH_TUI_H
|
||||
#define XENITH_TUI_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/select.h>
|
||||
#include <termios.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
class Mouse
|
||||
{
|
||||
private:
|
||||
struct pos_t { int x; int y; };
|
||||
inline static bool btn0 = false;
|
||||
inline static bool btn1 = false;
|
||||
inline static bool btn2 = false;
|
||||
inline static int scroll = 0;
|
||||
inline static pos_t pos = {};
|
||||
public:
|
||||
static bool is_pressed(int index_btn = 0);
|
||||
static int get_scroll();
|
||||
static void reset_scroll();
|
||||
static void parse_ansi(char buf[]);
|
||||
static pos_t get_pos();
|
||||
};
|
||||
|
||||
class Tui
|
||||
{
|
||||
public:
|
||||
enum class Color : int {
|
||||
DefaultText = 39,
|
||||
DefaultBg = 49,
|
||||
Black = 30,
|
||||
Red = 31,
|
||||
Green = 32,
|
||||
Yellow = 33,
|
||||
Blue = 34,
|
||||
Magenta = 35,
|
||||
Cyan = 36,
|
||||
White = 37
|
||||
DefaultText = 39, DefaultBg = 49, Black = 30, Red = 31,
|
||||
Green = 32, Yellow = 33, Blue = 34, Magenta = 35,
|
||||
Cyan = 36, White = 37
|
||||
};
|
||||
Tui();
|
||||
~Tui();
|
||||
static void update();
|
||||
bool is_running() const;
|
||||
private:
|
||||
inline static Mouse mouse = Mouse();
|
||||
inline static std::string chat_input = "";
|
||||
inline static bool cursor = false;
|
||||
inline static uint32_t frame_count = 0;
|
||||
int background = 49;
|
||||
int foreground = 39;
|
||||
bool running = true;
|
||||
struct ConsoleSize {
|
||||
int width = 80;
|
||||
int height = 24;
|
||||
inline static bool running = true;
|
||||
inline static int width = 80;
|
||||
inline static int height = 24;
|
||||
|
||||
struct SplitDesc {
|
||||
enum Type { VERTICAL, HORIZONTAL };
|
||||
Type type;
|
||||
int position;
|
||||
std::vector<std::unique_ptr<SplitDesc>> children;
|
||||
SplitDesc(Type t, int pos) : type(t), position(pos) {}
|
||||
};
|
||||
struct border_charset
|
||||
{
|
||||
std::string_view top_left;
|
||||
std::string_view top_right;
|
||||
std::string_view bottom_left;
|
||||
std::string_view bottom_right;
|
||||
std::string_view bottom;
|
||||
std::string_view top;
|
||||
std::string_view vertical;
|
||||
std::string_view bg;
|
||||
|
||||
struct border_charset {
|
||||
std::string_view top_left, top_right, bottom_left, bottom_right;
|
||||
std::string_view bottom, top, horizontal, vertical, bg;
|
||||
std::string_view cross, tee_up, tee_down, tee_left, tee_right;
|
||||
};
|
||||
|
||||
// Ваша структура, которая теперь будет использоваться по назначению
|
||||
struct TuiCell {
|
||||
std::string ch; // UTF-8 последовательность (например, "│" или "┌")
|
||||
int width; // Ширина на экране (обычно 1)
|
||||
|
||||
TuiCell() : ch(" "), width(1) {}
|
||||
TuiCell(const std::string& c, int w) : ch(c), width(w) {}
|
||||
};
|
||||
|
||||
inline static struct termios orig_termios;
|
||||
inline static const border_charset border_clip {
|
||||
.top_left = "╭",
|
||||
.top_right = "╮",
|
||||
.bottom_left = "╰",
|
||||
.bottom_right = "╯",
|
||||
.bottom = "─",
|
||||
.top = "─",
|
||||
.vertical = "│",
|
||||
.bg = " ",
|
||||
.top_left="╭", .top_right="╮", .bottom_left="╰", .bottom_right="╯",
|
||||
.bottom="─", .top="─", .horizontal="─", .vertical="│", .bg=" ",
|
||||
.cross="┼", .tee_up="┴", .tee_down="┬", .tee_left="┤", .tee_right="├"
|
||||
};
|
||||
inline static const border_charset border_ascii {
|
||||
.top_left="+", .top_right="+", .bottom_left="+", .bottom_right="+",
|
||||
.bottom="-", .top="-", .vertical="|", .bg=" ", .cross="+",
|
||||
.tee_up="+", .tee_down="+", .tee_left="+", .tee_right="+"
|
||||
};
|
||||
inline static const border_charset border_bold {
|
||||
.top_left = "┏",
|
||||
.top_right = "┓",
|
||||
.bottom_left = "┗",
|
||||
.bottom_right = "┛",
|
||||
.bottom = "━",
|
||||
.top = "━",
|
||||
.vertical = "┃",
|
||||
.bg = " ",
|
||||
.top_left="┏", .top_right="┓", .bottom_left="┗", .bottom_right="┛",
|
||||
.bottom="━", .top="━", .vertical="┃", .bg=" "
|
||||
};
|
||||
inline static const border_charset border_solid {
|
||||
.top_left = "▟",
|
||||
.top_right = "▙",
|
||||
.bottom_left = "▜",
|
||||
.bottom_right = "▛",
|
||||
.bottom = "█",
|
||||
.top = "█",
|
||||
.vertical = "█",
|
||||
.bg = " ",
|
||||
.top_left="▟", .top_right="▙", .bottom_left="▜", .bottom_right="▛",
|
||||
.bottom="█", .top="█", .vertical="█", .bg=" "
|
||||
};
|
||||
inline static const border_charset border_btn {
|
||||
.top_left = "▟",
|
||||
.top_right = "▙",
|
||||
.bottom_left = "▜",
|
||||
.bottom_right = "▛",
|
||||
.bottom = "█",
|
||||
.top = "█",
|
||||
.vertical = "█",
|
||||
.bg = "█",
|
||||
.top_left="▟", .top_right="▙", .bottom_left="▜", .bottom_right="▛",
|
||||
.bottom="█", .top="█", .vertical="█", .bg="█"
|
||||
};
|
||||
static ConsoleSize get_console_size();
|
||||
|
||||
static void get_console_size();
|
||||
static bool wait_for_input_or_timeout(int timeout_ms);
|
||||
void apply_color() const;
|
||||
static void disable_raw_mode();
|
||||
static void enable_raw_mode();
|
||||
static void move_cursor(int x, int y);
|
||||
static void clear();
|
||||
static void draw_border(int w, int h, border_charset charset = border_clip, int x = 1, int y = 1);
|
||||
@ -99,9 +117,14 @@ namespace ui
|
||||
static void move_cursor_y(int row);
|
||||
static void invert_color();
|
||||
static void draw_button(int x, int y, int w, int h, std::string_view label, border_charset charset = border_solid);
|
||||
void invert_color_off();
|
||||
static void invert_color_off();
|
||||
|
||||
// --- ОБНОВЛЕННЫЕ СИГНАТУРЫ ---
|
||||
void draw_split_border(int w, int h, border_charset charset, int x, int y, const SplitDesc* root);
|
||||
static void apply_splits_recursive(std::vector<std::vector<TuiCell>>& grid, int offset_x, int offset_y, int w, int h, border_charset charset, const SplitDesc* split);
|
||||
static void apply_single_split(std::vector<std::vector<TuiCell>>& grid, int offset_x, int offset_y, int w, int h, border_charset charset, const SplitDesc* split);
|
||||
static void draw_border_to_buffer(std::vector<std::vector<TuiCell>>& grid, int w, int h, border_charset charset);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
51
xenith/ui/tui/mouse.cpp
Normal file
51
xenith/ui/tui/mouse.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include "../tui.h"
|
||||
|
||||
namespace ui
|
||||
{
|
||||
void Mouse::parse_ansi(char buf[])
|
||||
{
|
||||
int button = 0;
|
||||
char state = '\0';
|
||||
if (sscanf(buf + 3, "%d;%d;%d%c", &button, &pos.x, &pos.y, &state) == 4)
|
||||
{
|
||||
auto btn_index = static_cast<uint8_t>(button);
|
||||
uint8_t click_type = btn_index & 0xC3;
|
||||
if (click_type == 0b00000000)
|
||||
{
|
||||
btn0 = (state == 'M');
|
||||
}
|
||||
else if (click_type == 0b00000001)
|
||||
{
|
||||
btn1 = (state == 'M');
|
||||
}
|
||||
else if (click_type == 0b00000010)
|
||||
{
|
||||
btn2 = (state == 'M');
|
||||
}
|
||||
else if (click_type == 0b01000000)
|
||||
{
|
||||
scroll++;
|
||||
}
|
||||
else if (click_type == 0b01000001)
|
||||
{
|
||||
scroll--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Mouse::is_pressed(int index_btn)
|
||||
{
|
||||
switch (index_btn)
|
||||
{
|
||||
case 0: return btn0;
|
||||
case 1: return btn1;
|
||||
case 2: return btn2;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Mouse::reset_scroll() { scroll = 0; }
|
||||
int Mouse::get_scroll() { return scroll; }
|
||||
Mouse::pos_t Mouse::get_pos() { return pos; }
|
||||
}
|
||||
382
xenith/ui/tui/tui.cpp
Normal file
382
xenith/ui/tui/tui.cpp
Normal file
@ -0,0 +1,382 @@
|
||||
#include "../tui.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
namespace ui
|
||||
{
|
||||
Tui::Tui()
|
||||
{
|
||||
get_console_size();
|
||||
int panel_w = (width / 100) * 27;
|
||||
std::cout << "Ширина (колонок): " << width << "\n";
|
||||
std::cout << "Высота (строк): " << height << "\n";
|
||||
clear();
|
||||
hide_cursor();
|
||||
set_foreground(Color::White);
|
||||
apply_color();
|
||||
|
||||
auto root = std::make_unique<SplitDesc>(SplitDesc::VERTICAL, (width / 100) * 27);
|
||||
|
||||
root->children.push_back(nullptr);
|
||||
|
||||
auto chat_split = std::make_unique<SplitDesc>(SplitDesc::HORIZONTAL, height - 3);
|
||||
chat_split->children.push_back(nullptr);
|
||||
auto send_split = std::make_unique<SplitDesc>(SplitDesc::VERTICAL, (width - (width / 100) * 27) - 11);
|
||||
chat_split->children.push_back(std::move(send_split));
|
||||
root->children.push_back(std::move(chat_split));
|
||||
|
||||
draw_split_border(width, height, border_clip, 0, 0, root.get());
|
||||
|
||||
draw_button(2, 2, panel_w - 2, 3, "Chat", border_clip);
|
||||
draw_button(2, 2 + 3, panel_w - 2, 3, "Models", border_clip);
|
||||
draw_button(2, 2 + 6, panel_w - 2, 3, "Settings", border_clip);
|
||||
draw_button(2, 2 + 9, panel_w - 2, 3, "Server", border_clip);
|
||||
|
||||
move_cursor(width - 8, height - 1);
|
||||
std::cout << "[enter>" <<std::flush;
|
||||
|
||||
enable_raw_mode();
|
||||
}
|
||||
|
||||
Tui::~Tui()
|
||||
= default;
|
||||
|
||||
bool Tui::wait_for_input_or_timeout(int timeout_us) {
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(STDIN_FILENO, &fds);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout_us / 1000000;
|
||||
tv.tv_usec = timeout_us % 1000000;
|
||||
|
||||
int retval = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
|
||||
return retval > 0;
|
||||
}
|
||||
|
||||
|
||||
void Tui::disable_raw_mode()
|
||||
{
|
||||
std::cout << "\033[?1006l\033[?1002l\033[?1000l" << std::flush;
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
|
||||
}
|
||||
|
||||
void Tui::enable_raw_mode() {
|
||||
tcgetattr(STDIN_FILENO, &orig_termios);
|
||||
struct termios raw = orig_termios;
|
||||
std::signal(SIGINT, [](int sig)
|
||||
{
|
||||
disable_raw_mode();
|
||||
show_cursor();
|
||||
running = false;
|
||||
});
|
||||
raw.c_lflag &= ~(ECHO | ICANON);
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
||||
std::cout << "\033[?1000h\033[?1002h\033[?1006h" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::update()
|
||||
{
|
||||
bool input_changed = false;
|
||||
if (wait_for_input_or_timeout(10000)) {
|
||||
char buf[64];
|
||||
int bytes_read = read(STDIN_FILENO, buf, sizeof(buf) - 1);
|
||||
|
||||
if (bytes_read > 0) {
|
||||
buf[bytes_read] = '\0';
|
||||
int i = 0;
|
||||
|
||||
while (i < bytes_read) {
|
||||
unsigned char c = buf[i];
|
||||
|
||||
if (c == '\033') {
|
||||
if (i + 2 < bytes_read && strncmp(&buf[i], "\033[<", 3) == 0) {
|
||||
mouse.parse_ansi(&buf[i]);
|
||||
while (i < bytes_read && buf[i] != 'm' && buf[i] != 'M') {
|
||||
i++;
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
while (i < bytes_read && buf[i] >= 0x40 && buf[i] <= 0x7E) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else if (c == 127 || c == 8) {
|
||||
if (!chat_input.empty()) {
|
||||
while (!chat_input.empty() && (static_cast<unsigned char>(chat_input.back()) & 0xC0) == 0x80) {
|
||||
chat_input.pop_back();
|
||||
}
|
||||
if (!chat_input.empty()) {
|
||||
chat_input.pop_back();
|
||||
}
|
||||
input_changed = true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
else if (c >= 32) {
|
||||
chat_input += c;
|
||||
input_changed = true;
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static auto last_blink = std::chrono::steady_clock::now();
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (input_changed || std::chrono::duration_cast<std::chrono::milliseconds>(now - last_blink).count() >= 500) {
|
||||
if (input_changed) {
|
||||
last_blink = now;
|
||||
cursor = true;
|
||||
} else {
|
||||
cursor = !cursor;
|
||||
}
|
||||
last_blink = now;
|
||||
|
||||
int input_x = ((width / 100) * 27) + 3;
|
||||
move_cursor(input_x, height - 1);
|
||||
std::cout << chat_input << (cursor ? "_ " : " ") << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
bool Tui::is_running() const
|
||||
{
|
||||
return running;
|
||||
}
|
||||
|
||||
void Tui::get_console_size()
|
||||
{
|
||||
winsize w;
|
||||
|
||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) {
|
||||
width = w.ws_col;
|
||||
height = w.ws_row;
|
||||
}
|
||||
}
|
||||
|
||||
void Tui::apply_color() const
|
||||
{
|
||||
std::cout << "\033["<< foreground << ";" << background <<"m" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::move_cursor(int x, int y)
|
||||
{
|
||||
std::cout << "\033[" << y << ";" << x << "H" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::clear()
|
||||
{
|
||||
std::cout << "\033c" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::draw_border(int w, int h, border_charset charset, int x, int y)
|
||||
{
|
||||
move_cursor(x, y);
|
||||
std::cout << charset.top_left;
|
||||
for (int q = 0; q < w - 2; q++) std::cout << charset.top;
|
||||
std::cout << charset.top_right <<"\n";
|
||||
move_cursor_x(x);
|
||||
for (int i = 0; i < h - 2; i++)
|
||||
{
|
||||
std::cout << charset.vertical;
|
||||
for (int q = 0; q < w - 2; q++) std::cout << charset.bg;
|
||||
std::cout << charset.vertical;
|
||||
std::cout << "\n";
|
||||
move_cursor_x(x);
|
||||
}
|
||||
std::cout << charset.bottom_left;
|
||||
for (int q = 0; q < w - 2; q++) std::cout << charset.bottom;
|
||||
std::cout << charset.bottom_right << std::flush;
|
||||
}
|
||||
|
||||
void Tui::hide_cursor()
|
||||
{
|
||||
std::cout << "\033[?25l" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::show_cursor()
|
||||
{
|
||||
std::cout << "\033[?25h" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::set_background(Color clr)
|
||||
{
|
||||
background = static_cast<int>(clr);
|
||||
}
|
||||
|
||||
void Tui::set_foreground(Color clr)
|
||||
{
|
||||
foreground = static_cast<int>(clr);
|
||||
}
|
||||
|
||||
void Tui::move_cursor_right(int n)
|
||||
{
|
||||
if (n > 0) {
|
||||
std::cout << "\033[" << n << "C" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
void Tui::move_cursor_x(int col)
|
||||
{
|
||||
if (col > 0) {
|
||||
std::cout << "\033[" << col << "G" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
void Tui::move_cursor_y(int row)
|
||||
{
|
||||
if (row > 0) {
|
||||
std::cout << "\033[" << row << "d" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
void Tui::invert_color()
|
||||
{
|
||||
std::cout << "\033[7m" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::draw_button(int x, int y, int w, int h, std::string_view label, border_charset charset)
|
||||
{
|
||||
draw_border(w, h, charset, x, y);
|
||||
int text_y = y + (h / 2);
|
||||
int text_x = x + ((w - static_cast<int>(label.length())) / 2);
|
||||
move_cursor(text_x, text_y);
|
||||
std::cout << label;
|
||||
std::cout << std::flush;
|
||||
}
|
||||
|
||||
void Tui::invert_color_off()
|
||||
{
|
||||
std::cout << "\033[27m" << std::flush;
|
||||
}
|
||||
|
||||
void Tui::draw_split_border(int w, int h, border_charset charset, int x, int y, const SplitDesc* root)
|
||||
{
|
||||
// Создаем сетку из TuiCell, инициализированную фоновым символом
|
||||
std::vector<std::vector<TuiCell>> grid(h, std::vector<TuiCell>(w, TuiCell(std::string(charset.bg), 1)));
|
||||
|
||||
draw_border_to_buffer(grid, w, h, charset);
|
||||
|
||||
if (root) {
|
||||
apply_splits_recursive(grid, 0, 0, w, h, charset, root);
|
||||
}
|
||||
|
||||
move_cursor(x, y);
|
||||
|
||||
// Посимвольный вывод без искажения UTF-8
|
||||
for (int i = 0; i < h; i++) {
|
||||
for (int j = 0; j < w; j++) {
|
||||
std::cout << grid[i][j].ch;
|
||||
}
|
||||
if (i < h - 1) {
|
||||
std::cout << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << std::flush;
|
||||
}
|
||||
|
||||
void Tui::apply_splits_recursive(std::vector<std::vector<TuiCell>>& grid,
|
||||
int offset_x, int offset_y,
|
||||
int w, int h,
|
||||
border_charset charset,
|
||||
const SplitDesc* split)
|
||||
{
|
||||
if (!split) return;
|
||||
|
||||
apply_single_split(grid, offset_x, offset_y, w, h, charset, split);
|
||||
|
||||
if (split->type == SplitDesc::VERTICAL) {
|
||||
if (split->children.size() >= 1) {
|
||||
int left_w = split->position;
|
||||
apply_splits_recursive(grid, offset_x, offset_y, left_w, h, charset, split->children[0].get());
|
||||
}
|
||||
if (split->children.size() >= 2) {
|
||||
int right_w = w - split->position;
|
||||
apply_splits_recursive(grid, offset_x + split->position, offset_y, right_w, h, charset, split->children[1].get());
|
||||
}
|
||||
} else { // HORIZONTAL
|
||||
if (split->children.size() >= 1) {
|
||||
int top_h = split->position;
|
||||
apply_splits_recursive(grid, offset_x, offset_y, w, top_h, charset, split->children[0].get());
|
||||
}
|
||||
if (split->children.size() >= 2) {
|
||||
int bottom_h = h - split->position;
|
||||
apply_splits_recursive(grid, offset_x, offset_y + split->position, w, bottom_h, charset, split->children[1].get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tui::apply_single_split(std::vector<std::vector<TuiCell>>& grid, int offset_x, int offset_y, int w, int h, border_charset charset, const SplitDesc* split)
|
||||
{
|
||||
if (split->type == SplitDesc::VERTICAL) {
|
||||
int x = offset_x + split->position;
|
||||
|
||||
for (int i = 0; i < h; i++) {
|
||||
int y = offset_y + i;
|
||||
TuiCell& cell = grid[y][x];
|
||||
|
||||
if (i == 0) {
|
||||
if (cell.ch == charset.top) cell.ch = std::string(charset.tee_down);
|
||||
} else if (i == h - 1) {
|
||||
if (cell.ch == charset.bottom) cell.ch = std::string(charset.tee_up);
|
||||
} else {
|
||||
if (cell.ch == charset.bg) {
|
||||
cell.ch = std::string(charset.vertical);
|
||||
} else if (cell.ch == charset.horizontal || cell.ch == charset.top || cell.ch == charset.bottom) {
|
||||
cell.ch = std::string(charset.cross);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // HORIZONTAL
|
||||
int y = offset_y + split->position;
|
||||
|
||||
for (int j = 0; j < w; j++) {
|
||||
int x = offset_x + j;
|
||||
TuiCell& cell = grid[y][x];
|
||||
|
||||
if (j == 0) {
|
||||
if (cell.ch == charset.vertical) cell.ch = std::string(charset.tee_right);
|
||||
} else if (j == w - 1) {
|
||||
if (cell.ch == charset.vertical) cell.ch = std::string(charset.tee_left);
|
||||
} else {
|
||||
if (cell.ch == charset.bg) {
|
||||
// Используем horizontal вместо bottom для семантической корректности горизонтального разделителя
|
||||
cell.ch = std::string(charset.horizontal);
|
||||
} else if (cell.ch == charset.vertical) {
|
||||
cell.ch = std::string(charset.cross);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tui::draw_border_to_buffer(std::vector<std::vector<TuiCell>>& grid, int w, int h, border_charset charset)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
// Верхняя строка
|
||||
grid[0][0] = TuiCell(std::string(charset.top_left), 1);
|
||||
for (int j = 1; j < w - 1; j++) grid[0][j] = TuiCell(std::string(charset.top), 1);
|
||||
grid[0][w - 1] = TuiCell(std::string(charset.top_right), 1);
|
||||
|
||||
// Средние строки
|
||||
for (int i = 1; i < h - 1; i++) {
|
||||
grid[i][0] = TuiCell(std::string(charset.vertical), 1);
|
||||
grid[i][w - 1] = TuiCell(std::string(charset.vertical), 1);
|
||||
}
|
||||
|
||||
// Нижняя строка
|
||||
grid[h - 1][0] = TuiCell(std::string(charset.bottom_left), 1);
|
||||
for (int j = 1; j < w - 1; j++) grid[h - 1][j] = TuiCell(std::string(charset.bottom), 1);
|
||||
grid[h - 1][w - 1] = TuiCell(std::string(charset.bottom_right), 1);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user