// 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