// Copyright © Gamesmiths Guild. #if TOOLS using System; using System.Collections.Generic; using Gamesmiths.Forge.Godot.Resources.Statescript; using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers; using Gamesmiths.Forge.Statescript.Properties; using Godot; using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128; namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers; /// /// Resolver editor that compares two nested numeric resolvers and produces a boolean result. Supports nesting any /// numeric-compatible resolver as left/right operands, enabling powerful comparisons like "Attribute > Constant". /// [Tool] internal sealed partial class ComparisonResolverEditor : NodeEditorProperty { private StatescriptGraph? _graph; private Action? _onChanged; private OptionButton? _operationDropdown; private VBoxContainer? _leftContainer; private VBoxContainer? _rightContainer; private OptionButton? _leftResolverDropdown; private OptionButton? _rightResolverDropdown; private NodeEditorProperty? _leftEditor; private NodeEditorProperty? _rightEditor; private List> _numericFactories = []; private ComparisonOperation _operation; private VBoxContainer? _leftEditorContainer; private VBoxContainer? _rightEditorContainer; /// public override string DisplayName => "Comparison"; /// public override string ResolverTypeId => "Comparison"; /// public override bool IsCompatibleWith(Type expectedType) { return expectedType == typeof(bool) || expectedType == typeof(ForgeVariant128); } /// public override void Setup( StatescriptGraph graph, StatescriptNodeProperty? property, Type expectedType, Action onChanged, bool isArray) { _graph = graph; _onChanged = onChanged; SizeFlagsHorizontal = SizeFlags.ExpandFill; var vBox = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; AddChild(vBox); _numericFactories = StatescriptResolverRegistry.GetCompatibleFactories(typeof(ForgeVariant128)); _numericFactories.RemoveAll(x => { using NodeEditorProperty temp = x(); return temp.ResolverTypeId == "Comparison"; }); var comparisonResolver = property?.Resolver as ComparisonResolverResource; if (comparisonResolver is not null) { _operation = comparisonResolver.Operation; } var leftFoldable = new FoldableContainer { Title = "Left:" }; leftFoldable.FoldingChanged += OnFoldingChanged; vBox.AddChild(leftFoldable); _leftContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; leftFoldable.AddChild(_leftContainer); _leftEditorContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; _leftResolverDropdown = CreateResolverDropdownControl(comparisonResolver?.Left); _leftContainer.AddChild(_leftResolverDropdown); _leftContainer.AddChild(_leftEditorContainer); ShowNestedEditor( GetSelectedIndex(comparisonResolver?.Left), comparisonResolver?.Left, _leftEditorContainer, x => _leftEditor = x); _leftResolverDropdown.ItemSelected += OnLeftResolverDropdownItemSelected; var opRow = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; vBox.AddChild(opRow); opRow.AddChild(new Label { Text = "Op:" }); _operationDropdown = new OptionButton { SizeFlagsHorizontal = SizeFlags.ExpandFill }; _operationDropdown.AddItem("=="); _operationDropdown.AddItem("!="); _operationDropdown.AddItem("<"); _operationDropdown.AddItem("<="); _operationDropdown.AddItem(">"); _operationDropdown.AddItem(">="); _operationDropdown.Selected = (int)_operation; _operationDropdown.ItemSelected += OnOperationDropdownItemSelected; opRow.AddChild(_operationDropdown); var rightFoldable = new FoldableContainer { Title = "Right:" }; rightFoldable.FoldingChanged += OnFoldingChanged; vBox.AddChild(rightFoldable); _rightContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; rightFoldable.AddChild(_rightContainer); _rightEditorContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; _rightResolverDropdown = CreateResolverDropdownControl(comparisonResolver?.Right); _rightContainer.AddChild(_rightResolverDropdown); _rightContainer.AddChild(_rightEditorContainer); ShowNestedEditor( GetSelectedIndex(comparisonResolver?.Right), comparisonResolver?.Right, _rightEditorContainer, x => _rightEditor = x); _rightResolverDropdown.ItemSelected += OnRightResolverDropdownItemSelected; } /// public override void SaveTo(StatescriptNodeProperty property) { var comparisonResolver = new ComparisonResolverResource { Operation = _operation }; if (_leftEditor is not null) { var leftProperty = new StatescriptNodeProperty(); _leftEditor.SaveTo(leftProperty); comparisonResolver.Left = leftProperty.Resolver; } if (_rightEditor is not null) { var rightProperty = new StatescriptNodeProperty(); _rightEditor.SaveTo(rightProperty); comparisonResolver.Right = rightProperty.Resolver; } property.Resolver = comparisonResolver; } /// public override void ClearCallbacks() { base.ClearCallbacks(); _onChanged = null; _leftEditor?.ClearCallbacks(); _rightEditor?.ClearCallbacks(); } private void OnFoldingChanged(bool isFolded) { RaiseLayoutSizeChanged(); } private void OnOperationDropdownItemSelected(long x) { _operation = (ComparisonOperation)(int)x; _onChanged?.Invoke(); } private void OnLeftResolverDropdownItemSelected(long x) { HandleResolverDropdownChanged((int)x, _leftEditorContainer, editor => _leftEditor = editor); } private void OnRightResolverDropdownItemSelected(long x) { HandleResolverDropdownChanged((int)x, _rightEditorContainer, editor => _rightEditor = editor); } private void HandleResolverDropdownChanged( int selectedIndex, VBoxContainer? editorContainer, Action setEditor) { if (editorContainer is null) { return; } foreach (Node child in editorContainer.GetChildren()) { editorContainer.RemoveChild(child); child.Free(); } setEditor(null); ShowNestedEditor(selectedIndex, null, editorContainer, setEditor); _onChanged?.Invoke(); RaiseLayoutSizeChanged(); } private int GetSelectedIndex(StatescriptResolverResource? existingResolver) { var selectedIndex = 0; if (existingResolver is not null) { var existingTypeId = existingResolver.ResolverTypeId; for (var i = 0; i < _numericFactories.Count; i++) { using NodeEditorProperty temp = _numericFactories[i](); if (temp.ResolverTypeId == existingTypeId) { selectedIndex = i; break; } } } return selectedIndex; } private OptionButton CreateResolverDropdownControl(StatescriptResolverResource? existingResolver) { var dropdown = new OptionButton { SizeFlagsHorizontal = SizeFlags.ExpandFill }; foreach (Func factory in _numericFactories) { using NodeEditorProperty temp = factory(); dropdown.AddItem(temp.DisplayName); } dropdown.Selected = GetSelectedIndex(existingResolver); return dropdown; } private void ShowNestedEditor( int factoryIndex, StatescriptResolverResource? existingResolver, VBoxContainer container, Action setEditor) { if (_graph is null || factoryIndex < 0 || factoryIndex >= _numericFactories.Count) { return; } NodeEditorProperty editor = _numericFactories[factoryIndex](); StatescriptNodeProperty? tempProperty = null; if (existingResolver is not null) { tempProperty = new StatescriptNodeProperty { Resolver = existingResolver }; } editor.Setup(_graph, tempProperty, typeof(ForgeVariant128), OnNestedEditorChanged, false); editor.LayoutSizeChanged += RaiseLayoutSizeChanged; container.AddChild(editor); setEditor(editor); } private void OnNestedEditorChanged() { _onChanged?.Invoke(); } } #endif