Initial commit: X11 GUI

This commit is contained in:
Karnilov 2026-08-25 01:47:43 +07:00
parent 975fba0ba8
commit 1b90a3ba62
3 changed files with 432 additions and 522 deletions

522
docs.html
View File

@ -1,522 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GUI Library Documentation</title>
<!-- Подключаем шрифты и иконки -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
<style>
:root {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-tertiary: #334155;
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--accent: #38bdf8;
--accent-hover: #0ea5e9;
--success: #22c55e;
--border: #334155;
--code-bg: #0d1117;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
display: flex;
min-height: 100vh;
}
/* Скроллбар */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg-primary); }
::-webkit-scrollbar-thumb { background: var(--bg-tertiary); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent); }
/* Боковая панель */
.sidebar {
width: 280px;
background-color: var(--bg-secondary);
border-right: 1px solid var(--border);
position: fixed;
height: 100vh;
overflow-y: auto;
padding: 2rem 1.5rem;
z-index: 100;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--accent);
margin-bottom: 2rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.search-box {
width: 100%;
padding: 0.75rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-family: 'Inter', sans-serif;
margin-bottom: 1.5rem;
outline: none;
transition: border-color 0.2s;
}
.search-box:focus { border-color: var(--accent); }
.nav-group { margin-bottom: 1.5rem; }
.nav-title {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-secondary);
margin-bottom: 0.5rem;
font-weight: 600;
}
.nav-link {
display: block;
padding: 0.5rem 0.75rem;
color: var(--text-secondary);
text-decoration: none;
border-radius: 6px;
font-size: 0.9rem;
transition: all 0.2s;
}
.nav-link:hover, .nav-link.active {
background-color: var(--bg-tertiary);
color: var(--accent);
}
/* Основной контент */
.main-content {
margin-left: 280px;
flex: 1;
padding: 3rem 4rem;
max-width: 1200px;
}
h1 { font-size: 2.5rem; margin-bottom: 1rem; color: var(--text-primary); }
h2 { font-size: 1.8rem; margin-top: 3rem; margin-bottom: 1rem; color: var(--accent); border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; }
h3 { font-size: 1.3rem; margin-top: 2rem; margin-bottom: 0.75rem; color: var(--text-primary); }
p { margin-bottom: 1rem; color: var(--text-secondary); }
.badge {
display: inline-block;
padding: 0.25rem 0.5rem;
background: var(--bg-tertiary);
color: var(--accent);
border-radius: 4px;
font-size: 0.75rem;
font-family: 'JetBrains Mono', monospace;
margin-right: 0.5rem;
}
/* Блоки кода */
.code-wrapper {
position: relative;
margin: 1.5rem 0;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--border);
}
.code-header {
background: var(--bg-secondary);
padding: 0.5rem 1rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
font-size: 0.85rem;
color: var(--text-secondary);
}
.copy-btn {
background: transparent;
border: 1px solid var(--border);
color: var(--text-secondary);
padding: 0.25rem 0.75rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.8rem;
transition: all 0.2s;
}
.copy-btn:hover { background: var(--bg-tertiary); color: var(--text-primary); }
.copy-btn.copied { border-color: var(--success); color: var(--success); }
pre { margin: 0 !important; border-radius: 0 !important; background: var(--code-bg) !important; }
code { font-family: 'JetBrains Mono', monospace !important; font-size: 0.9rem !important; }
/* Таблицы */
table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
background: var(--bg-secondary);
border-radius: 8px;
overflow: hidden;
}
th, td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid var(--border);
}
th { background: var(--bg-tertiary); color: var(--text-primary); font-weight: 600; }
td { color: var(--text-secondary); }
td code { background: var(--bg-primary); padding: 0.2rem 0.4rem; border-radius: 4px; color: var(--accent); }
/* Аккордеон для API */
details {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
margin-bottom: 1rem;
overflow: hidden;
}
summary {
padding: 1rem;
cursor: pointer;
font-weight: 600;
color: var(--text-primary);
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
}
summary::-webkit-details-marker { display: none; }
summary::after { content: '+'; font-size: 1.5rem; color: var(--accent); }
details[open] summary::after { content: '−'; }
details[open] summary { border-bottom: 1px solid var(--border); }
.details-content { padding: 1.5rem; }
.highlight { color: var(--accent); font-weight: 600; }
@media (max-width: 768px) {
.sidebar { transform: translateX(-100%); transition: transform 0.3s; }
.sidebar.open { transform: translateX(0); }
.main-content { margin-left: 0; padding: 2rem; }
}
</style>
</head>
<body>
<!-- Боковая навигация -->
<nav class="sidebar">
<div class="logo">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
X11 GUI Lib
</div>
<input type="text" class="search-box" placeholder="Поиск по документации..." id="searchInput">
<div class="nav-group">
<div class="nav-title">Начало работы</div>
<a href="#intro" class="nav-link">Введение</a>
<a href="#quickstart" class="nav-link">Быстрый старт</a>
<a href="#architecture" class="nav-link">Архитектура</a>
</div>
<div class="nav-group">
<div class="nav-title">Компоненты</div>
<a href="#eventbus" class="nav-link">EventBus</a>
<a href="#rawpicture" class="nav-link">RawPicture</a>
<a href="#widgets" class="nav-link">Виджеты</a>
</div>
<div class="nav-group">
<div class="nav-title">API Справочник</div>
<a href="#api-window" class="nav-link">gui::window</a>
<a href="#api-page" class="nav-link">gui::page</a>
<a href="#api-combat" class="nav-link">gui::combat</a>
<a href="#api-widget" class="nav-link">gui::Widget</a>
</div>
</nav>
<!-- Основной контент -->
<main class="main-content">
<section id="intro">
<h1>Документация GUI Библиотеки</h1>
<p>Легковесная, кроссплатформенная (X11) библиотека для создания графических интерфейсов на C++. Поддерживает программный рендеринг, анимации, иерархию виджетов и событийно-ориентированную архитектуру.</p>
</section>
<section id="quickstart">
<h2>🚀 Быстрый старт</h2>
<p>Минимальный пример создания окна с кнопкой и обработкой события.</p>
<div class="code-wrapper">
<div class="code-header">
<span>main.cpp</span>
<button class="copy-btn" onclick="copyCode(this)">Копировать</button>
</div>
<pre><code class="language-cpp">#include "gui.h"
int main() {
// 1. Создаем окно
gui::window win("Мое Приложение");
// 2. Добавляем страницу
win.addPAGE("main");
// 3. Настраиваем страницу виджетами
win.configur_page("main", [](gui::page& p) {
// Создаем кнопку
gui::WidgetData btnData;
btnData.id = "my_button";
btnData.text = "Нажми меня!";
btnData.x = 50; btnData.y = 50;
btnData.width = 150; btnData.height = 40;
btnData.bg_color = 0x2563EB; // Синий
btnData.fg_color = 0xFFFFFF; // Белый текст
p.Button(btnData);
}, true); // true = сделать активной сразу
// 4. Подписываемся на событие
gui::EventBus::get().on("button.click", [](const gui::EventData& e) {
if (e.source_id == "my_button") {
std::cout << "Кнопка нажата!" << std::endl;
}
});
// 5. Главный цикл
while (true) {
win.update(); // Обработка событий X11 и отрисовка
// Добавьте std::this_thread::sleep_for для экономии CPU
}
return 0;
}</code></pre>
</div>
</section>
<section id="architecture">
<h2>🏛 Архитектура</h2>
<p>Библиотека построена на строгой иерархии:</p>
<table>
<thead>
<tr><th>Уровень</th><th>Класс</th><th>Описание</th></tr>
</thead>
<tbody>
<tr><td><span class="badge">1</span></td><td><code>gui::window</code></td><td>Управляет соединением с X11, главным циклом (`update`) и вкладками.</td></tr>
<tr><td><span class="badge">2</span></td><td><code>gui::page</code></td><td>Представляет собой вкладку или экран. Наследуется от <code>combat</code>.</td></tr>
<tr><td><span class="badge">3</span></td><td><code>gui::combat</code></td><td>Базовый контейнер. Хранит коллекцию виджетов и предоставляет фабричные методы для их создания.</td></tr>
<tr><td><span class="badge">4</span></td><td><code>gui::Widget</code></td><td>Конкретный элемент интерфейса (кнопка, ввод, слайдер). Также наследуется от <code>combat</code>, позволяя создавать вложенные структуры (например, деревья).</td></tr>
</tbody>
</table>
</section>
<section id="eventbus">
<h2>📡 EventBus</h2>
<p>Глобальная шина событий для слабой связи между компонентами. Реализована как Singleton.</p>
<div class="code-wrapper">
<div class="code-header"><span>Использование EventBus</span><button class="copy-btn" onclick="copyCode(this)">Копировать</button></div>
<pre><code class="language-cpp">// Подписка на событие
int token = gui::EventBus::get().on("slider.change", [](const gui::EventData& e) {
std::cout << "Widget: " << e.source_id << ", Value: " << e.value << std::endl;
});
// Генерация события (обычно делается внутри библиотеки, но можно и вручную)
gui::EventBus::get().emit("custom.event", {"widget_id", "some text", 0.5f, 0, true});
// Отписка
gui::EventBus::get().off(token);</code></pre>
</div>
<p>Структура <code>EventData</code> содержит: <code>source_id</code> (ID виджета), <code>text</code>, <code>value</code> (для слайдеров/прогресса), <code>index</code> (для списков), <code>flag</code> (для чекбоксов).</p>
</section>
<section id="rawpicture">
<h2>🎨 RawPicture</h2>
<p>Класс для программного рендеринга в буфер. Поддерживает 8, 16, 24 и 32-битные форматы.</p>
<ul>
<li><strong>Примитивы:</strong> <code>drawLine</code>, <code>fillRect</code>, <code>drawCircle</code>, <code>fillPolygon</code>, <code>drawText</code>.</li>
<li><strong>Загрузка:</strong> <code>load("image.bmp")</code> или <code>load("image.ppm")</code>.</li>
<li><strong>Отображение:</strong> <code>present(display, window, gc, x, y)</code> для вывода на экран X11.</li>
</ul>
</section>
<section id="widgets">
<h2>🧩 Доступные виджеты</h2>
<p>Все виджеты создаются через методы класса <code>combat</code> (или <code>page</code>) путем передачи структуры <code>WidgetData</code>.</p>
<table>
<thead><tr><th>Метод</th><th>Тип</th><th>Описание</th></tr></thead>
<tbody>
<tr><td><code>Button()</code></td><td><code>BUTTON</code></td><td>Стандартная кнопка. Поддерживает <code>on_page_click</code> и <code>on_window_click</code>.</td></tr>
<tr><td><code>Label()</code></td><td><code>LABEL</code></td><td>Текстовая метка.</td></tr>
<tr><td><code>Input()</code></td><td><code>INPUT</code></td><td>Поле ввода текста. Перехватывает фокус клавиатуры.</td></tr>
<tr><td><code>Checkbox()</code></td><td><code>CHECKBOX</code></td><td>Флажок. Состояние в <code>data.checked</code>.</td></tr>
<tr><td><code>Radiobutton()</code></td><td><code>RADIO</code></td><td>Радиокнопка. Автоматически снимает выделение с других радио-кнопок с тем же <code>id</code> на странице.</td></tr>
<tr><td><code>Slider()</code></td><td><code>SLIDER</code></td><td>Ползунок. Значение 0.0 - 1.0 в <code>data.value</code>. Поддерживает перетаскивание.</td></tr>
<tr><td><code>Progressbar()</code></td><td><code>PROGRESS</code></td><td>Индикатор прогресса. Поддерживает анимацию <code>indeterminate</code>.</td></tr>
<tr><td><code>Listbox()</code></td><td><code>LISTBOX</code></td><td>Список элементов. Элементы задаются через <code>data.items</code>.</td></tr>
<tr><td><code>Dropdown()</code></td><td><code>DROPDOWN</code></td><td>Выпадающий список с анимацией открытия.</td></tr>
<tr><td><code>TreeView()</code></td><td><code>TREE_VIEW</code></td><td>Контейнер для древовидной структуры. Использует <code>TreeNode</code> как детей.</td></tr>
<tr><td><code>ScrollView()</code></td><td><code>SCROLL</code></td><td>Область с прокруткой содержимого.</td></tr>
<tr><td><code>RawPictureWidget()</code></td><td><code>RAW_PIC</code></td><td>Отображает объект <code>RawPicture</code> из <code>data.picture</code>.</td></tr>
</tbody>
</table>
</section>
<section id="api-window">
<h2>📚 API Справочник: gui::window</h2>
<details>
<summary><code>window(std::string title)</code></summary>
<div class="details-content">
<p>Конструктор. Инициализирует соединение с X11, создает окно заданного размера (по умолчанию 1200x600) и настраивает маску событий.</p>
</div>
</details>
<details>
<summary><code>void update()</code></summary>
<div class="details-content">
<p>Главный метод цикла. Обрабатывает все накопленные события X11, обновляет анимации виджетов (<code>tick</code>), очищает окно и перерисовывает вкладки и активную страницу. <strong>Должен вызываться постоянно.</strong></p>
</div>
</details>
<details>
<summary><code>void addPAGE(std::string name)</code> / <code>void configur_page(...)</code></summary>
<div class="details-content">
<p><code>addPAGE</code> создает пустую страницу. <code>configur_page</code> создает (если нет), применяет лямбда-функцию для наполнения виджетами и может сделать её активной (<code>active = true</code>).</p>
</div>
</details>
<details>
<summary><code>void openPAGE(std::string name)</code></summary>
<div class="details-content">
<p>Делает указанную страницу активной, деактивируя остальные. Генерирует событие <code>"page.open"</code>.</p>
</div>
</details>
</section>
<section id="api-combat">
<h2>📚 API Справочник: gui::combat</h2>
<p>Базовый класс для контейнеров. Все методы создания виджетов возвращают ссылку на созданный <code>Widget</code>, что позволяет сохранять его для последующего изменения.</p>
<details>
<summary>Фабричные методы (<code>Button</code>, <code>Label</code>, <code>Input</code> и т.д.)</summary>
<div class="details-content">
<p>Принимают объект <code>WidgetData</code>. Автоматически устанавливают соответствующий <code>WidgetType</code>, добавляют виджет во внутренний вектор и возвращают на него ссылку.</p>
</div>
</details>
<details>
<summary><code>Widget* find(const std::string& id)</code></summary>
<div class="details-content">
<p>Рекурсивный поиск виджета по его уникальному идентификатору <code>id</code> внутри контейнера и всех его дочерних элементов.</p>
</div>
</details>
<details>
<summary><code>void visit(const std::function<void(Widget&)>& fn)</code></summary>
<div class="details-content">
<p>Применяет переданную лямбда-функцию ко всем виджетам в иерархии. Удобно для массовой модификации (например, отключения всех кнопок).</p>
</div>
</details>
</section>
<section id="api-widget">
<h2>📚 API Справочник: gui::Widget</h2>
<details>
<summary><code>void setValue(float v)</code> / <code>float getValue()</code></summary>
<div class="details-content">
<p>Устанавливает целевое значение. Для прогресс-баров и слайдеров это запускает плавную анимацию изменения <code>data.value</code> к <code>data.target_value</code> внутри метода <code>tick()</code>.</p>
</div>
</details>
<details>
<summary>Анимация и состояние</summary>
<div class="details-content">
<p>Виджет содержит поля <code>anim</code> (для dropdown), <code>phase</code> (для неопределенного прогресса). Метод <code>tick(float dt)</code> автоматически вызывается из <code>window::update()</code> и интерполирует значения.</p>
</div>
</details>
</section>
<section id="tips">
<h2>💡 Советы и лучшие практики</h2>
<ul>
<li><strong>Производительность:</strong> Библиотека использует программный рендеринг (CPU). Для сложных сцен избегайте частого пересоздания <code>RawPicture</code>. Используйте <code>blit</code> для кеширования отрисовки.</li>
<li><strong>ID Виджетов:</strong> Всегда задавайте уникальное поле <code>id</code> в <code>WidgetData</code>, если планируете обращаться к виджету позже или слушать его события.</li>
<li><strong>Вложенность:</strong> Виджеты <code>TreeView</code> и <code>ScrollView</code> предназначены для содержания других виджетов. Добавляйте их через стандартные методы, они будут учитываться при расчете высоты контента и прокрутки.</li>
<li><strong>Цвета:</strong> Указываются в формате HEX (например, <code>0xFF0000</code> для красного). Библиотека автоматически преобразует их в формат пикселей X11 при отрисовке.</li>
</ul>
</section>
<footer style="margin-top: 4rem; padding-top: 2rem; border-top: 1px solid var(--border); color: var(--text-secondary); text-align: center; font-size: 0.9rem;">
<p>Сгенерировано для X11 C++ GUI Library. 2026</p>
</footer>
</main>
<!-- Скрипты -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-cpp.min.js"></script>
<script>
// Функция копирования кода
function copyCode(button) {
const codeBlock = button.parentElement.nextElementSibling.querySelector('code');
const text = codeBlock.innerText;
navigator.clipboard.writeText(text).then(() => {
const originalText = button.innerText;
button.innerText = 'Скопировано!';
button.classList.add('copied');
setTimeout(() => {
button.innerText = originalText;
button.classList.remove('copied');
}, 2000);
});
}
// Поиск по документации
const searchInput = document.getElementById('searchInput');
const sections = document.querySelectorAll('section');
const navLinks = document.querySelectorAll('.nav-link');
searchInput.addEventListener('input', (e) => {
const term = e.target.value.toLowerCase();
sections.forEach(section => {
const text = section.innerText.toLowerCase();
if (text.includes(term)) {
section.style.display = 'block';
} else {
section.style.display = term === '' ? 'block' : 'none';
}
});
// Подсветка активных ссылок в сайдбаре
navLinks.forEach(link => {
const targetId = link.getAttribute('href').substring(1);
const targetSection = document.getElementById(targetId);
if (targetSection && targetSection.style.display === 'none') {
link.style.display = 'none';
} else {
link.style.display = 'block';
}
});
});
// Плавная прокрутка и активный класс
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
navLinks.forEach(l => l.classList.remove('active'));
link.classList.add('active');
});
});
window.addEventListener('scroll', () => {
let current = '';
sections.forEach(section => {
const sectionTop = section.offsetTop;
if (pageYOffset >= sectionTop - 100) {
current = section.getAttribute('id');
}
});
navLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href').substring(1) === current) {
link.classList.add('active');
}
});
});
</script>
</body>
</html>

