Tui/OllamaApi

This commit is contained in:
KoDer 2026-10-01 01:59:58 +07:00
parent 990477f0e9
commit 35dfb8d3a5
15 changed files with 628 additions and 88 deletions

View File

@ -11,6 +11,7 @@ else()
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -ffast-math -funroll-loops")
endif()
add_compile_options(-Wall -Wextra -Wno-unused-parameter)
set(SOURCES
@ -29,6 +30,9 @@ set(SOURCES
xenith/ollama_api/handler.cpp
xenith/ollama_api/handler.h
xenith/ollama_api/server.h
xenith/ollama_api/server.cpp
xenith/ui/tui.cpp
xenith/ui/tui.h
)
# Заголовочные файлы (для удобства в IDE)
@ -38,9 +42,9 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
add_executable(Xenith ${SOURCES} ${HEADER_FILES})
# Настройка include директорий
target_include_directories(Xenith SYSTEM PRIVATE ./xenith/libs/cpp-httplib-0.57.1)
target_include_directories(Xenith PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
# Потоки (pthread)
find_package(Threads REQUIRED)
target_link_libraries(Xenith PRIVATE Threads::Threads)

View File

@ -26,16 +26,30 @@
## Быстрый старт
```bash
# 1) собрать модель из своего текста
./bin/Xenith new models/my.xnh --corpus data.txt \
# 1) активация окружения
source activate.sh
# 2) собрать модель из своего текста
Xenith new models/my.xnh --corpus data.txt \
--vocab 512 --embd 128 --layers 6 --heads 8 --ctx 256
# 2) обучить
./bin/Xenith train models/my.xnh --corpus data.txt \
# 3) обучить
Xenith train models/my.xnh --corpus data.txt \
--out models/my_trained.xnh --steps 5000 --batch 16 --lr 3e-4
# 3) сгенерировать
./bin/Xenith gen models/my_trained.xnh --prompt "Привет" --n 200 --temp 0.8
# 4) сгенерировать
Xenith gen models/my_trained.xnh --prompt "Привет" --n 200 --temp 0.8
```
## Перед использованием ***Xenith*** желательно активировать окружение
### Команда активации окружения
```bash
# для Zsh/bash
source activate.sh
# для fish
source activate.fish
```
## Команды
@ -114,9 +128,9 @@
### info / gradcheck / bench
```bash
./bin/Xenith info models/my.xnh # конфигурация и словарь
./bin/Xenith gradcheck # градиенты против численных
./bin/Xenith bench models/my.xnh # скорость
Xenith info models/my.xnh # конфигурация и словарь
Xenith gradcheck # градиенты против численных
Xenith bench models/my.xnh # скорость
```
`gradcheck` стоит запускать после любой правки в `model.cpp` / `backward.cpp` —

85
activate.fish Normal file
View File

@ -0,0 +1,85 @@
set -gx VIRTUAL_ENV_PROMPT "Xenith"
complete -c Xenith -erase
complete -c ./bin/Xenith -erase
complete -c Xenith -f
complete -c ./bin/Xenith -f
set -l main_commands "new:создать новую модель из корпуса" \
"train:обучить существующую модель" \
"gen:сгенерировать текст" \
"info:показать конфигурацию модели" \
"gradcheck:проверить градиенты численно" \
"bench:замерить скорость форварда и шага обучения" \
"update:Автомотическое обновление с гита"
for cmd in $main_commands
set -l parts (string split ":" $cmd)
complete -c Xenith -n "__fish_use_subcommand" -a $parts[1] -d $parts[2]
complete -c ./bin/Xenith -n "__fish_use_subcommand" -a $parts[1] -d $parts[2]
end
set -l new_cond "__fish_seen_subcommand_from new"
complete -c Xenith -n $new_cond -l corpus -r -F -d "текст для построения словаря (обязательно)"
complete -c Xenith -n $new_cond -l out -r -F -d "файл модели (по умолчанию model.xnh)"
complete -c Xenith -n $new_cond -l vocab -r -d "размер словаря (по умолчанию 512)"
complete -c Xenith -n $new_cond -l embd -r -d "размер эмбеддинга (по умолчанию 128)"
complete -c Xenith -n $new_cond -l layers -r -d "число слоёв (по умолчанию 4)"
complete -c Xenith -n $new_cond -l heads -r -d "число голов (по умолчанию 4)"
complete -c Xenith -n $new_cond -l ctx -r -d "максимальный контекст (по умолчанию 128)"
complete -c Xenith -n $new_cond -l ffn -r -d "размер FFN (по умолчанию 4*embd)"
complete -c Xenith -n $new_cond -l rope -r -d "база RoPE (по умолчанию 10000)"
complete -c Xenith -n $new_cond -l seed -r -d "зерно инициализации (по умолчанию 1337)"
complete -c Xenith -n $new_cond -l untied -d "отдельная матрица выхода вместо привязанной к wte"
complete -c Xenith -n $new_cond -l view-vocab -d "выводит словарь нейросети"
set -l train_cond "__fish_seen_subcommand_from train"
complete -c Xenith -n $train_cond -l corpus -r -F -d "обучающий текст (обязательно)"
complete -c Xenith -n $train_cond -l out -r -F -d "куда сохранить (по умолчанию перезапись входного файла)"
complete -c Xenith -n $train_cond -l steps -r -d "шагов (по умолчанию 1000)"
complete -c Xenith -n $train_cond -l batch -r -d "батч (по умолчанию 8)"
complete -c Xenith -n $train_cond -l block -r -d "длина окна (по умолчанию 64)"
complete -c Xenith -n $train_cond -l lr -r -d "скорость (по умолчанию 3e-4)"
complete -c Xenith -n $train_cond -l wd -r -d "weight decay (по умолчанию 0.01)"
complete -c Xenith -n $train_cond -l clip -r -d "клип нормы градиента (по умолчанию 1.0)"
complete -c Xenith -n $train_cond -l warmup -r -d "прогрев (по умолчанию 100)"
complete -c Xenith -n $train_cond -l seed -r -d "зерно (по умолчанию 1337)"
complete -c Xenith -n $train_cond -l ckpt-every -r -d "промежуточное сохранение каждые N шагов"
complete -c Xenith -n $train_cond -l val-every -r -d "валидация каждые N шагов"
complete -c Xenith -n $train_cond -l val-tokens -r -d "размер валидации (по умолчанию 20000)"
complete -c Xenith -n $train_cond -l threads -r -d "потоков (0 = все ядра)"
complete -c Xenith -n $train_cond -l resume -d "продолжить с сохранённого step (Adam-state из файла)"
set -l gen_cond "__fish_seen_subcommand_from gen"
complete -c Xenith -n $gen_cond -l prompt -r -d "стартовый текст"
complete -c Xenith -n $gen_cond -l n -r -d "сколько токенов (по умолчанию 200)"
complete -c Xenith -n $gen_cond -l temp -r -d "температура, 0 = жадный выбор (по умолчанию 0.8)"
complete -c Xenith -n $gen_cond -l top-k -r -d "top-k (0 = выкл)"
complete -c Xenith -n $gen_cond -l top-p -r -d "top-p (1.0 = выкл)"
complete -c Xenith -n $gen_cond -l seed -r -d "зерно"
complete -c Xenith -n $gen_cond -l no-stream -d "не печатать в процессе"
complete -c Xenith -n $gen_cond -l batch -r -d "несколько промптов через ;"
complete -c Xenith -n $gen_cond -l show-tokens -d "показать id токенов"
complete -c ./bin/Xenith -n "__fish_seen_subcommand_from new" -a "(complete -C'Xenith new ')"
complete -c ./bin/Xenith -n "__fish_seen_subcommand_from train" -a "(complete -C'Xenith train ')"
complete -c ./bin/Xenith -n "__fish_seen_subcommand_from gen" -a "(complete -C'Xenith gen ')"
function Xenith
./bin/Xenith $argv
end
if not set -q _OLD_VIRTUAL_PS1
functions -c fish_prompt _old_fish_prompt
function fish_prompt
echo -n "($VIRTUAL_ENV_PROMPT) "
_old_fish_prompt
end
set -g _OLD_VIRTUAL_PS1 1
end
echo "Xenith activate"
function deactivate
functions -e Xenith
if functions -q _old_fish_prompt
functions -e fish_prompt
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
end
set -e VIRTUAL_ENV_PROMPT
set -e _OLD_VIRTUAL_PS1
complete -c Xenith -erase
complete -c ./bin/Xenith -erase
functions -e deactivate
echo "Xenith deactivated"
end

26
activate.sh Normal file
View File

@ -0,0 +1,26 @@
export VIRTUAL_ENV_PROMPT="Xenith"
alias Xenith='./bin/Xenith'
if [ -z "${_OLD_VIRTUAL_PS1+x}" ]; then
_OLD_VIRTUAL_PS1="$PS1"
fi
PS1="($VIRTUAL_ENV_PROMPT) $_OLD_VIRTUAL_PS1"
echo "Xenith activate (zsh)"
deactivate_custom() {
unalias Xenith 2>/dev/null
unalias deactivate 2>/dev/null
if [ -n "${_OLD_VIRTUAL_PS1+x}" ]; then
PS1="$_OLD_VIRTUAL_PS1"
unset _OLD_VIRTUAL_PS1
fi
if (( $+functions[_old_virtual_deactivate] )); then
_old_virtual_deactivate
elif (( $+functions[deactivate] )); then
builtin deactivate 2>/dev/null || true
fi
unset -f deactivate_custom
echo "Xenith deactivated"
}
alias deactivate='deactivate_custom'

Binary file not shown.

View File

@ -1,2 +1,9 @@
models:
BiPy: models/bipy.bif
BiPy:
file: "models/my.xnh"
details:
parent_model: ""
family: "Bfr"
families:
- Bfr

191
help.h
View File

@ -1,71 +1,138 @@
#ifndef XENITH_HELP_H
#define XENITH_HELP_H
inline const std::string HELP_TEXT = R"(ИСПОЛЬЗОВАНИЕ
xenith <команда> [опции]
#define NEW
#define TRAIN
#define GEN
#define INFO
#define GRADCHECK
#define BENCH
//#define OLLAMA
#define UPDATE
//#define TUI
КОМАНДЫ
new создать новую модель из корпуса
train обучить существующую модель
gen сгенерировать текст
info показать конфигурацию модели
gradcheck проверить градиенты численно
bench замерить скорость форварда и шага обучения
ollama запустьтить сервер с api ollama
update Автомотическое обновление с гита
#ifdef NEW
#define CMD_NEW " new создать новую модель из корпуса\n"
#else
#define CMD_NEW ""
#endif
#ifdef TRAIN
#define CMD_TRAIN " train обучить существующую модель\n"
#else
#define CMD_TRAIN ""
#endif
#ifdef GEN
#define CMD_GEN " gen сгенерировать текст\n"
#else
#define CMD_GEN ""
#endif
#ifdef INFO
#define CMD_INFO " info показать конфигурацию модели\n"
#else
#define CMD_INFO ""
#endif
#ifdef GRADCHECK
#define CMD_GRADCHECK " gradcheck проверить градиенты численно\n"
#else
#define CMD_GRADCHECK ""
#endif
#ifdef BENCH
#define CMD_BENCH " bench замерить скорость форварда и шага обучения\n"
#else
#define CMD_BENCH ""
#endif
#ifdef OLLAMA
#define CMD_OLLAMA " ollama запустьтить сервер с api ollama\n"
#else
#define CMD_OLLAMA ""
#endif
#ifdef UPDATE
#define CMD_UPDATE " update Автомотическое обновление с гита\n"
#else
#define CMD_UPDATE ""
#endif
#ifdef TUI
#define CMD_TUI " tui Графическо-консольный режим (находится в тестировании)\n"
#else
#define CMD_TUI ""
#endif
ПРИМЕРЫ
xenith new model.xnh --corpus data.txt --vocab 512 --embd 64 --layers 4 --heads 4
xenith train model.xnh --corpus data.txt --out model_trained.xnh --steps 3000
xenith train model_trained.xnh --corpus data.txt --out model_trained.xnh --steps 6000
xenith gen model_trained.xnh --prompt "Привет" --n 200 --temp 0.8
ОПЦИИ new
--corpus PATH текст для построения словаря (обязательно)
--out PATH файл модели (по умолчанию model.xnh)
--vocab N размер словаря (по умолчанию 512)
--embd N размер эмбеддинга (по умолчанию 128)
--layers N число слоёв (по умолчанию 4)
--heads N число голов (по умолчанию 4)
--ctx N максимальный контекст (по умолчанию 128)
--ffn N размер FFN (по умолчанию 4*embd)
--rope N база RoPE (по умолчанию 10000)
--seed N зерно инициализации (по умолчанию 1337)
--untied отдельная матрица выхода вместо привязанной к wte
--view-vocab выводит словарь нейросети
#ifdef NEW
#define OPT_NEW "\nОПЦИИ new\n" \
" --corpus PATH текст для построения словаря (обязательно)\n" \
" --out PATH файл модели (по умолчанию model.xnh)\n" \
" --vocab N размер словаря (по умолчанию 512)\n" \
" --embd N размер эмбеддинга (по умолчанию 128)\n" \
" --layers N число слоёв (по умолчанию 4)\n" \
" --heads N число голов (по умолчанию 4)\n" \
" --ctx N максимальный контекст (по умолчанию 128)\n" \
" --ffn N размер FFN (по умолчанию 4*embd)\n" \
" --rope N база RoPE (по умолчанию 10000)\n" \
" --seed N зерно инициализации (по умолчанию 1337)\n" \
" --untied отдельная матрица выхода вместо привязанной к wte\n" \
" --view-vocab выводит словарь нейросети\n"
#else
#define OPT_NEW ""
#endif
#ifdef TRAIN
#define OPT_TRAIN "\nОПЦИИ train\n" \
" --corpus PATH обучающий текст (обязательно)\n" \
" --out PATH куда сохранить (по умолчанию перезапись входного файла)\n" \
" --steps N шагов (по умолчанию 1000)\n" \
" --batch N батч (по умолчанию 8)\n" \
" --block N длина окна (по умолчанию 64)\n" \
" --lr F скорость (по умолчанию 3e-4)\n" \
" --wd F weight decay (по умолчанию 0.01)\n" \
" --clip F клип нормы градиента (по умолчанию 1.0)\n" \
" --warmup N прогрев (по умолчанию 100)\n" \
" --seed N зерно (по умолчанию 1337)\n" \
" --ckpt-every N промежуточное сохранение каждые N шагов (0 = нет)\n" \
" --val-every N валидация каждые N шагов (0 = нет)\n" \
" --val-tokens N размер валидации (по умолчанию 20000)\n" \
" --threads N потоков (0 = все ядра)\n" \
" --resume продолжить с сохранённого step (Adam-state из файла)\n"
#else
#define OPT_TRAIN ""
#endif
#ifdef GEN
#define OPT_GEN "\nОПЦИИ gen\n" \
" --prompt STR стартовый текст\n" \
" --n N сколько токенов (по умолчанию 200)\n" \
" --temp F температура, 0 = жадный выбор (по умолчанию 0.8)\n" \
" --top-k N top-k (0 = выкл)\n" \
" --top-p F top-p (1.0 = выкл)\n" \
" --seed N зерно\n" \
" --no-stream не печатать в процессе\n" \
" --batch \"a;b;c\" несколько промптов OPTчерез ;\n" \
" --show-tokens показать id токенов\n"
#else
#define OPT_GEN ""
#endif
#ifdef OLLAMA
#define OPT_OLLAMA "\nОПЦИИ ollama\n" \
" --port PORT порт на котором будет открыт сервер\n" \
" --conf NAME.conf конфиг с настройками и списком моделей\n"
#else
#define OPT_OLLAMA ""
#endif
ОПЦИИ train
--corpus PATH обучающий текст (обязательно)
--out PATH куда сохранить (по умолчанию перезапись входного файла)
--steps N шагов (по умолчанию 1000)
--batch N батч (по умолчанию 8)
--block N длина окна (по умолчанию 64)
--lr F скорость (по умолчанию 3e-4)
--wd F weight decay (по умолчанию 0.01)
--clip F клип нормы градиента (по умолчанию 1.0)
--warmup N прогрев (по умолчанию 100)
--seed N зерно (по умолчанию 1337)
--log-every N как часто печатать (по умолчанию 50)
--ckpt-every N промежуточное сохранение каждые N шагов (0 = нет)
--val-every N валидация каждые N шагов (0 = нет)
--val-tokens N размер валидации (по умолчанию 20000)
--threads N потоков (0 = все ядра)
--resume продолжить с сохранённого step (Adam-state из файла)
ОПЦИИ gen
--prompt STR стартовый текст
--n N сколько токенов (по умолчанию 200)
--temp F температура, 0 = жадный выбор (по умолчанию 0.8)
--top-k N top-k (0 = выкл)
--top-p F top-p (1.0 = выкл)
--seed N зерно
--no-stream не печатать в процессе
--batch "a;b;c" несколько промптов через ;
--show-tokens показать id токенов
ОПЦИИ ollama
--port PORT порт на котором будет открыт сервер
--conf NAME.conf конфиг с настройками и списком моделей
)";
#define HELP_TEXT \
"ИСПОЛЬЗОВАНИЕ\n" \
" ./bin/Xenith <команда> [опции]\n\n" \
"КОМАНДЫ\n" \
CMD_NEW \
CMD_TRAIN \
CMD_GEN \
CMD_INFO \
CMD_GRADCHECK \
CMD_BENCH \
CMD_OLLAMA \
CMD_UPDATE \
CMD_TUI \
OPT_NEW \
OPT_TRAIN \
OPT_GEN \
OPT_OLLAMA
#endif

View File

@ -2,6 +2,7 @@
#include "xenith/core/generate.h"
#include "xenith/core/train.h"
#include "xenith/core/gradcheck.h"
#include "xenith/ui/tui.h"
#include <cstdio>
#include "help.h"
#include <cstdlib>
@ -263,7 +264,6 @@ Version parse_version(const std::string& str) {
return v;
}
// Правильное сравнение версий
bool is_newer(const Version& remote, const Version& current) {
if (remote.major != current.major) return remote.major > current.major;
if (remote.minor != current.minor) return remote.minor > current.minor;
@ -356,10 +356,20 @@ static int cmd_update(const Args& a) {
return 0;
}
static int cmd_tui(const Args& a)
{
ui::Tui tui = ui::Tui();
while (tui.is_running())
{
tui.update();
}
return 0;
}
int main(int argc, char** argv) {
if (argc < 2) { printf(HELP_TEXT.c_str()); return 1; }
if (argc < 2) { printf(HELP_TEXT); return 1; }
const std::string cmd = argv[1];
if (cmd == "-h" || cmd == "--help" || cmd == "help") { printf(HELP_TEXT.c_str()); return 0; }
if (cmd == "-h" || cmd == "--help" || cmd == "help") { printf(HELP_TEXT); return 0; }
Args a(argc, argv, 2);
if (cmd == "new") return cmd_new(a);
@ -370,8 +380,9 @@ int main(int argc, char** argv) {
if (cmd == "bench") return cmd_bench(a);
if (cmd == "ollama") return cmd_ollama(a);
if (cmd == "update") return cmd_update(a);
if (cmd == "tui") return cmd_tui(a);
fprintf(stderr, "неизвестная команда: %s\n\n", cmd.c_str());
printf(HELP_TEXT.c_str());
printf(HELP_TEXT);
return 1;
}

View File

@ -1,5 +1,2 @@
//
// Created by koder on 30.09.2026.
//
#include "handler.h"

View File

@ -1,13 +1,69 @@
#ifndef XENITH_HANDLER_H
#define XENITH_HANDLER_H
#include <cstdint>
#include <string>
#include <vector>
namespace api
{
class handler
enum class QuantLevel
{
BF16,
FP16,
Q8,
Q6,
Q5,
Q4,
Q2,
Q1,
};
class QuantizationLevel
{
private:
std::string value;
static std::string enum_to_string(QuantLevel quant)
{
switch (quant) {
case QuantLevel::BF16: return "BF16";
case QuantLevel::FP16: return "FP16";
case QuantLevel::Q8: return "Q8_0";
case QuantLevel::Q6: return "Q6_K";
case QuantLevel::Q5: return "Q5_K_M";
case QuantLevel::Q4: return "Q4_K_M";
case QuantLevel::Q2: return "Q2_K";
case QuantLevel::Q1: return "Q1";
default: return "UNKNOWN";
}
}
public:
QuantizationLevel() : value("UNKNOWN") {}
QuantizationLevel(QuantLevel quant) : value(enum_to_string(quant)) {}
QuantizationLevel(const std::string& str) : value(str) {}
operator std::string() const { return value; }
};
struct models_details
{
std::string parent_model;
std::string format;
std::string family;
std::pmr::vector<std::string> families;
uint32_t parameter_size;
QuantizationLevel quantization_level;
std::string expires_at;
std::string size_vram;
};
struct models {
std::string name;
std::string model;
std::string modified_at;
uint64_t size;
std::string digest;
models_details details;
};
}
#endif //XENITH_HANDLER_H
#endif

View File

@ -0,0 +1,9 @@
#include "server.h"
namespace api
{
server::server()
{
}
}

View File

@ -1,6 +1,14 @@
#ifndef XENITH_SERVER_H
#define XENITH_SERVER_H
#include "handler.h"
namespace api
{
class server
{
server();
~server();
};
}
#endif //XENITH_SERVER_H

View File

@ -3,8 +3,8 @@
#include <thread>
#include <vector>
#include <string>
#include "libs/cpp-httplib-0.57.1/httplib.h"
#include "libs/json-3.12.0/single_include/nlohmann/json.hpp"
#include "./xenith/libs/cpp-httplib-0.57.1/httplib.h"
#include "./xenith/libs/json-3.12.0/nlohmann/json.hpp"
using json = nlohmann::json;
@ -29,7 +29,7 @@ void log_ollama_options(const std::string& request_body) {
}
}
int main() {
int main_() {
httplib::Server svr;
svr.set_logger([](const httplib::Request& req, const httplib::Response& res) {

149
xenith/ui/tui.cpp Normal file
View File

@ -0,0 +1,149 @@
#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;
}
}

107
xenith/ui/tui.h Normal file
View File

@ -0,0 +1,107 @@
#ifndef XENITH_TUI_H
#define XENITH_TUI_H
#include <iostream>
#include <sys/ioctl.h>
#include <unistd.h>
namespace ui
{
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
};
Tui();
~Tui();
static void update();
bool is_running() const;
private:
int background = 49;
int foreground = 39;
bool running = true;
struct ConsoleSize {
int width = 80;
int height = 24;
};
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;
};
inline static const border_charset border_clip {
.top_left = "╭",
.top_right = "╮",
.bottom_left = "╰",
.bottom_right = "╯",
.bottom = "─",
.top = "─",
.vertical = "│",
.bg = " ",
};
inline static const border_charset border_bold {
.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 = " ",
};
inline static const border_charset border_btn {
.top_left = "▟",
.top_right = "▙",
.bottom_left = "▜",
.bottom_right = "▛",
.bottom = "█",
.top = "█",
.vertical = "█",
.bg = "█",
};
static ConsoleSize get_console_size();
void apply_color() const;
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);
static void hide_cursor();
static void show_cursor();
void set_background(Color clr);
void set_foreground(Color clr);
static void move_cursor_right(int n);
static void move_cursor_x(int col);
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();
};
}
#endif