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