# HUD и интерфейс — `$.ui`

`$.ui` — слой HUD: узлы, положенные в него, рисуются в координатах **окна** и
не двигаются с камерой. Тексты, полосы, иконки и свои шрифты живут здесь.

Интерфейс игры делается на RmlUi (`$.ui.doc`) — см. закон
[UI_RMLUI_LAW.md](UI_RMLUI_LAW): узлы `<ui.*>` остаются рабочими как
быстрый рисователь HUD, но новые меню, экраны и диалоги на них не строятся.

```js
$('<ui.label>', { id: 'hp', text: 'HP 100', size: 20 }).at(60, 30).appendTo($.ui);
$('<ui.bar>', { id: 'stam', value: 0, max: 100, w: 200, h: 12 }).at(60, 60).appendTo($.ui);
$.ui.icon('play', 24);                      // иконка Material Design
```

---

## 1. Методы

| Вызов | Смысл |
|---|---|
| `label(text, opts?)` | подпись |
| `bar(value, max, opts?)` | полоса |
| `text(text, opts?)` | многострочный текст |
| `icon(name, size?)` / `hasIcon(name)` / `iconNames()` / `iconCount()` | иконки Material Design |
| `on(event, fn)` / `listeners()` / `off()` | события интерфейса |
| `show()` / `hide()` / `visible(on?)` | видимость всего слоя |
| `html(markup)` / `style(css)` / `unload()` | RmlUi-документ и его стиль |
| `fps()` / `cls()` / `doc()` | диагностика и доступ к документу |
| `scale(value?)` / `scaleValue()` | масштаб интерфейса (0.25…4) |
| `aria(sel, props?)` / `ariaOf(sel)` / `ariaCount()` | семантика для ассистивных технологий |

## 2. Узлы интерфейса

| Тег | Назначение |
|---|---|
| `ui.label` | подпись |
| `ui.button` | кнопка (событие `click`, `activate` по Enter) |
| `ui.bar` | полоса прогресса |
| `ui.panel` | прямоугольник-подложка |
| `ui.list` | список с выбором |
| `ui.input` | поле ввода (учитывает `$.input.text()`) |
| `ui.image` | картинка из атласа или файла |

Полный список и события — [widgets.md](highlevel/widgets).

## 3. Масштаб интерфейса

```js
$.ui.scale(1.5);        // крупнее: 4K-экран или слабое зрение
$.ui.scale();           // прочитать текущий
$.ui.scale(1);          // вернуть как было
```

Масштаб умножает **положение, размер и кегль** всех узлов `<ui.*>`. Величина
**абсолютная**: повторный `scale(1.5)` не увеличит вдвое, а поставит ровно 1.5
(пересчёт идёт от текущего значения к новому), поэтому «вернуть как было» —
это `scale(1)`, и узлы встают на исходные числа. Значение зажимается в
`0.25…4`, ноль и нечисло не ломают интерфейс.

Масштаб живёт **в модуле**, а не в подсистеме: это настройка игрока, и она не
сбрасывается при `createApi()` (тесты создают свой API).

## 4. Доступность (a11y)

```js
$.ui.aria('#hp',   { role: 'status', label: 'Здоровье', live: 'polite' });
$.ui.aria('#stam', { role: 'progressbar', label: 'Выносливость', max: 100 });
$.ui.ariaOf('#hp');     // { role: 'status', label: 'Здоровье', live: 'polite' }
$.ui.ariaCount();       // сколько узлов размечено
```

Свойства лежат на узле (`node.aria`) и **попадают в снимок агента** в разделе
`ui`, поэтому доступность интерфейса проверяется автотестом, а не на глаз.
Дополняющий вызов не затирает записанное поле.

**Честно:** движок сам ничего не произносит и не строит дерево доступности —
он только хранит и отдаёт семантику. Озвучивание делает оболочка, которая
читает снимок или `ariaOf`.

## 5. Документ RmlUi: значения, события и программное нажатие

