#include "../tui.hpp" #include #include #include #include #include #include #include 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::VERTICAL, (width / 100) * 27); root->children.push_back(nullptr); auto chat_split = std::make_unique(SplitDesc::HORIZONTAL, height - 3); chat_split->children.push_back(nullptr); auto send_split = std::make_unique(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>" < 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(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(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(clr); } void Tui::set_foreground(Color clr) { foreground = static_cast(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(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> grid(h, std::vector(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>& 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>& 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>& 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); } }