update forge
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 27s
Create tag and build when new code gets to main / Export (push) Successful in 7m25s

This commit is contained in:
2026-05-17 00:06:44 +02:00
parent 8b54f00814
commit e09714cf83
473 changed files with 13577 additions and 767 deletions

View File

@@ -0,0 +1,196 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Resources.Statescript;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.NodeEditors;
/// <summary>
/// Custom editor for <c>DebugNode</c>. Lets the user choose the concrete value type to debug, then constrains the
/// source resolver picker to that type so debug output can be interpreted correctly.
/// </summary>
[Tool]
internal sealed partial class DebugNodeEditor : CustomNodeEditor
{
private const string ValueTypeKey = "valueType";
private const string TypeFoldKey = "_debug_type_fold";
private StatescriptVariableType _selectedType = StatescriptVariableType.Int;
private VBoxContainer? _inputRootContainer;
private VBoxContainer? _inputEditorContainer;
private StatescriptNodeDiscovery.NodeTypeInfo? _cachedTypeInfo;
private OptionButton? _typeDropdown;
private FoldableContainer? _typeFoldable;
/// <inheritdoc/>
public override string HandledRuntimeTypeName => "Gamesmiths.Forge.Statescript.Nodes.Action.DebugNode";
/// <inheritdoc/>
public override void BuildPropertySections(StatescriptNodeDiscovery.NodeTypeInfo typeInfo)
{
_cachedTypeInfo = typeInfo;
if (NodeResource.CustomData.TryGetValue(ValueTypeKey, out Variant storedType))
{
_selectedType = (StatescriptVariableType)storedType.AsInt32();
}
bool inputFolded = GetFoldState("_fold_input");
FoldableContainer inputContainer = AddPropertySectionDivider(
"Input Properties",
InputPropertyColor,
"_fold_input",
inputFolded);
_inputRootContainer = new VBoxContainer { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
inputContainer.AddChild(_inputRootContainer);
BuildTypeRow(_inputRootContainer);
if (typeInfo.InputPropertiesInfo.Length > 0)
{
RebuildInputEditor(typeInfo.InputPropertiesInfo[0]);
}
}
private static void AddTypeOption(OptionButton dropdown, StatescriptVariableType type, string label)
{
dropdown.AddItem(label, (int)type);
}
private void BuildTypeRow(VBoxContainer root)
{
_typeFoldable = new FoldableContainer
{
Title = "Type:",
Folded = GetFoldState(TypeFoldKey, true),
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
};
_typeFoldable.FoldingChanged += OnTypeFoldableFoldingChanged;
root.AddChild(_typeFoldable);
var container = new VBoxContainer { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
_typeFoldable.AddChild(container);
var headerRow = new HBoxContainer { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
container.AddChild(headerRow);
_typeDropdown = new OptionButton
{
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
CustomMinimumSize = new Vector2(80, 0),
};
foreach (StatescriptVariableType variableType in StatescriptVariableTypeConverter.GetAllTypes())
{
AddTypeOption(
_typeDropdown,
variableType,
StatescriptVariableTypeConverter.GetDisplayName(variableType));
}
_typeDropdown.Selected = FindSelectedTypeIndex(_typeDropdown);
_typeDropdown.ItemSelected += OnTypeDropdownItemSelected;
headerRow.AddChild(_typeDropdown);
UpdateTypeFoldableTitle();
}
private int FindSelectedTypeIndex(OptionButton dropdown)
{
for (int i = 0; i < dropdown.ItemCount; i++)
{
if (dropdown.GetItemId(i) == (int)_selectedType)
{
return i;
}
}
return 0;
}
private void OnTypeDropdownItemSelected(long index)
{
if (_typeDropdown is null)
{
return;
}
var selectedValue = (StatescriptVariableType)_typeDropdown.GetItemId((int)index);
NodeResource.CustomData[ValueTypeKey] = Variant.From((int)selectedValue);
_selectedType = selectedValue;
UpdateTypeFoldableTitle();
RemoveBinding(StatescriptPropertyDirection.Input, 0);
ActiveResolverEditors.Remove(new PropertySlotKey(StatescriptPropertyDirection.Input, 0));
if (_cachedTypeInfo is not null
&& _inputEditorContainer is not null
&& _cachedTypeInfo.InputPropertiesInfo.Length > 0)
{
RebuildInputEditor(_cachedTypeInfo.InputPropertiesInfo[0]);
}
RaisePropertyBindingChanged();
ResetSize();
}
private void OnTypeFoldableFoldingChanged(bool folded)
{
SetFoldStateWithUndo(TypeFoldKey, folded);
UpdateTypeFoldableTitle();
RefreshInputPropertyFoldableTitles();
ResetSize();
}
private void UpdateTypeFoldableTitle()
{
if (_typeFoldable is null)
{
return;
}
InlineConstantSummaryFormatter.ApplyFoldableTitle(
"Type:",
_typeFoldable,
StatescriptVariableTypeConverter.GetDisplayName(_selectedType),
InlineSummaryBadgeKind.Enum);
}
private void RebuildInputEditor(StatescriptNodeDiscovery.InputPropertyInfo originalInfo)
{
if (_inputRootContainer is null)
{
return;
}
ClearValueRows();
Type clrType = StatescriptVariableTypeConverter.ToSystemType(_selectedType);
_inputEditorContainer = _inputRootContainer;
AddInputPropertyRow(
new StatescriptNodeDiscovery.InputPropertyInfo(originalInfo.Label, clrType),
0,
_inputEditorContainer);
}
private void ClearValueRows()
{
if (_inputRootContainer is null)
{
return;
}
foreach (Node child in _inputRootContainer.GetChildren())
{
if (child == _typeFoldable)
{
continue;
}
_inputRootContainer.RemoveChild(child);
child.Free();
}
}
}
#endif

View File

@@ -0,0 +1 @@
uid://pr5y56nebti

View File

@@ -21,6 +21,8 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
{
private const string FoldInputKey = "_fold_input";
private const string FoldOutputKey = "_fold_output";
private const string ScopeFoldKey = "_fold_output_scope";
private const string TargetFoldKey = "_fold_output_target";
private const string ScopeKey = "_output_scope";
private readonly List<string> _setPaths = [];
@@ -33,6 +35,8 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
private VBoxContainer? _cachedInputEditorContainer;
private VBoxContainer? _cachedTargetContainer;
private int _cachedOutputIndex;
private FoldableContainer? _scopeFoldable;
private FoldableContainer? _targetFoldable;
private bool _isSharedScope;
@@ -50,7 +54,7 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
{
_cachedTypeInfo = typeInfo;
var inputFolded = GetFoldState(FoldInputKey);
bool inputFolded = GetFoldState(FoldInputKey);
FoldableContainer inputContainer = AddPropertySectionDivider(
"Input Properties",
InputPropertyColor,
@@ -66,7 +70,7 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
inputContainer.AddChild(inputEditorContainer);
var outputFolded = GetFoldState(FoldOutputKey);
bool outputFolded = GetFoldState(FoldOutputKey);
FoldableContainer outputContainer = AddPropertySectionDivider(
"Output Variables",
OutputVariableColor,
@@ -122,9 +126,9 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
private static void ScanFilesystemDirectory(EditorFileSystemDirectory dir, List<string> results)
{
for (var i = 0; i < dir.GetFileCount(); i++)
for (int i = 0; i < dir.GetFileCount(); i++)
{
var path = dir.GetFilePath(i);
string path = dir.GetFilePath(i);
if (!path.EndsWith(".tres", StringComparison.InvariantCultureIgnoreCase)
&& !path.EndsWith(".res", StringComparison.InvariantCultureIgnoreCase))
@@ -140,7 +144,7 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
}
}
for (var i = 0; i < dir.GetSubdirCount(); i++)
for (int i = 0; i < dir.GetSubdirCount(); i++)
{
ScanFilesystemDirectory(dir.GetSubdir(i), results);
}
@@ -190,22 +194,22 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
sectionContainer.AddChild(outerVBox);
_scopeFoldable = new FoldableContainer
{
Title = "Scope:",
Folded = GetFoldState(ScopeFoldKey, true),
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
};
_scopeFoldable.FoldingChanged += OnScopeFoldableFoldingChanged;
outerVBox.AddChild(_scopeFoldable);
// Scope toggle row.
var scopeRow = new HBoxContainer
{
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
};
outerVBox.AddChild(scopeRow);
var scopeLabel = new Label
{
Text = "Scope",
CustomMinimumSize = new Vector2(60, 0),
};
scopeLabel.AddThemeColorOverride("font_color", OutputVariableColor);
scopeRow.AddChild(scopeLabel);
_scopeFoldable.AddChild(scopeRow);
var graphButton = new CheckBox
{
@@ -228,6 +232,15 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
scopeRow.AddChild(graphButton);
scopeRow.AddChild(sharedButton);
_targetFoldable = new FoldableContainer
{
Title = $"{varInfo.Label}:",
Folded = GetFoldState(TargetFoldKey, true),
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
};
_targetFoldable.FoldingChanged += OnTargetFoldableFoldingChanged;
outerVBox.AddChild(_targetFoldable);
// Target variable container (rebuilt when scope changes).
var targetContainer = new VBoxContainer
{
@@ -235,9 +248,11 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
};
_cachedTargetContainer = targetContainer;
outerVBox.AddChild(targetContainer);
_targetFoldable.AddChild(targetContainer);
RebuildTargetUI(varInfo, index, targetContainer);
UpdateScopeFoldableTitle();
UpdateTargetFoldableTitle(varInfo.Label);
graphButton.Pressed += () => OnScopeChanged(false, varInfo, index);
sharedButton.Pressed += () => OnScopeChanged(true, varInfo, index);
@@ -259,7 +274,9 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
as StatescriptResolverResource;
_isSharedScope = isShared;
NodeResource.CustomData[ScopeKey] = Variant.From(isShared ? (int)VariableScope.Shared : (int)VariableScope.Graph);
NodeResource.CustomData[ScopeKey] = Variant.From(isShared
? (int)VariableScope.Shared
: (int)VariableScope.Graph);
// Clear the output binding since scope changed.
RemoveBinding(StatescriptPropertyDirection.Output, index);
@@ -310,6 +327,8 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
newInputResolver,
"Change Variable Scope Input");
UpdateScopeFoldableTitle();
UpdateTargetFoldableTitle(varInfo.Label);
RaisePropertyBindingChanged();
ResetSize();
}
@@ -365,12 +384,12 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
}
StatescriptNodeProperty? binding = FindBinding(StatescriptPropertyDirection.Output, index);
var selectedIndex = 0;
int selectedIndex = 0;
if (binding?.Resolver is VariableResolverResource varRes
&& !string.IsNullOrEmpty(varRes.VariableName))
{
for (var i = 0; i < Graph.Variables.Count; i++)
for (int i = 0; i < Graph.Variables.Count; i++)
{
if (Graph.Variables[i].VariableName == varRes.VariableName)
{
@@ -432,6 +451,7 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
});
_sharedVarDropdown = new OptionButton { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
_sharedVarDropdown.SetMeta("is_shared_variable_dropdown", true);
PopulateSharedVariableDropdown();
varRow.AddChild(_sharedVarDropdown);
@@ -452,9 +472,9 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
_setDropdown.AddItem("(None)");
_setPaths.Add(string.Empty);
foreach (var path in FindAllSharedVariableSetPaths())
foreach (string path in FindAllSharedVariableSetPaths())
{
var displayName = path[(path.LastIndexOf('/') + 1)..];
string displayName = path[(path.LastIndexOf('/') + 1)..];
if (displayName.EndsWith(".tres", StringComparison.OrdinalIgnoreCase))
{
@@ -466,7 +486,7 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
}
// Restore selection.
for (var i = 0; i < _setPaths.Count; i++)
for (int i = 0; i < _setPaths.Count; i++)
{
if (_setPaths[i] == _selectedSetPath)
{
@@ -486,6 +506,8 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
return;
}
_sharedVarDropdown.SetMeta("shared_variable_set_path", _selectedSetPath);
_sharedVarDropdown.Clear();
_variableNames.Clear();
@@ -498,7 +520,7 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
if (set is not null)
{
foreach (var variableName in set.Variables.Select(x => x.VariableName))
foreach (string? variableName in set.Variables.Select(x => x.VariableName))
{
if (string.IsNullOrEmpty(variableName))
{
@@ -512,7 +534,7 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
}
// Restore selection.
for (var i = 0; i < _variableNames.Count; i++)
for (int i = 0; i < _variableNames.Count; i++)
{
if (_variableNames[i] == _selectedSharedVarName)
{
@@ -532,7 +554,7 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
return;
}
var idx = _setDropdown.Selected;
int idx = _setDropdown.Selected;
var oldResolver = FindBinding(StatescriptPropertyDirection.Output, _cachedOutputIndex)?.Resolver?.Duplicate()
as StatescriptResolverResource;
@@ -597,7 +619,7 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
return;
}
var idx = _sharedVarDropdown.Selected;
int idx = _sharedVarDropdown.Selected;
var oldResolver = FindBinding(StatescriptPropertyDirection.Output, _cachedOutputIndex)?.Resolver?.Duplicate()
as StatescriptResolverResource;
@@ -605,7 +627,7 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
as StatescriptResolverResource;
StatescriptVariableType? previousType = _resolvedType;
var previousIsArray = _resolvedIsArray;
bool previousIsArray = _resolvedIsArray;
if (idx >= 0 && idx < _variableNames.Count)
{
@@ -666,6 +688,11 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
"Change Shared Target Variable Input");
}
if (_cachedTypeInfo?.OutputVariablesInfo.Length > 0)
{
UpdateTargetFoldableTitle(_cachedTypeInfo.OutputVariablesInfo[0].Label);
}
RaisePropertyBindingChanged();
ResetSize();
}
@@ -724,11 +751,11 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
return;
}
var index = _cachedOutputIndex;
var variableIndex = (int)x - 1;
int index = _cachedOutputIndex;
int variableIndex = (int)x - 1;
StatescriptVariableType? previousType = _resolvedType;
var previousIsArray = _resolvedIsArray;
bool previousIsArray = _resolvedIsArray;
var oldOutputResolver = FindBinding(StatescriptPropertyDirection.Output, index)?.Resolver?.Duplicate()
as StatescriptResolverResource;
@@ -743,7 +770,7 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
}
else
{
var variableName = Graph.Variables[variableIndex].VariableName;
string variableName = Graph.Variables[variableIndex].VariableName;
EnsureBinding(StatescriptPropertyDirection.Output, index).Resolver =
new VariableResolverResource { VariableName = variableName };
@@ -787,10 +814,97 @@ internal sealed partial class SetVariableNodeEditor : CustomNodeEditor
"Change Target Variable Input");
}
if (_cachedTypeInfo?.OutputVariablesInfo.Length > 0)
{
UpdateTargetFoldableTitle(_cachedTypeInfo.OutputVariablesInfo[0].Label);
}
RaisePropertyBindingChanged();
ResetSize();
}
private void OnScopeFoldableFoldingChanged(bool folded)
{
SetFoldStateWithUndo(ScopeFoldKey, folded);
UpdateScopeFoldableTitle();
RaisePropertyBindingChanged();
ResetSize();
}
private void OnTargetFoldableFoldingChanged(bool folded)
{
SetFoldStateWithUndo(TargetFoldKey, folded);
if (_cachedTypeInfo?.OutputVariablesInfo.Length > 0)
{
UpdateTargetFoldableTitle(_cachedTypeInfo.OutputVariablesInfo[0].Label);
}
RaisePropertyBindingChanged();
ResetSize();
}
private void UpdateScopeFoldableTitle()
{
if (_scopeFoldable is null)
{
return;
}
InlineConstantSummaryFormatter.ApplyFoldableTitle(
"Scope:",
_scopeFoldable,
_isSharedScope ? "Shared" : "Graph",
InlineSummaryBadgeKind.Enum);
}
private void UpdateTargetFoldableTitle(string label)
{
if (_targetFoldable is null)
{
return;
}
string summary = _isSharedScope
? _selectedSharedVarName
: GetSelectedGraphVariableName();
InlineSummaryBadgeKind badgeKind = _isSharedScope
? InlineSummaryBadgeKind.SharedVariable
: InlineSummaryBadgeKind.Variable;
string highlightedVariableName = !_isSharedScope
? summary
: string.Empty;
string highlightedSharedVariableSetPath = _isSharedScope ? _selectedSetPath : string.Empty;
string highlightedSharedVariableName = _isSharedScope ? summary : string.Empty;
InlineConstantSummaryFormatter.ApplyFoldableTitle(
$"{label}:",
_targetFoldable,
string.IsNullOrWhiteSpace(summary) ? "(None)" : summary,
badgeKind,
highlightedVariableName: highlightedVariableName,
highlightedSharedVariableSetPath: highlightedSharedVariableSetPath,
highlightedSharedVariableName: highlightedSharedVariableName);
}
private string GetSelectedGraphVariableName()
{
if (_cachedOutputIndex < 0)
{
return string.Empty;
}
if (FindBinding(StatescriptPropertyDirection.Output, _cachedOutputIndex)?.Resolver
is VariableResolverResource varRes)
{
return varRes.VariableName;
}
return string.Empty;
}
private void RebuildInputUI(
StatescriptNodeDiscovery.InputPropertyInfo propInfo,
VBoxContainer container)