// Copyright © Gamesmiths Guild. #if TOOLS using System; using System.Collections.Generic; using Gamesmiths.Forge.Godot.Resources.Statescript; using Godot; namespace Gamesmiths.Forge.Godot.Editor.Statescript; /// /// Base class for custom node property editors. Implementations override the default input-property / output-variable /// sections rendered by for specific node types. Analogous to Godot's /// EditorInspectorPlugin pattern. /// /// /// /// If a is registered for a node's RuntimeTypeName, its /// method is called instead of the default property rendering. The base class /// provides helper methods that mirror the default behavior so that custom editors can reuse them selectively. /// /// /// Because this class extends , signal handlers defined on subclasses can be connected /// directly to Godot signals (e.g. dropdown.ItemSelected += OnItemSelected) without needing wrapper nodes /// or workarounds for serialization. /// /// [Tool] internal abstract partial class CustomNodeEditor : RefCounted { private StatescriptGraphNode? _graphNode; private StatescriptGraph? _graph; private StatescriptNode? _nodeResource; private Dictionary? _activeResolverEditors; /// /// Gets the runtime type name this editor handles (e.g., /// "Gamesmiths.Forge.Statescript.Nodes.Action.SetVariableNode"). /// public abstract string HandledRuntimeTypeName { get; } /// /// Builds the custom input-property and output-variable sections for the node. /// /// Discovered metadata about the node type. public abstract void BuildPropertySections(StatescriptNodeDiscovery.NodeTypeInfo typeInfo); /// /// Gets the input property section color. /// protected static Color InputPropertyColor { get; } = new(0x61afefff); /// /// Gets the output variable section color. /// protected static Color OutputVariableColor { get; } = new(0xe5c07bff); /// /// Gets the active resolver editors dictionary. /// protected Dictionary ActiveResolverEditors => _activeResolverEditors!; /// /// Gets the owning graph resource. /// protected StatescriptGraph Graph => _graph!; /// /// Gets the node resource. /// protected StatescriptNode NodeResource => _nodeResource!; /// /// Gets the undo/redo manager, if available. /// protected EditorUndoRedoManager? UndoRedo => _graphNode?.GetUndoRedo(); /// /// Stores references needed by helper methods. Called once after the instance is created. /// /// The graph node this editor is bound to. /// The graph resource this node belongs to. /// The node resource being edited. /// A dictionary of active resolver editors. internal void Bind( StatescriptGraphNode graphNode, StatescriptGraph graph, StatescriptNode nodeResource, Dictionary activeResolverEditors) { _graphNode = graphNode; _graph = graph; _nodeResource = nodeResource; _activeResolverEditors = activeResolverEditors; } /// /// Clears all references stored by . Called before the owning graph node is freed or serialized /// to prevent accessing disposed objects. /// internal virtual void Unbind() { _graphNode = null; _graph = null; _nodeResource = null; _activeResolverEditors = null; } /// /// Clears all children from a container control. /// /// The container control to clear. protected static void ClearContainer(Control container) { foreach (Node child in container.GetChildren()) { container.RemoveChild(child); child.Free(); } } /// /// Adds a foldable section divider to the graph node. /// /// Title displayed on the divider. /// Color of the divider. /// Key used to persist the fold state. /// Initial fold state. protected FoldableContainer AddPropertySectionDivider( string sectionTitle, Color color, string foldKey, bool folded) { return _graphNode!.AddPropertySectionDividerInternal(sectionTitle, color, foldKey, folded); } /// /// Renders a standard input-property row (resolver dropdown + editor UI). /// /// Metadata about the input property. /// Index of the input property. /// Container to add the input property row to. protected void AddInputPropertyRow( StatescriptNodeDiscovery.InputPropertyInfo propInfo, int index, Control container) { _graphNode!.AddInputPropertyRowInternal(propInfo, index, container); } /// /// Renders a standard output-variable row (variable dropdown). /// /// Metadata about the output variable. /// Index of the output variable. /// Container to add the output variable row to. protected void AddOutputVariableRow( StatescriptNodeDiscovery.OutputVariableInfo varInfo, int index, FoldableContainer container) { _graphNode!.AddOutputVariableRowInternal(varInfo, index, container); } /// /// Gets the persisted fold state for a given key. /// /// The key used to persist the fold state. protected bool GetFoldState(string key) { return _graphNode!.GetFoldStateInternal(key); } /// /// Finds an existing property binding by direction and index. /// /// The direction of the property (input or output). /// The index of the property. protected StatescriptNodeProperty? FindBinding( StatescriptPropertyDirection direction, int propertyIndex) { return _graphNode!.FindBindingInternal(direction, propertyIndex); } /// /// Ensures a property binding exists for the given direction and index, creating one if needed. /// /// The direction of the property (input or output). /// The index of the property. protected StatescriptNodeProperty EnsureBinding( StatescriptPropertyDirection direction, int propertyIndex) { return _graphNode!.EnsureBindingInternal(direction, propertyIndex); } /// /// Removes a property binding by direction and index. /// /// The direction of the property (input or output). /// The index of the property. protected void RemoveBinding( StatescriptPropertyDirection direction, int propertyIndex) { _graphNode!.RemoveBindingInternal(direction, propertyIndex); } /// /// Shows a resolver editor inside the given container. /// /// A factory function to create the resolver editor. /// The existing binding, if any. /// The expected type for the resolver editor. /// The container to add the resolver editor to. /// The direction of the property (input or output). /// The index of the property. /// Whether the input expects an array of values. protected void ShowResolverEditorUI( Func factory, StatescriptNodeProperty? existingBinding, Type expectedType, VBoxContainer container, StatescriptPropertyDirection direction, int propertyIndex, bool isArray = false) { _graphNode!.ShowResolverEditorUIInternal( factory, existingBinding, expectedType, container, direction, propertyIndex, isArray); } /// /// Requests the owning graph node to recalculate its size. /// protected void ResetSize() { _graphNode!.ResetSize(); } /// /// Raises the event. /// protected void RaisePropertyBindingChanged() { _graphNode!.RaisePropertyBindingChangedInternal(); } /// /// Records an undo/redo action for changing a resolver binding, then rebuilds the node. /// /// The direction of the property. /// The index of the property. /// The previous resolver resource. /// The new resolver resource. /// The name for the undo/redo action. protected void RecordResolverBindingChange( StatescriptPropertyDirection direction, int propertyIndex, StatescriptResolverResource? oldResolver, StatescriptResolverResource? newResolver, string actionName = "Change Node Property") { _graphNode!.RecordResolverBindingChangeInternal( direction, propertyIndex, oldResolver, newResolver, actionName); } } #endif