Inspector: собственное поле и Undo/Redo
Заменить одно поле ресурса и сохранить правильный обмен данными.
Inspector-плагин заменяет интерфейс выбранного свойства, а не весь ресурс. В примере обрабатываются только DocsInspectorSettings.Amount и его числовой тип; остальные объекты остаются у стандартного Inspector.
Подготовьте пример
Создайте asset://addons/docs_inspector/. Сохраните четыре C#-файла ниже и plugin.cfg в этот каталог, соберите проект и включите дополнение. Затем создайте ресурс DocsInspectorSettings и откройте его в Inspector. [GlobalClass] объявляет тип ресурса для редактора; все классы примера работают как tool-код.
using Reckfell;
[Tool, GlobalClass]
public partial class DocsInspectorSettings : Resource
{
[Export(PropertyHint.Range, "0,100,0.5")]
public double Amount { get; set; } = 2;
}using Reckfell;
[Tool]
public partial class AmountEditor : EditorProperty
{
private readonly SpinBox _spin = new() { Name = "Value", MinValue = 0, MaxValue = 100, Step = 0.5 };
private bool _readOnly;
public AmountEditor()
{
Name = "DocsAmountEditor";
AddChild(_spin); AddFocusable(_spin.GetLineEdit());
_spin.ValueChanged += OnValueChanged;
}
private void OnValueChanged(double value)
{
if (!_readOnly) EmitChanged(GetEditedProperty(), value);
}
public override void _UpdateProperty()
{
var edited = GetEditedObject();
if (edited is not null) _spin.SetValueNoSignal(edited.Get(GetEditedProperty()).AsDouble());
}
public override void _SetReadOnly(bool readOnly)
{
_readOnly = readOnly; _spin.Editable = !readOnly;
}
}using Reckfell;
[Tool]
public partial class AmountInspector : EditorInspectorPlugin
{
public override bool _CanHandle(ReckfellObject @object) => @object is DocsInspectorSettings;
public override bool _ParseProperty(ReckfellObject @object, Variant.Type type, string name,
PropertyHint hintType, string hintString, PropertyUsageFlags usageFlags, bool wide)
{
if (type != Variant.Type.Float || name != "Amount") return false;
AddPropertyEditor(name, new AmountEditor(), false, "Amount");
return true;
}
}using Reckfell;
[Tool]
public partial class DocsInspectorPlugin : EditorPlugin
{
private AmountInspector? _inspector;
public override void _EnterTree()
{
_inspector = new AmountInspector(); AddInspectorPlugin(_inspector);
}
public override void _ExitTree()
{
if (_inspector is null) return;
RemoveInspectorPlugin(_inspector);
var mainInspector = EditorInterface.Singleton.GetInspector();
var edited = mainInspector.GetEditedObject();
if (edited is DocsInspectorSettings)
{
mainInspector.Edit(null!); mainInspector.Edit(edited);
}
_inspector.Dispose(); _inspector = null;
}
}[plugin]
name="Docs amount inspector"
description="Custom numeric editor for DocsInspectorSettings"
author="Reckfell documentation"
version="1.0.0"
script="DocsInspectorPlugin.cs"AmountInspector ограничивает _CanHandle() конкретным классом, а _ParseProperty() — именем и типом свойства. Возврат true означает, что свойство обработано и стандартный редактор не нужен. Возврат false оставляет обычную обработку. Не возвращайте true для всех свойств только потому, что удалось создать виджет.
Два направления обмена
При вводе AmountEditor вызывает EmitChanged(GetEditedProperty(), value). Inspector получает изменение и проводит его через редакторский механизм, включая Undo/Redo. Простое присваивание settings.Amount внутри обработчика не заменяет этот путь.
При обновлении _UpdateProperty() читает текущее значение ресурса и задаёт виджет через SetValueNoSignal(). Это не пользовательский ввод: нельзя снова посылать EmitChanged, иначе появятся петля обновлений и лишние действия отмены. _SetReadOnly() отдельно отключает ввод и защищает обработчик изменения.
В проверке начальное значение 2 заменилось на 5,5, Undo вернул 2, Redo — 5,5. Внешнее присваивание 7,5 и обновление виджета не добавили сигнал изменения. Это проверяет контракт обновления, а не обещает мгновенное обновление UI без ожидания редакторского цикла.
PropertyHint.Range и SpinBox задают интерфейс диапазона. Автоматическое свойство ресурса само по себе не проверяет любое внешнее присваивание. Если игре нужен строгий инвариант, добавьте отдельную валидацию в модель.
Очистка и повторное включение
EditorInspectorPlugin — ref-counted объект: снимите его через RemoveInspectorPlugin(), затем освободите свою ссылку через Dispose(). Не применяйте к нему Entity-операцию QueueFree().
Пример при отключении перестраивает главный Inspector, если там открыт его ресурс. Это удаляет старый пользовательский EditorProperty; повторное включение не должно оставлять два поля. После регистрации дополнения при уже выбранном объекте перевыберите ресурс, чтобы Inspector заново разобрал свойства.
Собственный виджет после передачи Inspector принадлежит его UI. Не освобождайте активное поле вручную из обработчика SpinBox. Отдельные плавающие Inspector этим примером не обслуживаются и требуют своей проверки.
Проверены привязка к свойству, одиночное изменение, Undo/Redo, обновление без сигнала, read-only, отключение, повторное включение и отказ от постороннего Resource. Физический ввод, внешний вид, сохранение/повторная загрузка ресурса, плавающие Inspector и экспорт не проверялись.