432
docs/index.html Normal file
View File

@ -0,0 +1,432 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>X11 GUI Library — Documentation</title>
<style>
:root{--bg:#202020;--bg2:#2b2b2b;--bg3:#383838;--tx:#fff;--tx2:#9d9d9d;--ac:#0078d4;--ach:#2488e0;--bd:#3b3b3b;--r:8px}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--tx);display:flex}
::-webkit-scrollbar{width:8px}::-webkit-scrollbar-thumb{background:var(--bg3);border-radius:4px}
.sb{width:260px;position:fixed;inset:0 auto 0 0;background:var(--bg2);border-right:1px solid var(--bd);padding:16px 12px;overflow-y:auto}
.logo{font-size:17px;font-weight:600;padding:6px 12px 18px;display:flex;gap:10px;align-items:center}
.logo .sq{width:22px;height:22px;background:var(--ac);border-radius:5px}
#q{width:100%;background:var(--bg);border:1px solid var(--bd);border-radius:6px;color:var(--tx);padding:8px 10px;font-size:13px;margin-bottom:12px;outline:none}
#q:focus{border-color:var(--ac)}
.nv{display:block;width:100%;text-align:left;background:none;border:none;color:var(--tx2);padding:10px 12px;border-radius:6px;font-size:14px;cursor:pointer;margin-bottom:2px;position:relative}
.nv:hover{background:var(--bg3);color:var(--tx)}
.nv.on{background:var(--bg3);color:var(--tx)}
.nv.on::before{content:'';position:absolute;left:0;top:8px;bottom:8px;width:3px;background:var(--ac);border-radius:2px}
main{margin-left:260px;flex:1;padding:32px 40px;max-width:1100px}
h1{font-size:28px;font-weight:600;margin-bottom:6px}
h2{font-size:20px;font-weight:600;margin:26px 0 10px}
h3{font-size:15px;font-weight:600;margin:18px 0 8px}
p,li{color:var(--tx2);font-size:14px;line-height:1.7}
ul{padding-left:20px;margin:8px 0}
.card{background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r);padding:20px;margin:14px 0}
table{width:100%;border-collapse:collapse;margin:12px 0;font-size:13px}
th,td{padding:8px 10px;text-align:left;border-bottom:1px solid var(--bd);color:var(--tx2);vertical-align:top}
th{color:var(--tx);background:var(--bg3)}
code{font-family:'Cascadia Code',Consolas,monospace;font-size:12.5px;color:#4cc2ff}
pre{background:#1a1a1a;border:1px solid var(--bd);border-radius:var(--r);padding:14px;overflow-x:auto;margin:10px 0}
pre code{color:#e8e8e8;line-height:1.6}
.cb{position:relative}
.cp{position:absolute;top:8px;right:8px;background:var(--bg3);border:1px solid var(--bd);color:var(--tx2);border-radius:5px;padding:3px 10px;font-size:11px;cursor:pointer}
.cp:hover{background:var(--ac);color:#fff}
.page{display:none}.page.on{display:block}
.badge{display:inline-block;background:var(--ac);color:#fff;border-radius:4px;padding:2px 8px;font-size:11px;margin-right:6px}
.tip{border-left:3px solid var(--ac);background:var(--bg2);padding:12px 16px;border-radius:0 6px 6px 0;margin:12px 0}
.warn{border-left:3px solid #f59e0b;background:var(--bg2);padding:12px 16px;border-radius:0 6px 6px 0;margin:12px 0}
</style>
</head>
<body>
<aside class="sb">
<div class="logo"><div class="sq"></div>X11 GUI Library</div>
<input id="q" placeholder="Search...">
<button class="nv on" data-p="start"> Getting Started</button>
<button class="nv" data-p="window">🪟 Window &amp; Pages</button>
<button class="nv" data-p="widgets">🧩 Widgets</button>
<button class="nv" data-p="themes">🎨 Themes &amp; Customization</button>
<button class="nv" data-p="custom">🛠 Custom Widgets</button>
<button class="nv" data-p="events">📡 Events</button>
<button class="nv" data-p="anim">✨ Animations</button>
<button class="nv" data-p="debug">🐞 Debug &amp; Performance</button>
<button class="nv" data-p="api">📚 API Reference</button>
</aside>
<main>
<!-- ============ START ============ -->
<section id="p-start" class="page on">
<h1>Getting Started</h1>
<p>Lightweight X11 GUI library for C++20 with software rendering, dirty-rectangle partial updates, TTF fonts (FreeType), two built-in themes (WIN11 / TERMINAL) and smooth animations.</p>
<div class="card">
<h3>Features</h3>
<ul>
<li>Partial redraw (dirty rectangles) — only changed regions repaint</li>
<li>Double buffering — no flicker</li>
<li>TTF fonts via FreeType with cached 1-bit masks (fast text)</li>
<li>Two themes: <code>WIN11</code> and <code>TERMINAL</code>, fully customizable</li>
<li>Animations: hover, press, push-down, check, dropdown</li>
<li>EventBus + per-widget callbacks</li>
<li>Debug overlay, dirty-rect visualization, click markers (F1–F6)</li>
</ul>
</div>
<h2>Build</h2>
<pre><code>find_package(X11 REQUIRED)
find_package(Freetype REQUIRED)
add_executable(host
main.cpp
libs/gui/core/event_bus.cpp
libs/gui/core/raw_picture.cpp
libs/gui/core/font_manager.cpp
libs/gui/core/theme.cpp
libs/gui/widgets/widget.cpp
libs/gui/widgets/draw_basic.cpp
libs/gui/widgets/draw_toggles.cpp
libs/gui/widgets/draw_lists.cpp
libs/gui/window.cpp
libs/gui/demo.cpp
)
target_include_directories(host PRIVATE libs/gui ${X11_INCLUDE_DIR})
target_link_libraries(host PRIVATE ${X11_LIBRARIES} Freetype::Freetype)</code></pre>
<h2>Quick start</h2>
<pre><code>#include "gui.h"
int main() {
gui::set_theme(gui::ThemeStyle::WIN11);
gui::window win("My App");
win.set_font("SGr-Iosevka-Regular.ttc");
win.set_default_icon();
win.configur_page("main", [](gui::page&amp; p) {
gui::WidgetData b;
b.id = "btn"; b.text = "Hello";
b.x = 100; b.y = 100; b.width = 300; b.height = 70;
p.Button(b);
}, true);
gui::EventBus::get().on("button.click", [](const gui::EventData&amp; e) {
std::cout &lt;&lt; "clicked: " &lt;&lt; e.source_id &lt;&lt; "\n";
});
while (win.running()) win.update();
}</code></pre>
<div class="tip">Put <code>SGr-Iosevka-Regular.ttc</code> (or any TTF/TTC) next to the binary. If not found, a fallback X11 core font is used.</div>
</section>
<!-- ============ WINDOW ============ -->
<section id="p-window" class="page">
<h1>Window &amp; Pages</h1>
<h2>gui::window</h2>
<table>
<tr><th>Method</th><th>Description</th></tr>
<tr><td><code>window(title)</code></td><td>Create X11 window (1600×900), backbuffer, load font</td></tr>
<tr><td><code>update()</code></td><td>Process events + render one frame (call in a loop)</td></tr>
<tr><td><code>running()</code></td><td>false after WM_DELETE (close button)</td></tr>
<tr><td><code>set_font(path)</code></td><td>Load TTF/TTC font</td></tr>
<tr><td><code>set_font_size(px)</code></td><td>Global font size (default 32)</td></tr>
<tr><td><code>set_icon(path)</code> / <code>set_icon(pic)</code> / <code>set_default_icon()</code></td><td>Window icon (_NET_WM_ICON)</td></tr>
<tr><td><code>set_max_fps(n)</code></td><td>Frame cap (default 240)</td></tr>
<tr><td><code>on_key</code></td><td>std::function&lt;void(KeySym)&gt; — custom hotkeys (F6+)</td></tr>
</table>
<h2>Pages &amp; tabs</h2>
<pre><code>// tabs (left column)
gui::WidgetData tab; tab.id = "main"; tab.text = "main";
tab.on_window_click = [&amp;win](gui::window&amp; w){ w.openPAGE("main"); };
win.addTAB_BUTTON(tab);
// pages
win.addPAGE("main");
win.configur_page("main", [](gui::page&amp; p){ /* fill widgets */ }, true);
win.openPAGE("main");</code></pre>
<div class="tip">Only the <b>active page</b> is ticked and rendered — inactive pages cost zero CPU.</div>
<h2>Finding widgets</h2>
<pre><code>gui::Widget* w = win.find("btn"); // search all pages
if (w) { w->setValue(0.7f); win.mark_dirty(100,100,300,70); }</code></pre>
</section>
<!-- ============ WIDGETS ============ -->
<section id="p-widgets" class="page">
<h1>Widgets</h1>
<h2>WidgetData fields</h2>
<table>
<tr><th>Field</th><th>Default</th><th>Meaning</th></tr>
<tr><td><code>id, text</code></td><td>""</td><td>identifier / label</td></tr>
<tr><td><code>x,y,width,height</code></td><td>0,0,200,60</td><td>geometry (2× scale)</td></tr>
<tr><td><code>bg_color, fg_color, frame_color</code></td><td>0</td><td>0 = take from theme, else override</td></tr>
<tr><td><code>font_size</code></td><td>0</td><td>0 = theme size, else per-widget</td></tr>
<tr><td><code>primary</code></td><td>false</td><td>accent-colored button</td></tr>
<tr><td><code>checked</code></td><td>false</td><td>checkbox / radio state</td></tr>
<tr><td><code>value, target_value</code></td><td>0</td><td>slider / progress (0..1)</td></tr>
<tr><td><code>items</code></td><td>[]</td><td>listbox / dropdown entries</td></tr>
<tr><td><code>picture</code></td><td>null</td><td>RawPicture for sprite / raw widgets</td></tr>
<tr><td><code>on_change / on_page_click / on_window_click</code></td><td>null</td><td>callbacks</td></tr>
</table>
<h2>Factory methods (on page or any container)</h2>
<pre><code>p.Button(d); p.Label(d); p.Input(d); p.Checkbox(d); p.Radiobutton(d);
p.Slider(d); p.Progressbar(d); p.Listbox(d); p.Dropdown(d);
p.TreeView(d); p.TreeNode(d); p.ScrollView(d);
p.RawPictureWidget(d); p.SpriteButton(d);</code></pre>
<h2>Examples</h2>
<pre><code>// accent button
gui::WidgetData b; b.id="go"; b.text="Run"; b.primary=true;
b.x=100; b.y=100; b.width=300; b.height=70;
p.Button(b);
// input field (click to focus, bottom line + blinking caret)
gui::WidgetData i; i.id="name"; i.x=100; i.y=200; i.width=500; i.height=68;
p.Input(i);
// slider drives progress
gui::WidgetData s; s.id="vol"; s.x=100; s.y=300; s.width=600; s.height=48; s.value=0.4f;
p.Slider(s);
gui::WidgetData pr; pr.id="prog"; pr.x=100; pr.y=380; pr.width=600; pr.height=44;
p.Progressbar(pr);</code></pre>
<h2>Nested containers</h2>
<pre><code>auto&amp; sv = p.ScrollView(d); // returns Widget&amp;
sv.Button(inner); // children live inside
auto&amp; tv = p.TreeView(d);
auto&amp; n1 = tv.TreeNode(n); // node
n1.Label(child); // leaf inside node</code></pre>
</section>
<!-- ============ THEMES ============ -->
<section id="p-themes" class="page">
<h1>Themes &amp; Customization</h1>
<h2>Switch built-in themes</h2>
<pre><code>gui::set_theme(gui::ThemeStyle::WIN11); // rounded, blue accent
gui::set_theme(gui::ThemeStyle::TERMINAL); // square, green accent
// hotkey F6 toggles at runtime (see demo)</code></pre>
<h2>Theme struct (all tunable)</h2>
<pre><code>struct Theme {
ThemeStyle style;
int radius, radius_sm, font_size; // 12/8/32 (win11), 0/0/32 (term)
uint32_t window_bg, panel_bg, text, text_dim;
uint32_t btn, btn_h, btn_p, border, border_h; // button + hover + press
uint32_t accent, accent_h, accent_p, on_accent;
uint32_t input_bg, focus;
uint32_t track, prog_bg;
uint32_t list_bg, list_h, list_sel, list_sel_text;
uint32_t tab_bg, tab_h, tab_active;
};
const Theme&amp; theme(); // current</code></pre>
<h2>Per-widget override (beats theme)</h2>
<pre><code>gui::WidgetData d;
d.bg_color = 0x332211; // non-zero overrides theme
d.fg_color = 0xFFCC00;
d.frame_color = 0x00FF88;
d.font_size = 40; // per-widget size</code></pre>
<div class="tip">Rule: <code>0</code> = use theme. Any other value = your override. This is the <code>C(override, theme)</code> helper in <code>widget.h</code>.</div>
<h2>Where theme values live</h2>
<p>Edit <code>libs/gui/core/theme.cpp</code> — the two static structs <code>t_win11</code> / <code>t_term</code>. Field order matches the struct above (e.g. the pair <code>input_bg, focus</code> is the 14th/15th value).</p>
</section>
<!-- ============ CUSTOM ============ -->
<section id="p-custom" class="page">
<h1>Custom Widgets</h1>
<p>Three levels of extension, from easy to full.</p>
<h2>1. Restyle an existing widget (no code change)</h2>
<pre><code>gui::WidgetData d;
d.type = gui::WidgetType::BUTTON; // or set via factory
d.bg_color = 0x402020; d.frame_color = 0xFF4444; d.font_size = 28;
p.Button(d);</code></pre>
<h2>2. Compose with RawPicture (sprite button)</h2>
<pre><code>auto pic = std::make_shared&lt;gui::RawPicture&gt;(300, 100, 32);
pic-&gt;clear(0x202020);
pic-&gt;fillRoundRect(0,0,300,100,16,0x0078D4);
pic-&gt;drawText(win.display(), 20, 30, "Custom", 0xFFFFFF, 1);
gui::WidgetData d; d.picture = pic; d.width=300; d.height=100;
p.SpriteButton(d); // still clickable, emits button.click</code></pre>
<h2>3. Add a brand-new widget type</h2>
<ol>
<li><code>widget.h</code>: add enum value <code>WidgetType::MY_WIDGET</code></li>
<li><code>widget.h</code>: add factory decl <code>Widget&amp; MyWidget(WidgetData);</code></li>
<li><code>widget.cpp</code>: implement factory (<code>a.type = ...; return add_widget(...)</code>)</li>
<li>draw: add a case in <code>Widget::draw</code> dispatch → your <code>wdraw::my(...)</code></li>
<li>input: add a case in <code>Widget::handle_press</code> (return 1 consumed / 2 capture)</li>
</ol>
<pre><code>// widget.cpp factory
Widget&amp; combat::MyWidget(WidgetData a){ a.type=WidgetType::MY_WIDGET; return add_widget(std::move(a)); }
// draw dispatch (Widget::draw)
case WidgetType::MY_WIDGET: wdraw::my(d, win, gc, *this, ax, ay); break;
// your renderer (any draw_*.cpp)
void wdraw::my(Display* d, Drawable win, GC gc, const Widget&amp; w, int ax, int ay) {
const auto&amp; t = theme();
XSetForeground(d, gc, t.accent);
x_fill_round(d, win, gc, ax, ay, w.data.width, w.data.height, t.radius);
gui_text(d, win, gc, ax+12, gui_base_c(ay, w.data.height, t.font_size),
w.data.text, t.on_accent, t.font_size);
}</code></pre>
<div class="warn">Declare your <code>wdraw::my</code> in <code>widget.h</code> and, if it needs protected <code>widgets</code>, add it as <code>friend</code> of <code>combat</code> (like basic/toggles/lists).</div>
</section>
<!-- ============ EVENTS ============ -->
<section id="p-events" class="page">
<h1>Events</h1>
<h2>EventBus</h2>
<pre><code>int tok = gui::EventBus::get().on("button.click", [](const gui::EventData&amp; e){ ... });
gui::EventBus::get().off(tok);
gui::EventBus::get().emit("my.event", {"id","text",0.5f,1,true});</code></pre>
<h2>Built-in events</h2>
<table>
<tr><th>Event</th><th>Data</th></tr>
<tr><td><code>button.click</code></td><td>source_id, text</td></tr>
<tr><td><code>checkbox.change</code></td><td>source_id, flag</td></tr>
<tr><td><code>radio.change</code></td><td>source_id, flag=true</td></tr>
<tr><td><code>slider.change</code></td><td>source_id, value 0..1</td></tr>
<tr><td><code>list.select</code></td><td>source_id, text, index</td></tr>
<tr><td><code>dropdown.select</code></td><td>source_id, text, index</td></tr>
<tr><td><code>tree.toggle</code></td><td>source_id, flag=expanded</td></tr>
<tr><td><code>input.submit</code></td><td>source_id, text (Enter)</td></tr>
<tr><td><code>page.open</code></td><td>source_id = page name</td></tr>
</table>
<h2>Per-widget callbacks (alternative)</h2>
<pre><code>d.on_change = []{ ... }; // value/state changed
d.on_page_click = [](gui::page&amp; p){ ... }; // button clicked
d.on_window_click= [](gui::window&amp; w){ ... };</code></pre>
<h2>Global logger (debug)</h2>
<pre><code>gui::EventBus::get().set_global_logger([](const std::string&amp; e, const gui::EventData&amp; d){
std::cout &lt;&lt; "[LOG] " &lt;&lt; e &lt;&lt; "\n";
});</code></pre>
</section>
<!-- ============ ANIM ============ -->
<section id="p-anim" class="page">
<h1>Animations</h1>
<p>All animation state lives in <code>struct Widget</code> as 0..1 floats, advanced in <code>Widget::tick(dt)</code> and applied in <code>Widget::draw()</code>.</p>
<table>
<tr><th>Field</th><th>Drives</th><th>Where applied</th></tr>
<tr><td><code>hover_a</code></td><td>hover highlight</td><td>draw_basic / toggles color mix</td></tr>
<tr><td><code>press_a</code></td><td>press darkening</td><td>draw_basic color mix</td></tr>
<tr><td><code>push_a</code></td><td>push-down offset (6px)</td><td><code>Widget::draw</code>: <code>ay += ease(push_a)*6</code></td></tr>
<tr><td><code>check_a</code></td><td>checkmark / radio dot reveal</td><td>draw_toggles</td></tr>
<tr><td><code>anim</code></td><td>dropdown open/close</td><td>draw_lists popup height</td></tr>
<tr><td><code>cursor_timer / cursor_visible</code></td><td>input caret blink (0.5s)</td><td>draw_basic</td></tr>
</table>
<h2>Tune speed</h2>
<pre><code>// Widget::tick — the speed constants
hover_a = approach(hover_a, hovered?1:0, dt, 10); // higher = snappier
push_a = approach(push_a, pressed?1:0, dt, 18);
check_a = approach(check_a, checked?1:0, dt, 10);</code></pre>
<h2>Disable push-down (e.g. for slider)</h2>
<pre><code>// Widget::draw
int ay = data.y + oy + (data.type == WidgetType::SLIDER ? 0 : (int)(ease(push_a)*6));</code></pre>
<h2>Remove color flicker (design rule)</h2>
<div class="tip">Colors fed into <code>x_fill_round</code>/<code>x_draw_round</code>/<code>XDrawArc</code> must NOT depend on animated <code>hover_a</code>/<code>press_a</code>. Keep animation in safe places: checkmark lines, radio dot, slider thumb position, push-down offset. This is why checkbox/radio use static border colors.</div>
</section>
<!-- ============ DEBUG ============ -->
<section id="p-debug" class="page">
<h1>Debug &amp; Performance</h1>
<h2>Hotkeys</h2>
<table>
<tr><th>Key</th><th>Action</th></tr>
<tr><td>F1</td><td>Debug overlay (FPS, widgets, partial updates, mouse, event log)</td></tr>
<tr><td>F2</td><td>Dirty-rect visualization (XOR flash of repainted regions)</td></tr>
<tr><td>F3</td><td>Click markers (red cross at click point)</td></tr>
<tr><td>F4</td><td>Widget bounds (magenta outlines)</td></tr>
<tr><td>F5</td><td>Console event logging</td></tr>
<tr><td>F6</td><td>Toggle theme WIN11 ↔ TERMINAL (via <code>on_key</code>)</td></tr>
</table>
<h2>Programmatic access</h2>
<pre><code>win.debug().show_debug_overlay = true;
win.debug().show_dirty_rects = true;
win.debug().max_log_size = 30;
std::cout &lt;&lt; win.debug().fps &lt;&lt; "\n";
win.mark_dirty(x,y,w,h); // invalidate a region
win.mark_all_dirty(); // full repaint</code></pre>
<h2>Why it's fast</h2>
<ul>
<li><b>Dirty rectangles</b> — only changed regions redraw; <code>Partial updates</code> counter shows it</li>
<li><b>Only active page</b> is ticked/rendered</li>
<li><b>Text cache</b> — strings rendered once to 1-bit masks, drawn with one <code>XFillRectangle</code> (no XGetImage)</li>
<li><b>Overlay throttle</b> — debug panel refreshes at 10 Hz</li>
<li><b>Idle sleep</b> — <code>usleep(10ms)</code> when nothing is dirty → ~0% CPU at rest</li>
<li><b>Frame cap</b> — <code>set_max_fps()</code>, default 240</li>
</ul>
<div class="warn">F2 flashes only visualize <i>user</i> dirty rects (snapshot taken before flash/marker/overlay rects) — no feedback loop, FPS stays high.</div>
</section>
<!-- ============ API ============ -->
<section id="p-api" class="page">
<h1>API Reference</h1>
<h2>gui::window</h2>
<pre><code>window(title) / ~window()
update() running() display()
addTAB_BUTTON(d) addPAGE(n) configur_page(n, fn, active) openPAGE(n)
find(id) set_focus(w)
set_font(p) set_font_size(px) set_icon(p|pic) set_default_icon() set_max_fps(n)
debug() mark_dirty(x,y,w,h) mark_all_dirty()
std::function&lt;void(KeySym)&gt; on_key;</code></pre>
<h2>gui::combat (container base: page, Widget)</h2>
<pre><code>Button/Label/Input/Checkbox/Radiobutton/Slider/Progressbar/
Listbox/Dropdown/TreeView/TreeNode/ScrollView/RawPictureWidget/SpriteButton
visit(fn) find(id) find_at(x,y) count_widgets()
tick(dt) render_all(...) render_dirty(...) mark_all_dirty(...)
process_press(...) process_wheel(...)</code></pre>
<h2>gui::Widget</h2>
<pre><code>WidgetData data;
float hover_a, press_a, check_a, push_a, anim, phase;
bool hovered, pressed, focused, cursor_visible, open_target;
float cursor_timer;
setValue(v) getValue()
tick(dt) draw(...) draw_popup(...)
handle_press/drag/release/wheel(...)
contains(...) content_height() mark_dirty(...)</code></pre>
<h2>gui::RawPicture</h2>
<pre><code>create(w,h,bits) clear(c) setPixel/getPixel/blendPixel
drawLine/drawRect/fillRect/fillRoundRect
drawCircle/fillCircle/fillTriangle/fillPolygon
drawText(dpy,x,y,t,c,scale) blit(src,dx,dy)
load(path) // BMP 24/32, PPM P6
present(dpy,win,gc,dx,dy)</code></pre>
<h2>gui::FontManager</h2>
<pre><code>FontManager::get()
load(path) available() set_default_size(px) default_size()
ascent/descent/line_height/text_width(size)
draw(RawPicture&amp;,...) draw(Display*,Drawable,GC,...)</code></pre>
<h2>gui::EventBus / gui::DirtyRegion / gui::Theme</h2>
<pre><code>EventBus::get().on/off/emit/set_global_logger
DirtyRegion: add/mark_all/is_dirty/intersects/get/clear
theme() set_theme(ThemeStyle)</code></pre>
</section>
</main>
<script>
function go(id){
document.querySelectorAll('.page').forEach(p=>p.classList.remove('on'));
document.querySelectorAll('.nv').forEach(n=>n.classList.remove('on'));
document.getElementById('p-'+id).classList.add('on');
document.querySelector('[data-p="'+id+'"]').classList.add('on');
window.scrollTo(0,0);
}
document.querySelectorAll('.nv').forEach(n=>n.onclick=()=>go(n.dataset.p));
// wrap every <pre> with copy button
document.querySelectorAll('pre').forEach(pre=>{
const wrap=document.createElement('div'); wrap.className='cb';
pre.parentNode.insertBefore(wrap,pre); wrap.appendChild(pre);
const b=document.createElement('button'); b.className='cp'; b.textContent='Copy';
b.onclick=()=>{ navigator.clipboard.writeText(pre.innerText).then(()=>{
b.textContent='Copied!'; setTimeout(()=>b.textContent='Copy',1200);
});};
wrap.appendChild(b);
});
// sidebar search
document.getElementById('q').addEventListener('input',e=>{
const t=e.target.value.toLowerCase();
document.querySelectorAll('.nv').forEach(n=>{
n.style.display = n.textContent.toLowerCase().includes(t) ? 'block' : 'none';
});
});
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB