Initial commit: X11 GUI
This commit is contained in:
parent
975fba0ba8
commit
1b90a3ba62
522
docs.html
522
docs.html
@ -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
432
docs/index.html
Normal 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 & Pages</button>
|
||||
<button class="nv" data-p="widgets">🧩 Widgets</button>
|
||||
<button class="nv" data-p="themes">🎨 Themes & 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 & 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& 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& e) {
|
||||
std::cout << "clicked: " << e.source_id << "\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 & 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<void(KeySym)> — custom hotkeys (F6+)</td></tr>
|
||||
</table>
|
||||
<h2>Pages & tabs</h2>
|
||||
<pre><code>// tabs (left column)
|
||||
gui::WidgetData tab; tab.id = "main"; tab.text = "main";
|
||||
tab.on_window_click = [&win](gui::window& w){ w.openPAGE("main"); };
|
||||
win.addTAB_BUTTON(tab);
|
||||
|
||||
// pages
|
||||
win.addPAGE("main");
|
||||
win.configur_page("main", [](gui::page& 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& sv = p.ScrollView(d); // returns Widget&
|
||||
sv.Button(inner); // children live inside
|
||||
auto& tv = p.TreeView(d);
|
||||
auto& n1 = tv.TreeNode(n); // node
|
||||
n1.Label(child); // leaf inside node</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- ============ THEMES ============ -->
|
||||
<section id="p-themes" class="page">
|
||||
<h1>Themes & 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& 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<gui::RawPicture>(300, 100, 32);
|
||||
pic->clear(0x202020);
|
||||
pic->fillRoundRect(0,0,300,100,16,0x0078D4);
|
||||
pic->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& 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& 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& w, int ax, int ay) {
|
||||
const auto& 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& 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& p){ ... }; // button clicked
|
||||
d.on_window_click= [](gui::window& w){ ... };</code></pre>
|
||||
<h2>Global logger (debug)</h2>
|
||||
<pre><code>gui::EventBus::get().set_global_logger([](const std::string& e, const gui::EventData& d){
|
||||
std::cout << "[LOG] " << e << "\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 & 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 << win.debug().fps << "\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<void(KeySym)> 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&,...) 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>
|
||||
BIN
docs/screenshots/theme_win11.png
Normal file
BIN
docs/screenshots/theme_win11.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Loading…
x
Reference in New Issue
Block a user