// Copyright © Gamesmiths Guild.
#if TOOLS
using System.Collections.Generic;
using Gamesmiths.Forge.Godot.Resources;
using Gamesmiths.Forge.Godot.Resources.Statescript;
using Godot;
using Godot.Collections;
namespace Gamesmiths.Forge.Godot.Editor.Statescript;
///
/// Custom that renders the array using the
/// same polished value-editor controls as the graph variable panel.
///
[Tool]
internal sealed partial class SharedVariableSetEditorProperty : EditorProperty, ISerializationListener
{
private const string BackgroundPanelNodeName = "BackgroundPanel";
private const string RootNodeName = "Root";
private const string HeaderRowNodeName = "HeaderRow";
private const string AddButtonNodeName = "AddButton";
private const string VariableListNodeName = "VariableList";
private const string VariableNameButtonMetaKey = "_shared_variable_name_button";
private static readonly Color _variableColor = new(0xe5c07bff);
private static readonly Color _highlightColor = new(0x56b6c2ff);
private readonly HashSet _expandedArrays = [];
private EditorUndoRedoManager? _undoRedo;
private VBoxContainer? _root;
private VBoxContainer? _variableList;
private Button? _addButton;
private AcceptDialog? _creationDialog;
private LineEdit? _newNameEdit;
private OptionButton? _newTypeDropdown;
private CheckBox? _newArrayToggle;
private Texture2D? _addIcon;
private Texture2D? _removeIcon;
private string? _selectedVariableName;
///
/// Sets the used for undo/redo support.
///
/// The undo/redo manager from the editor plugin.
public void SetUndoRedo(EditorUndoRedoManager? undoRedo)
{
_undoRedo = undoRedo;
}
public override void _Ready()
{
base._Ready();
SharedVariableHighlightState.Changed += OnSharedVariableHighlightChanged;
_addIcon = EditorInterface.Singleton.GetEditorTheme().GetIcon("Add", "EditorIcons");
_removeIcon = EditorInterface.Singleton.GetEditorTheme().GetIcon("Remove", "EditorIcons");
if (GetEditedObject() is ForgeSharedVariableSet sharedVariableSet)
{
SharedVariableHighlightState.SetInspectorContext(sharedVariableSet.ResourcePath);
SharedVariableHighlightState.SetSelection(sharedVariableSet.ResourcePath, _selectedVariableName);
}
var backgroundPanel = new PanelContainer
{
Name = BackgroundPanelNodeName,
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
var panelStyle = new StyleBoxFlat
{
BgColor = EditorInterface.Singleton.GetEditorTheme().GetColor("base_color", "Editor"),
ContentMarginLeft = 6,
ContentMarginRight = 6,
ContentMarginTop = 4,
ContentMarginBottom = 4,
CornerRadiusTopLeft = 3,
CornerRadiusTopRight = 3,
CornerRadiusBottomLeft = 3,
CornerRadiusBottomRight = 3,
};
backgroundPanel.AddThemeStyleboxOverride("panel", panelStyle);
AddChild(backgroundPanel);
SetBottomEditor(backgroundPanel);
_root = new VBoxContainer
{
Name = RootNodeName,
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
backgroundPanel.AddChild(_root);
var headerHBox = new HBoxContainer { Name = HeaderRowNodeName };
_root.AddChild(headerHBox);
_addButton = new Button
{
Name = AddButtonNodeName,
Text = "Add Variable",
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
_addButton.Pressed += OnAddPressed;
headerHBox.AddChild(_addButton);
_root.AddChild(new HSeparator());
_variableList = new VBoxContainer
{
Name = VariableListNodeName,
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
_root.AddChild(_variableList);
}
public override void _UpdateProperty()
{
EnsureControlsCached();
if (GetEditedObject() is ForgeSharedVariableSet sharedVariableSet)
{
SharedVariableHighlightState.SetInspectorContext(sharedVariableSet.ResourcePath);
}
SyncSelectedVariableFromHighlightState();
RebuildList();
}
public override void _ExitTree()
{
SharedVariableHighlightState.Changed -= OnSharedVariableHighlightChanged;
if (GetEditedObject() is ForgeSharedVariableSet sharedVariableSet)
{
SharedVariableHighlightState.ClearInspectorContext(sharedVariableSet.ResourcePath);
}
ReleaseUiState();
FreeAllChildren();
base._ExitTree();
}
public void OnBeforeSerialize()
{
SharedVariableHighlightState.Changed -= OnSharedVariableHighlightChanged;
ReleaseUiState();
}
public void OnAfterDeserialize()
{
EnsureControlsCached();
SharedVariableHighlightState.Changed += OnSharedVariableHighlightChanged;
SyncSelectedVariableFromHighlightState();
if (_addButton is not null && IsInstanceValid(_addButton))
{
_addButton.Pressed += OnAddPressed;
}
RebuildList();
}
private static int FindTypeDropdownIndex(OptionButton dropdown, StatescriptVariableType variableType)
{
for (int i = 0; i < dropdown.ItemCount; i++)
{
if (dropdown.GetItemId(i) == (int)variableType)
{
return i;
}
}
return 0;
}
private static void UpdateVariableNameButtonAppearance(Button button, bool isSelected)
{
Color buttonColor = isSelected ? _highlightColor : _variableColor;
button.AddThemeColorOverride("font_color", buttonColor);
button.AddThemeColorOverride("font_pressed_color", buttonColor);
button.AddThemeColorOverride("font_hover_color", buttonColor.Lightened(0.2f));
button.AddThemeColorOverride("font_hover_pressed_color", buttonColor.Lightened(0.2f));
}
private Array GetDefinitions()
{
GodotObject obj = GetEditedObject();
string propertyName = GetEditedProperty();
Variant value = obj.Get(propertyName);
return value.AsGodotArray() ?? [];
}
private void NotifyChanged()
{
GodotObject obj = GetEditedObject();
string propertyName = GetEditedProperty();
if (obj is not ForgeSharedVariableSet sharedVariableSet)
{
return;
}
obj.Set(propertyName, sharedVariableSet.Variables);
EmitChanged(propertyName, sharedVariableSet.Variables);
if (obj is Resource resource)
{
resource.EmitChanged();
}
}
private void RebuildList()
{
EnsureControlsCached();
if (_variableList is null)
{
return;
}
SyncSelectedVariableFromHighlightState();
ClearVariableList();
Array definitions = GetDefinitions();
for (int i = 0; i < definitions.Count; i++)
{
AddVariableRow(definitions, i);
}
RefreshVariableSelectionVisuals();
}
private void ClearVariableList()
{
EnsureControlsCached();
if (_variableList is null)
{
return;
}
foreach (Node child in _variableList.GetChildren())
{
_variableList.RemoveChild(child);
child.Free();
}
}
private void EnsureControlsCached()
{
_root ??= GetNodeOrNull(
$"{BackgroundPanelNodeName}/{RootNodeName}");
_addButton ??= GetNodeOrNull