`$.ui.doc(path)` возвращает обёртку документа. Помимо `show/hide/text/html/
cls/style/on`, у неё есть чтение состояния — оно нужно инструментам (SDK) и
тестам агента, которым нужны структурированные значения и программное нажатие.
Виртуальная мышь агента также передаётся в RmlUi через SDL
(`tests/agent/ui_virtual_mouse_test.py`):

| Вызов | Смысл |
|---|---|
| `value(id)` / `setValue(id, v)` | значение `<input>`, `<textarea>`, `<select>` (или атрибута `value`) |
| `content(id)` | внутренняя разметка элемента — то, что записали `text()`/`html()` |
| `attr(id, name)` / `attr(id, name, v)` | атрибут элемента (`null`, если его нет) |
| `rect(id)` | `{ x, y, w, h }` элемента в координатах окна; скрытый и ещё не размеченный — нулевой размер |
| `click(id)` | событие `click`, как от мыши; обработчики вызываются сразу |

**Делегирование.** Обработчик, повешенный на контейнер, получает события
потомков (всплытие) и три дополнительных аргумента:
`on('list', 'click', (elementId, eventName, targetKey, targetId) => …)`.
`targetKey` — значение `data-key` ближайшего к цели элемента вверх по дереву
до контейнера, `targetId` — `id` цели. Один обработчик на список вместо сотен
на строки: лимит обработчиков `ui.on` — 256 на рантайм.

```js
doc.html('assets', '<div class="row" data-key="a.png">a.png</div>');
doc.on('assets', 'click', (id, ev, key) => select(key));   // key === 'a.png'
```

## 6. Ограничения

* **раскладка вручную**: `at()` и `size()`; автораскладки и контейнеров
  (flex/grid) нет — только то, что даёт RmlUi через `$.ui.html`;
* **слой один**: второго независимого HUD-слоя нет, порядок задаётся созданием;
* **`:picked` работает и для мира**: мировой picking учитывает transform камеры;
  явный выбор — `$.pick` / `$.pickAll`;
* **legacy-модальность блокирует мышь и клавиатуру** для узлов вне активного
  диалога; новые диалоги создаются на RmlUi;
* **масштаб не перестраивает раскладку**: он умножает числа у узлов, а не
  пересчитывает «прилипание к краю» — элементы, прижатые к правому краю,
  после `scale(1.5)` уедут за экран, если игра не пересчитала их сама;
* **узлы, созданные после `scale()`**, получают масштаб только при следующем
  вызове `scale()`: ставьте масштаб при запуске, до создания HUD, либо зовите
  `scale()` ещё раз;
* **`rect()` скрытого экрана — ноль**, а у уже скрытого после показа может
  остаться прежняя геометрия: для «виден ли» смотрите свой флаг состояния;
* **`overflow: auto` требует стилей полосы прокрутки.** Без правил `scrollbarvertical`,
  `slidertrack`, `sliderbar` (и `sliderarrowdec/inc` с нулевым размером) RmlUi не знает ширину полосы,
  отдаёт под неё всю ширину контейнера и схлопывает содержимое в ноль, как только оно не
  помещается по высоте. Пример рабочих правил — `sdk/ui/theme.rcss`;
* **`rect()` и прокрутка.** RmlUi прокручивает плавно (колесо, `scrollIntoView`): положение
  элементов в прокручиваемом контейнере меняется ещё несколько кадров — для клика по такому
  элементу сначала дождитесь, пока `rect()` перестанет меняться;
* **`value()`/`content()`/`attr()` отдают не больше 8 КБ** (буфер `engine.ui.getValue/getText/getAttr`):
  длинную разметку проверяйте по частям, например по `id` вложенных элементов;
* **мышь агента** (`mouseMove`, `mouse`, `wheel`) доходит до RmlUi теми же событиями, что и
  настоящая ([AGENT_API.md](AGENT_API) §3.5);
* **`aria` — хранилище, а не движок доступности**: фокус, порядок обхода и
  озвучивание движок не делает (см. выше).
