Начните с обычного корня UI Toolkit.
Создайте GameObject, добавьте UIDocument и назначьте Panel Settings. LumaFlow не заменяет UI Toolkit: он монтирует своё дерево внутри rootVisualElement.
MonoBehaviour владеет UIDocument и возвращённым MountHandle. LumaFlow управляет только созданным host-элементом и его дочерним деревом.
Монтируйте дерево один раз при активации host.
Добавьте компонент на тот же GameObject. Начнём со статического текста, но жизненный цикл уже будет пригоден для production.
using LumaFlow;
using UnityEngine;
using UnityEngine.UIElements;
using Framework = LumaFlow.LumaFlow;
[RequireComponent(typeof(UIDocument))]
public sealed class ProjectCounterScreen : MonoBehaviour {
private MountHandle? _mount;
private void OnEnable() {
var root = GetComponent<UIDocument>().rootVisualElement;
_mount = Framework.Mount(BuildScreen(), root);
}
private static Widget BuildScreen() =>
new Text("Проекты");
}Опишите изменяемое значение явно.
Widget — неизменяемое описание, поэтому счётчик хранится вне него. ReactiveBuilder<T> читает значение; при изменении перестраивается именно эта ветвь.
private readonly State<int> _count = new(0);
private Widget BuildScreen() => new Column(
gap: 12f,
children: new Widget[] {
new Text("Обзор проектов"),
new ReactiveBuilder<int>(
_count, value => new Text($"Активных проектов: {value}")),
new Button("Добавить проект", () => _count.Value++)
});Build может вызываться многократно. State и контроллер должны принадлежать компоненту или State объекта StatefulWidget.
Освободите mount при деактивации документа.
Dispose() удаляет host LumaFlow и подписки. Нативные соседние элементы в корне UIDocument он не затрагивает.
private void OnDisable() {
_mount?.Dispose();
_mount = null;
}Теперь разберите, почему этот код работает.
Следующий материал объясняет Widget, Element, reconciliation, State и Key без привязки к абстрактным терминам.
Перейти к архитектуре →