Files
MovementTests/addons/forge/editor/statescript/resolvers/RandomResolverEditor.cs
Minimata e09714cf83
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 27s
Create tag and build when new code gets to main / Export (push) Successful in 7m25s
update forge
2026-05-17 00:06:44 +02:00

463 lines
12 KiB
C#

// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using System.Collections.Generic;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using GodotVector2 = Godot.Vector2;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class RandomResolverEditor : NodeEditorProperty
{
private enum ResolverSlot
{
Min = 0,
Max = 1,
}
private StatescriptGraph? _graph;
private Action? _onChanged;
private Type _expectedType = typeof(ForgeVariant128);
private OptionButton? _typeDropdown;
private FoldableContainer? _minFoldable;
private FoldableContainer? _maxFoldable;
private FoldableContainer? _inclusiveMaxFoldable;
private OptionButton? _minResolverDropdown;
private OptionButton? _maxResolverDropdown;
private VBoxContainer? _minEditorContainer;
private VBoxContainer? _maxEditorContainer;
private NodeEditorProperty? _minEditor;
private NodeEditorProperty? _maxEditor;
private CheckBox? _inclusiveMaxCheckBox;
private StatescriptVariableType _valueType = StatescriptVariableType.Int;
private bool _isMaxInclusive = true;
private List<Func<NodeEditorProperty>> _factories = [];
public override string DisplayName => "Random";
public override string ResolverTypeId => "Random";
public override bool IsCompatibleWith(Type expectedType)
{
if (expectedType == typeof(ForgeVariant128))
{
return true;
}
return expectedType == typeof(int)
|| expectedType == typeof(float)
|| expectedType == typeof(double);
}
public override void Setup(
StatescriptGraph graph,
StatescriptNodeProperty? property,
Type expectedType,
Action onChanged,
bool isArray)
{
_graph = graph;
_onChanged = onChanged;
_expectedType = expectedType;
var existing = property?.Resolver as RandomResolverResource;
_valueType = existing?.ValueType ?? GetDefaultValueType(expectedType);
_isMaxInclusive = existing?.IsMaxInclusive ?? true;
_factories = ResolverEditorFactoryCatalog.GetCompatibleFactories(GetBroadExpectedClrType(_valueType));
SizeFlagsHorizontal = SizeFlags.ExpandFill;
var root = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
AddChild(root);
bool allowTypeSelection = expectedType == typeof(ForgeVariant128);
if (allowTypeSelection)
{
var typeRow = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
root.AddChild(typeRow);
typeRow.AddChild(new Label
{
Text = "Type:",
CustomMinimumSize = new GodotVector2(45, 0),
HorizontalAlignment = HorizontalAlignment.Right,
});
_typeDropdown = new OptionButton { SizeFlagsHorizontal = SizeFlags.ExpandFill };
_typeDropdown.AddItem("Int");
_typeDropdown.AddItem("Float");
_typeDropdown.Selected = GetTypeDropdownIndex(_valueType);
_typeDropdown.ItemSelected += OnTypeDropdownItemSelected;
typeRow.AddChild(_typeDropdown);
}
BuildResolverSlot(
root,
ResolverSlot.Min,
"Min:",
existing?.Left,
existing?.MinFolded ?? true,
out _minFoldable,
out _minResolverDropdown,
out _minEditorContainer,
x => _minEditor = x);
BuildResolverSlot(
root,
ResolverSlot.Max,
"Max:",
existing?.Right,
existing?.MaxFolded ?? true,
out _maxFoldable,
out _maxResolverDropdown,
out _maxEditorContainer,
x => _maxEditor = x);
_inclusiveMaxFoldable = new FoldableContainer
{
Title = "Inclusive Max:",
Folded = existing?.InclusiveMaxFolded ?? true,
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
_inclusiveMaxFoldable.FoldingChanged += OnInclusiveMaxFoldableFoldingChanged;
root.AddChild(_inclusiveMaxFoldable);
var inclusiveMaxRow = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
_inclusiveMaxFoldable.AddChild(inclusiveMaxRow);
var exclusiveButton = new CheckBox
{
Text = "Exclusive",
ButtonPressed = !_isMaxInclusive,
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
_inclusiveMaxCheckBox = new CheckBox
{
Text = "Inclusive",
ButtonPressed = _isMaxInclusive,
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
var buttonGroup = new ButtonGroup();
exclusiveButton.ButtonGroup = buttonGroup;
_inclusiveMaxCheckBox.ButtonGroup = buttonGroup;
inclusiveMaxRow.AddChild(exclusiveButton);
inclusiveMaxRow.AddChild(_inclusiveMaxCheckBox);
exclusiveButton.Pressed += OnExclusiveMaxPressed;
_inclusiveMaxCheckBox.Pressed += OnInclusiveMaxPressed;
UpdateFoldableTitles();
}
public override void SaveTo(StatescriptNodeProperty property)
{
property.Resolver = new RandomResolverResource
{
ValueType = _valueType,
Left = SaveNestedEditor(_minEditor),
MinFolded = _minFoldable?.Folded ?? false,
Right = SaveNestedEditor(_maxEditor),
MaxFolded = _maxFoldable?.Folded ?? false,
IsMaxInclusive = _isMaxInclusive,
InclusiveMaxFolded = _inclusiveMaxFoldable?.Folded ?? false,
};
}
public override void ClearCallbacks()
{
base.ClearCallbacks();
_onChanged = null;
_minEditor?.ClearCallbacks();
_maxEditor?.ClearCallbacks();
_typeDropdown = null;
_minFoldable = null;
_maxFoldable = null;
_inclusiveMaxFoldable = null;
_minResolverDropdown = null;
_maxResolverDropdown = null;
_minEditorContainer = null;
_maxEditorContainer = null;
_minEditor = null;
_maxEditor = null;
_inclusiveMaxCheckBox = null;
}
private static StatescriptResolverResource? SaveNestedEditor(NodeEditorProperty? editor)
{
if (editor is null)
{
return null;
}
var property = new StatescriptNodeProperty();
editor.SaveTo(property);
return property.Resolver;
}
private static Type GetBroadExpectedClrType(StatescriptVariableType valueType)
{
return valueType switch
{
StatescriptVariableType.Float => typeof(double),
StatescriptVariableType.Double => typeof(double),
_ => typeof(int),
};
}
private static StatescriptVariableType GetDefaultValueType(Type expectedType)
{
if (expectedType == typeof(double))
{
return StatescriptVariableType.Double;
}
if (expectedType == typeof(float))
{
return StatescriptVariableType.Double;
}
return StatescriptVariableType.Int;
}
private static int GetTypeDropdownIndex(StatescriptVariableType valueType)
{
return valueType switch
{
StatescriptVariableType.Float => 1,
StatescriptVariableType.Double => 1,
_ => 0,
};
}
private void BuildResolverSlot(
VBoxContainer root,
ResolverSlot slot,
string title,
StatescriptResolverResource? existingResolver,
bool folded,
out FoldableContainer foldable,
out OptionButton resolverDropdown,
out VBoxContainer editorContainer,
Action<NodeEditorProperty?> setEditor)
{
foldable = new FoldableContainer { Title = title, Folded = folded };
foldable.FoldingChanged += OnResolverSlotFoldableFoldingChanged;
root.AddChild(foldable);
var container = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
foldable.AddChild(container);
resolverDropdown = CreateResolverDropdown(existingResolver);
editorContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
container.AddChild(resolverDropdown);
container.AddChild(editorContainer);
ShowEditor(GetSelectedIndex(existingResolver), existingResolver, editorContainer, setEditor);
if (slot == ResolverSlot.Min)
{
resolverDropdown.ItemSelected += OnMinResolverDropdownItemSelected;
}
else
{
resolverDropdown.ItemSelected += OnMaxResolverDropdownItemSelected;
}
}
private void OnTypeDropdownItemSelected(long index)
{
_valueType = index switch
{
1 => StatescriptVariableType.Double,
_ => StatescriptVariableType.Int,
};
_factories = ResolverEditorFactoryCatalog.GetCompatibleFactories(GetBroadExpectedClrType(_valueType));
RebuildResolverSlot(_minEditorContainer, x => _minEditor = x);
RebuildResolverSlot(_maxEditorContainer, x => _maxEditor = x);
UpdateFoldableTitles();
_onChanged?.Invoke();
RaiseLayoutSizeChanged();
}
private void OnMinResolverDropdownItemSelected(long index)
{
HandleResolverChanged((int)index, _minEditorContainer, x => _minEditor = x);
}
private void OnMaxResolverDropdownItemSelected(long index)
{
HandleResolverChanged((int)index, _maxEditorContainer, x => _maxEditor = x);
}
private void OnInclusiveMaxChanged(bool isInclusive)
{
if (_isMaxInclusive == isInclusive)
{
return;
}
_isMaxInclusive = isInclusive;
UpdateInclusiveMaxFoldableTitle();
_onChanged?.Invoke();
}
private void OnExclusiveMaxPressed()
{
OnInclusiveMaxChanged(false);
}
private void OnInclusiveMaxPressed()
{
OnInclusiveMaxChanged(true);
}
private void OnInclusiveMaxFoldableFoldingChanged(bool folded)
{
UpdateInclusiveMaxFoldableTitle();
_onChanged?.Invoke();
RaiseLayoutSizeChanged();
}
private void OnResolverSlotFoldableFoldingChanged(bool folded)
{
UpdateFoldableTitles();
_onChanged?.Invoke();
RaiseLayoutSizeChanged();
}
private void RebuildResolverSlot(VBoxContainer? editorContainer, Action<NodeEditorProperty?> setEditor)
{
if (editorContainer?.GetParent() is not VBoxContainer slotContainer)
{
return;
}
if (slotContainer.GetChildCount() == 0 || slotContainer.GetChild(0) is not OptionButton dropdown)
{
return;
}
dropdown.Clear();
foreach (Func<NodeEditorProperty> factory in _factories)
{
dropdown.AddItem(StatescriptResolverRegistry.GetDisplayName(factory));
}
int selectedIndex = GetSelectedIndex(null);
dropdown.Selected = selectedIndex;
HandleResolverChanged(selectedIndex, editorContainer, setEditor);
}
private void HandleResolverChanged(
int selectedIndex,
VBoxContainer? editorContainer,
Action<NodeEditorProperty?> setEditor)
{
if (editorContainer is null)
{
return;
}
foreach (Node child in editorContainer.GetChildren())
{
editorContainer.RemoveChild(child);
child.Free();
}
setEditor(null);
ShowEditor(selectedIndex, null, editorContainer, setEditor);
UpdateFoldableTitles();
_onChanged?.Invoke();
RaiseLayoutSizeChanged();
}
private int GetSelectedIndex(StatescriptResolverResource? existingResolver)
{
return ResolverEditorFactoryCatalog.GetDefaultFactoryIndex(_factories, existingResolver, "Variant");
}
private OptionButton CreateResolverDropdown(StatescriptResolverResource? existingResolver)
{
var dropdown = new OptionButton { SizeFlagsHorizontal = SizeFlags.ExpandFill };
foreach (Func<NodeEditorProperty> factory in _factories)
{
dropdown.AddItem(StatescriptResolverRegistry.GetDisplayName(factory));
}
dropdown.Selected = GetSelectedIndex(existingResolver);
return dropdown;
}
private void ShowEditor(
int factoryIndex,
StatescriptResolverResource? existingResolver,
VBoxContainer container,
Action<NodeEditorProperty?> setEditor)
{
if (_graph is null || factoryIndex < 0 || factoryIndex >= _factories.Count)
{
return;
}
NodeEditorProperty editor = _factories[factoryIndex]();
StatescriptNodeProperty? tempProperty =
existingResolver is null ? null : new StatescriptNodeProperty { Resolver = existingResolver };
editor.Setup(_graph, tempProperty, GetExpectedClrType(_valueType), OnNestedEditorChanged, false);
editor.LayoutSizeChanged += RaiseLayoutSizeChanged;
container.AddChild(editor);
setEditor(editor);
}
private void OnNestedEditorChanged()
{
UpdateFoldableTitles();
_onChanged?.Invoke();
}
private void UpdateFoldableTitles()
{
if (_minFoldable is not null)
{
InlineConstantSummaryFormatter.ApplyFoldableTitle("Min:", _minFoldable, _minEditor);
}
if (_maxFoldable is not null)
{
InlineConstantSummaryFormatter.ApplyFoldableTitle("Max:", _maxFoldable, _maxEditor);
}
UpdateInclusiveMaxFoldableTitle();
}
private void UpdateInclusiveMaxFoldableTitle()
{
if (_inclusiveMaxFoldable is null)
{
return;
}
InlineConstantSummaryFormatter.ApplyFoldableTitle(
"Max:",
_inclusiveMaxFoldable,
_isMaxInclusive ? "Inclusive" : "Exclusive",
InlineSummaryBadgeKind.Enum);
}
private Type GetExpectedClrType(StatescriptVariableType valueType)
{
if (_expectedType == typeof(ForgeVariant128))
{
return GetBroadExpectedClrType(valueType);
}
return _expectedType;
}
}
#endif