Compare commits

...

4 Commits

Author SHA1 Message Date
e09714cf83 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
2026-05-17 00:06:44 +02:00
8b54f00814 trying workaround
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 25s
Create tag and build when new code gets to main / Export (push) Successful in 7m0s
2026-05-16 23:52:05 +02:00
415897b7b0 debug
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 24s
Create tag and build when new code gets to main / Export (push) Successful in 8m17s
2026-05-16 21:57:04 +02:00
d302d75238 c for crouching
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 24s
Create tag and build when new code gets to main / Export (push) Successful in 5m33s
Create tag and build when new code gets to main / ReleaseName (push) Successful in 3s
Create tag and build when new code gets to main / Release (push) Successful in 14m28s
2026-05-16 20:58:11 +02:00
477 changed files with 13611 additions and 775 deletions

View File

@@ -172,7 +172,13 @@ jobs:
mkdir -v -p build/windows
${{ steps.setup-godot.outputs.godot_bin }} --headless --verbose --export-release "Windows Desktop" build/windows/${{ env.GAME_NAME }}.exe
ls -la build/windows
zip -r Windows.zip build/windows
- name: Upload build as artifact
uses: actions/upload-artifact@v3-node20
with:
name: Windows build
path: ${{ github.workspace }}/Windows.zip
# - name: Setup Butler
# shell: bash
# env:

BIN
addons/forge-godot-main.zip Normal file

Binary file not shown.

View File

@@ -3,7 +3,6 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Gamesmiths.Forge" Version="0.3.0" />
<PackageReference Include="Gamesmiths.Forge" Version="0.3.3" />
</ItemGroup>
</Project>

View File

@@ -2,7 +2,6 @@
#if TOOLS
using System;
using System.Diagnostics;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Godot.Core;
using Gamesmiths.Forge.Godot.Editor;
@@ -69,31 +68,35 @@ public partial class ForgePluginLoader : EditorPlugin
public override void _ExitTree()
{
Debug.Assert(
_tagsEditorDock is not null,
$"{nameof(_tagsEditorDock)} should have been initialized on _Ready().");
Debug.Assert(
_statescriptGraphEditorDock is not null,
$"{nameof(_statescriptGraphEditorDock)} should have been initialized on _Ready().");
if (_fileSystem?.IsConnected(EditorFileSystem.SignalName.ResourcesReimported, _resourcesReimportedCallable)
== true)
{
_fileSystem.Disconnect(EditorFileSystem.SignalName.ResourcesReimported, _resourcesReimportedCallable);
}
RemoveDock(_tagsEditorDock);
_tagsEditorDock.Free();
if (_tagsEditorDock is not null)
{
RemoveDock(_tagsEditorDock);
_tagsEditorDock.Free();
_tagsEditorDock = null;
}
RemoveInspectorPlugin(_tagContainerInspectorPlugin);
RemoveInspectorPlugin(_tagInspectorPlugin);
RemoveInspectorPlugin(_attributeSetInspectorPlugin);
RemoveInspectorPlugin(_cueHandlerInspectorPlugin);
RemoveInspectorPlugin(_attributeEditorPlugin);
RemoveInspectorPlugin(_sharedVariableSetInspectorPlugin);
RemoveInspectorPluginAndRelease(ref _tagContainerInspectorPlugin);
RemoveInspectorPluginAndRelease(ref _tagInspectorPlugin);
RemoveInspectorPluginAndRelease(ref _attributeSetInspectorPlugin);
RemoveInspectorPluginAndRelease(ref _cueHandlerInspectorPlugin);
RemoveInspectorPluginAndRelease(ref _attributeEditorPlugin);
RemoveInspectorPluginAndRelease(ref _sharedVariableSetInspectorPlugin);
RemoveDock(_statescriptGraphEditorDock);
_statescriptGraphEditorDock.Free();
if (_statescriptGraphEditorDock is not null)
{
RemoveDock(_statescriptGraphEditorDock);
_statescriptGraphEditorDock.Free();
_statescriptGraphEditorDock = null;
}
_fileSystem = null;
_resourcesReimportedCallable = default;
RemoveToolMenuItem("Repair assets tags");
}
@@ -132,7 +135,7 @@ public partial class ForgePluginLoader : EditorPlugin
EnsureForgeDataExists();
var config = ProjectSettings.LoadResourcePack(AutoloadPath);
bool config = ProjectSettings.LoadResourcePack(AutoloadPath);
if (config)
{
@@ -173,7 +176,7 @@ public partial class ForgePluginLoader : EditorPlugin
return;
}
var paths = _statescriptGraphEditorDock.GetOpenResourcePaths();
string[] paths = _statescriptGraphEditorDock.GetOpenResourcePaths();
if (paths.Length == 0)
{
@@ -183,7 +186,7 @@ public partial class ForgePluginLoader : EditorPlugin
configuration.SetValue("Forge", "open_tabs", string.Join(";", paths));
configuration.SetValue("Forge", "active_tab", _statescriptGraphEditorDock.GetActiveTabIndex());
var varStates = _statescriptGraphEditorDock.GetVariablesPanelStates();
bool[] varStates = _statescriptGraphEditorDock.GetVariablesPanelStates();
configuration.SetValue("Forge", "variables_states", string.Join(";", varStates));
}
@@ -197,26 +200,26 @@ public partial class ForgePluginLoader : EditorPlugin
Variant tabsValue = configuration.GetValue("Forge", "open_tabs", string.Empty);
Variant active = configuration.GetValue("Forge", "active_tab", -1);
var tabsString = tabsValue.AsString();
string tabsString = tabsValue.AsString();
if (string.IsNullOrEmpty(tabsString))
{
return;
}
var paths = tabsString.Split(';', StringSplitOptions.RemoveEmptyEntries);
var activeIndex = active.AsInt32();
string[] paths = tabsString.Split(';', StringSplitOptions.RemoveEmptyEntries);
int activeIndex = active.AsInt32();
bool[]? variablesStates = null;
Variant varStatesValue = configuration.GetValue("Forge", "variables_states", string.Empty);
var varString = varStatesValue.AsString();
string varString = varStatesValue.AsString();
if (!string.IsNullOrEmpty(varString))
{
var parts = varString.Split(';');
string[] parts = varString.Split(';');
variablesStates = new bool[parts.Length];
for (var i = 0; i < parts.Length; i++)
for (int i = 0; i < parts.Length; i++)
{
variablesStates[i] = bool.TryParse(parts[i], out var v) && v;
variablesStates[i] = bool.TryParse(parts[i], out bool v) && v;
}
}
@@ -248,16 +251,28 @@ public partial class ForgePluginLoader : EditorPlugin
AssetRepairTool.RepairAllAssetsTags();
}
private void RemoveInspectorPluginAndRelease<TPlugin>(ref TPlugin? plugin)
where TPlugin : EditorInspectorPlugin
{
if (plugin is null)
{
return;
}
RemoveInspectorPlugin(plugin);
plugin = null;
}
private void OnResourcesReimported(string[] resources)
{
foreach (var path in resources)
foreach (string path in resources)
{
if (!ResourceLoader.Exists(path))
{
continue;
}
var fileType = EditorInterface.Singleton.GetResourceFilesystem().GetFileType(path);
string fileType = EditorInterface.Singleton.GetResourceFilesystem().GetFileType(path);
if (fileType != "StatescriptGraph" && fileType != "Resource")
{
continue;

View File

@@ -18,7 +18,7 @@ public class ForgeRandom : IRandom, IDisposable
public void NextBytes(byte[] buffer)
{
for (var i = 0; i < buffer.Length; i++)
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte)_randomNumberGenerator.RandiRange(0, 255);
}
@@ -26,13 +26,25 @@ public class ForgeRandom : IRandom, IDisposable
public void NextBytes(Span<byte> buffer)
{
for (var i = 0; i < buffer.Length; i++)
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte)_randomNumberGenerator.RandiRange(0, 255);
}
}
public double NextDouble()
{
double value;
do
{
value = _randomNumberGenerator.Randf();
}
while (value >= 1.0d);
return value;
}
public double NextDoubleInclusive()
{
return _randomNumberGenerator.Randf();
}
@@ -52,12 +64,17 @@ public class ForgeRandom : IRandom, IDisposable
return _randomNumberGenerator.RandiRange(minValue, maxValue - 1);
}
public int NextIntInclusive(int minValue, int maxValue)
{
return _randomNumberGenerator.RandiRange(minValue, maxValue);
}
public long NextInt64()
{
unchecked
{
var high = _randomNumberGenerator.Randi();
var low = _randomNumberGenerator.Randi();
uint high = _randomNumberGenerator.Randi();
uint low = _randomNumberGenerator.Randi();
return ((long)high << 32) | low;
}
}
@@ -74,13 +91,47 @@ public class ForgeRandom : IRandom, IDisposable
throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be less than maxValue.");
}
var range = (ulong)(maxValue - minValue);
var rand = (ulong)NextInt64();
ulong range = (ulong)(maxValue - minValue);
ulong rand = (ulong)NextInt64();
return (long)(rand % range) + minValue;
}
public long NextInt64Inclusive(long minValue, long maxValue)
{
if (minValue > maxValue)
{
throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be less than or equal to maxValue.");
}
if (minValue == maxValue)
{
return minValue;
}
if (maxValue == long.MaxValue)
{
ulong inclusiveRange = (ulong)(maxValue - minValue) + 1UL;
ulong rand = (ulong)NextInt64();
return (long)(rand % inclusiveRange) + minValue;
}
return NextInt64(minValue, maxValue + 1);
}
public float NextSingle()
{
float value;
do
{
value = _randomNumberGenerator.Randf();
}
while (value >= 1.0f);
return value;
}
public float NextSingleInclusive()
{
return _randomNumberGenerator.Randf();
}

View File

@@ -73,8 +73,8 @@ public static class StatescriptGraphBuilder
continue;
}
var outputPortIndex = connectionResource.OutputPort;
var inputPortIndex = connectionResource.InputPort;
int outputPortIndex = connectionResource.OutputPort;
int inputPortIndex = connectionResource.InputPort;
if (outputPortIndex < 0 || outputPortIndex >= fromNode.OutputPorts.Length)
{
@@ -120,7 +120,7 @@ public static class StatescriptGraphBuilder
if (variable.IsArray)
{
var initialValues = new Variant128[variable.InitialArrayValues.Count];
for (var i = 0; i < variable.InitialArrayValues.Count; i++)
for (int i = 0; i < variable.InitialArrayValues.Count; i++)
{
initialValues[i] = StatescriptVariableTypeConverter.GodotVariantToForge(
variable.InitialArrayValues[i],
@@ -167,7 +167,7 @@ public static class StatescriptGraphBuilder
continue;
}
var index = (byte)binding.PropertyIndex;
byte index = (byte)binding.PropertyIndex;
if (binding.Direction == StatescriptPropertyDirection.Input)
{
@@ -251,11 +251,11 @@ public static class StatescriptGraphBuilder
ConstructorInfo constructor = constructors.OrderByDescending(x => x.GetParameters().Length).First();
ParameterInfo[] parameters = constructor.GetParameters();
var args = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
object[] args = new object[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
ParameterInfo param = parameters[i];
var paramName = param.Name ?? string.Empty;
string paramName = param.Name ?? string.Empty;
if (nodeResource.CustomData.TryGetValue(paramName, out GodotVariant value))
{
@@ -292,6 +292,20 @@ public static class StatescriptGraphBuilder
private static object ConvertParameter(GodotVariant value, Type targetType)
{
if (targetType.IsEnum)
{
if (value.VariantType == GodotVariant.Type.Int || value.VariantType == GodotVariant.Type.Float)
{
return Enum.ToObject(targetType, value.AsInt32());
}
string enumText = value.AsString();
if (!string.IsNullOrEmpty(enumText))
{
return Enum.Parse(targetType, enumText, ignoreCase: true);
}
}
if (targetType == typeof(StringKey))
{
return new StringKey(value.AsString());

View File

@@ -0,0 +1,83 @@
// Copyright © Gamesmiths Guild.
using System;
using System.Collections.Generic;
using Gamesmiths.Forge.Godot.Resources.Statescript;
using Godot;
namespace Gamesmiths.Forge.Statescript.Nodes.Action;
/// <summary>
/// Action node that resolves an input value of any supported type and prints it through
/// <see cref="GD.Print(params Variant[])"/>.
/// Useful for validating resolver chains while testing Statescript graphs in the editor.
/// </summary>
public sealed class DebugNode : ActionNode
{
private readonly StatescriptVariableType _valueType;
/// <inheritdoc/>
public override string Description => "Prints the resolved input value to the Godot console for debugging.";
public DebugNode(StatescriptVariableType valueType = StatescriptVariableType.Int)
{
_valueType = valueType;
}
/// <inheritdoc/>
protected override void DefineParameters(
List<InputProperty> inputProperties,
List<OutputVariable> outputVariables)
{
inputProperties.Add(new InputProperty("Value", StatescriptVariableTypeConverter.ToSystemType(_valueType)));
}
/// <inheritdoc/>
protected override void Execute(GraphContext graphContext)
{
if (!graphContext.TryResolveVariant(InputProperties[0].BoundName, out Variant128 value))
{
GD.Print("[Statescript Debug] <unresolved>");
return;
}
GD.Print("[Statescript Debug] ", FormatValue(value));
}
private string FormatValue(Variant128 value)
{
return _valueType switch
{
StatescriptVariableType.Bool => value.AsBool().ToString(),
StatescriptVariableType.Byte => value.AsByte().ToString(
System.Globalization.CultureInfo.InvariantCulture),
StatescriptVariableType.SByte => value.AsSByte().ToString(
System.Globalization.CultureInfo.InvariantCulture),
StatescriptVariableType.Char => value.AsChar().ToString(),
StatescriptVariableType.Decimal => value.AsDecimal().ToString(
System.Globalization.CultureInfo.InvariantCulture),
StatescriptVariableType.Double => value.AsDouble().ToString(
System.Globalization.CultureInfo.InvariantCulture),
StatescriptVariableType.Float => value.AsFloat().ToString(
System.Globalization.CultureInfo.InvariantCulture),
StatescriptVariableType.Int => value.AsInt().ToString(
System.Globalization.CultureInfo.InvariantCulture),
StatescriptVariableType.UInt => value.AsUInt().ToString(
System.Globalization.CultureInfo.InvariantCulture),
StatescriptVariableType.Long => value.AsLong().ToString(
System.Globalization.CultureInfo.InvariantCulture),
StatescriptVariableType.ULong => value.AsULong().ToString(
System.Globalization.CultureInfo.InvariantCulture),
StatescriptVariableType.Short => value.AsShort().ToString(
System.Globalization.CultureInfo.InvariantCulture),
StatescriptVariableType.UShort => value.AsUShort().ToString(
System.Globalization.CultureInfo.InvariantCulture),
StatescriptVariableType.Vector2 => value.AsVector2().ToString(),
StatescriptVariableType.Vector3 => value.AsVector3().ToString(),
StatescriptVariableType.Vector4 => value.AsVector4().ToString(),
StatescriptVariableType.Plane => value.AsPlane().ToString(),
StatescriptVariableType.Quaternion => value.AsQuaternion().ToString(),
_ => Convert.ToHexString(value.ToBytes()),
};
}
}

View File

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

View File

@@ -23,12 +23,12 @@ public partial class AssetRepairTool : EditorPlugin
List<string> scenes = GetScenePaths("res://");
GD.Print($"Found {scenes.Count} scene(s) to process.");
var openedScenes = EditorInterface.Singleton.GetOpenScenes();
string[] openedScenes = EditorInterface.Singleton.GetOpenScenes();
foreach (var originalScenePath in scenes)
foreach (string originalScenePath in scenes)
{
// For some weird reason scenes from the GetScenePath are coming with 3 slashes instead of just two.
var scenePath = originalScenePath.Replace("res:///", "res://");
string scenePath = originalScenePath.Replace("res:///", "res://");
GD.Print($"Processing scene: {scenePath}.");
PackedScene? packedScene = ResourceLoader.Load<PackedScene>(scenePath);
@@ -40,7 +40,7 @@ public partial class AssetRepairTool : EditorPlugin
}
Node sceneInstance = packedScene.Instantiate();
var modified = ProcessNode(sceneInstance, tagsManager);
bool modified = ProcessNode(sceneInstance, tagsManager);
if (!modified)
{
@@ -92,13 +92,13 @@ public partial class AssetRepairTool : EditorPlugin
dir.ListDirBegin();
while (true)
{
var fileName = dir.GetNext();
string fileName = dir.GetNext();
if (string.IsNullOrEmpty(fileName))
{
break;
}
var filePath = $"{basePath}/{fileName}";
string filePath = $"{basePath}/{fileName}";
if (dir.CurrentIsDir())
{
// Recursively scan subdirectories.
@@ -123,7 +123,7 @@ public partial class AssetRepairTool : EditorPlugin
/// <returns><see langword="true"/> if any ForgeEntity was modified.</returns>
private static bool ProcessNode(Node node, TagsManager tagsManager)
{
var modified = ValidateNode(node, tagsManager);
bool modified = ValidateNode(node, tagsManager);
foreach (Node child in node.GetChildren())
{
@@ -135,7 +135,7 @@ public partial class AssetRepairTool : EditorPlugin
private static bool ValidateNode(Node node, TagsManager tagsManager)
{
var modified = false;
bool modified = false;
foreach (Dictionary propertyInfo in node.GetPropertyList())
{
if (!propertyInfo.TryGetValue("class_name", out Variant className))
@@ -153,7 +153,7 @@ public partial class AssetRepairTool : EditorPlugin
continue;
}
var propertyName = nameObj.AsString();
string propertyName = nameObj.AsString();
Variant value = node.Get(propertyName);
if (value.VariantType != Variant.Type.Object)
@@ -182,9 +182,9 @@ public partial class AssetRepairTool : EditorPlugin
Array<string> originalTags = container.ContainerTags;
var newTags = new Array<string>();
var modified = false;
bool modified = false;
foreach (var tag in originalTags)
foreach (string tag in originalTags)
{
try
{

View File

@@ -11,7 +11,10 @@ public partial class AttributeEditorProperty : EditorProperty, ISerializationLis
private const int ButtonSize = 26;
private const int PopupSize = 300;
private Label _label = null!;
private Label? _label;
private Button? _button;
private Popup? _popup;
private Tree? _tree;
public override void _Ready()
{
@@ -19,20 +22,20 @@ public partial class AttributeEditorProperty : EditorProperty, ISerializationLis
var hBox = new HBoxContainer();
_label = new Label { Text = "None", SizeFlagsHorizontal = SizeFlags.ExpandFill };
var button = new Button { Icon = dropdownIcon, CustomMinimumSize = new Vector2(ButtonSize, 0) };
_button = new Button { Icon = dropdownIcon, CustomMinimumSize = new Vector2(ButtonSize, 0) };
hBox.AddChild(_label);
hBox.AddChild(button);
hBox.AddChild(_button);
AddChild(hBox);
var popup = new Popup { Size = new Vector2I(PopupSize, PopupSize) };
var tree = new Tree
_popup = new Popup { Size = new Vector2I(PopupSize, PopupSize) };
_tree = new Tree
{
HideRoot = true,
AnchorRight = 1,
AnchorBottom = 1,
};
popup.AddChild(tree);
_popup.AddChild(_tree);
var bg = new StyleBoxFlat
{
@@ -40,74 +43,125 @@ public partial class AttributeEditorProperty : EditorProperty, ISerializationLis
.GetEditorTheme()
.GetColor("dark_color_2", "Editor"),
};
tree.AddThemeStyleboxOverride("panel", bg);
_tree.AddThemeStyleboxOverride("panel", bg);
AddChild(popup);
AddChild(_popup);
BuildAttributeTree(tree);
BuildAttributeTree(_tree);
button.Pressed += () =>
{
Window win = GetWindow();
popup.Position = (Vector2I)button.GlobalPosition
+ win.Position
- new Vector2I(PopupSize - ButtonSize, -30);
popup.Popup();
};
tree.ItemActivated += () =>
{
TreeItem item = tree.GetSelected();
if (item?.HasMeta("attribute_path") != true)
{
return;
}
var fullPath = item.GetMeta("attribute_path").AsString();
_label.Text = fullPath;
EmitChanged(GetEditedProperty(), fullPath);
popup.Hide();
};
_button.Pressed += OnButtonPressed;
_tree.ItemActivated += OnTreeItemActivated;
}
public override void _UpdateProperty()
{
var value = GetEditedObject().Get(GetEditedProperty()).AsString();
if (_label is null || !IsInstanceValid(_label))
{
return;
}
string value = GetEditedObject().Get(GetEditedProperty()).AsString();
_label.Text = string.IsNullOrEmpty(value) ? "None" : value;
}
public override void _ExitTree()
{
ReleaseUiState();
FreeAllChildren();
base._ExitTree();
}
public void OnBeforeSerialize()
{
for (var i = GetChildCount() - 1; i >= 0; i--)
{
Node child = GetChild(i);
RemoveChild(child);
child.Free();
}
ReleaseUiState();
FreeAllChildren();
}
public void OnAfterDeserialize()
{
// This method was intentionally left blank.
}
private static void BuildAttributeTree(Tree tree)
{
TreeItem root = tree.CreateItem();
foreach (var attributeSet in EditorUtils.GetAttributeSetOptions())
foreach (string attributeSet in EditorUtils.GetAttributeSetOptions())
{
TreeItem setItem = tree.CreateItem(root);
setItem.SetText(0, attributeSet);
setItem.Collapsed = true;
foreach (var attribute in EditorUtils.GetAttributeOptions(attributeSet))
foreach (string attribute in EditorUtils.GetAttributeOptions(attributeSet))
{
TreeItem attributeItem = tree.CreateItem(setItem);
var attributePath = $"{attributeSet}.{attribute}";
string attributePath = $"{attributeSet}.{attribute}";
attributeItem.SetText(0, attribute);
attributeItem.SetMeta("attribute_path", attributePath);
}
}
}
private void OnButtonPressed()
{
if (_button is null || _popup is null || !IsInstanceValid(_button) || !IsInstanceValid(_popup))
{
return;
}
Window win = GetWindow();
_popup.Position = (Vector2I)_button.GlobalPosition
+ win.Position
- new Vector2I(PopupSize - ButtonSize, -30);
_popup.Popup();
}
private void OnTreeItemActivated()
{
if (_tree is null || _popup is null || _label is null
|| !IsInstanceValid(_tree) || !IsInstanceValid(_popup) || !IsInstanceValid(_label))
{
return;
}
TreeItem item = _tree.GetSelected();
if (item?.HasMeta("attribute_path") != true)
{
return;
}
string fullPath = item.GetMeta("attribute_path").AsString();
_label.Text = fullPath;
EmitChanged(GetEditedProperty(), fullPath);
_popup.Hide();
}
private void ReleaseUiState()
{
if (_button is not null && IsInstanceValid(_button))
{
_button.Pressed -= OnButtonPressed;
}
if (_tree is not null && IsInstanceValid(_tree))
{
_tree.ItemActivated -= OnTreeItemActivated;
}
_label = null;
_button = null;
_popup = null;
_tree = null;
}
private void FreeAllChildren()
{
for (int i = GetChildCount() - 1; i >= 0; i--)
{
Node child = GetChild(i);
RemoveChild(child);
child.Free();
}
}
}
#endif

View File

@@ -14,7 +14,7 @@ namespace Gamesmiths.Forge.Godot.Editor.Attributes;
[Tool]
public partial class AttributeSetClassEditorProperty : EditorProperty, ISerializationListener
{
private OptionButton _optionButton = null!;
private OptionButton? _optionButton;
public override void _Ready()
{
@@ -22,60 +22,25 @@ public partial class AttributeSetClassEditorProperty : EditorProperty, ISerializ
AddChild(_optionButton);
_optionButton.AddItem("Select AttributeSet Class");
foreach (var option in EditorUtils.GetAttributeSetOptions())
foreach (string option in EditorUtils.GetAttributeSetOptions())
{
_optionButton.AddItem(option);
}
_optionButton.ItemSelected += x =>
{
var className = _optionButton.GetItemText((int)x);
EmitChanged(GetEditedProperty(), className);
GodotObject @object = GetEditedObject();
if (@object is not null)
{
var dictionary = new Dictionary<string, AttributeValues>();
var assembly = Assembly.GetAssembly(typeof(ForgeAttributeSet));
Type? targetType = System.Array.Find(assembly?.GetTypes() ?? [], x => x.Name == className);
if (targetType is not null)
{
System.Collections.Generic.IEnumerable<PropertyInfo> attributeProperties = targetType
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(EntityAttribute));
foreach (var propertyName in attributeProperties.Select(x => x.Name))
{
if (@object is not ForgeAttributeSet forgeAttributeSet)
{
dictionary[propertyName] = new AttributeValues(0, 0, int.MaxValue);
continue;
}
AttributeSet? attributeSet = forgeAttributeSet.GetAttributeSet();
if (attributeSet is null)
{
dictionary[propertyName] = new AttributeValues(0, 0, int.MaxValue);
continue;
}
EntityAttribute key = attributeSet.AttributesMap[className + "." + propertyName];
dictionary[propertyName] = new AttributeValues(key.CurrentValue, key.Min, key.Max);
}
}
EmitChanged("InitialAttributeValues", dictionary);
}
};
_optionButton.ItemSelected += OnItemSelected;
}
public override void _UpdateProperty()
{
if (_optionButton is null || !IsInstanceValid(_optionButton))
{
return;
}
GodotObject obj = GetEditedObject();
StringName property = GetEditedProperty();
var val = obj.Get(property).AsString();
for (var i = 0; i < _optionButton.GetItemCount(); i++)
string val = obj.Get(property).AsString();
for (int i = 0; i < _optionButton.GetItemCount(); i++)
{
if (_optionButton.GetItemText(i) == val)
{
@@ -85,18 +50,90 @@ public partial class AttributeSetClassEditorProperty : EditorProperty, ISerializ
}
}
public override void _ExitTree()
{
ReleaseUiState();
FreeAllChildren();
base._ExitTree();
}
public void OnBeforeSerialize()
{
for (var i = GetChildCount() - 1; i >= 0; i--)
ReleaseUiState();
FreeAllChildren();
}
public void OnAfterDeserialize()
{
}
private void OnItemSelected(long index)
{
if (_optionButton is null || !IsInstanceValid(_optionButton))
{
return;
}
string className = _optionButton.GetItemText((int)index);
EmitChanged(GetEditedProperty(), className);
GodotObject @object = GetEditedObject();
if (@object is null)
{
return;
}
var dictionary = new Dictionary<string, AttributeValues>();
var assembly = Assembly.GetAssembly(typeof(ForgeAttributeSet));
Type? targetType = System.Array.Find(assembly?.GetTypes() ?? [], x => x.Name == className);
if (targetType is not null)
{
System.Collections.Generic.IEnumerable<PropertyInfo> attributeProperties = targetType
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(EntityAttribute));
foreach (string? propertyName in attributeProperties.Select(x => x.Name))
{
if (@object is not ForgeAttributeSet forgeAttributeSet)
{
dictionary[propertyName] = new AttributeValues(0, 0, int.MaxValue);
continue;
}
AttributeSet? attributeSet = forgeAttributeSet.GetAttributeSet();
if (attributeSet is null)
{
dictionary[propertyName] = new AttributeValues(0, 0, int.MaxValue);
continue;
}
EntityAttribute key = attributeSet.AttributesMap[className + "." + propertyName];
dictionary[propertyName] = new AttributeValues(key.CurrentValue, key.Min, key.Max);
}
}
EmitChanged("InitialAttributeValues", dictionary);
}
private void ReleaseUiState()
{
if (_optionButton is not null && IsInstanceValid(_optionButton))
{
_optionButton.ItemSelected -= OnItemSelected;
}
_optionButton = null;
}
private void FreeAllChildren()
{
for (int i = GetChildCount() - 1; i >= 0; i--)
{
Node child = GetChild(i);
RemoveChild(child);
child.Free();
}
}
public void OnAfterDeserialize()
{
}
}
#endif

View File

@@ -40,7 +40,7 @@ public partial class AttributeSetValuesEditorProperty : EditorProperty, ISeriali
return;
}
var className = obj.AttributeSetClass;
string className = obj.AttributeSetClass;
var assembly = Assembly.GetAssembly(typeof(ForgeAttributeSet));
Type? targetType = System.Array.Find(assembly?.GetTypes() ?? [], x => x.Name == className);
@@ -53,7 +53,7 @@ public partial class AttributeSetValuesEditorProperty : EditorProperty, ISeriali
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(EntityAttribute));
foreach (var attributeName in attributeProperties.Select(x => x.Name))
foreach (string? attributeName in attributeProperties.Select(x => x.Name))
{
var groupVBox = new VBoxContainer();
@@ -99,7 +99,7 @@ public partial class AttributeSetValuesEditorProperty : EditorProperty, ISeriali
VBoxContainer? attributesRoot = GetNodeOrNull<VBoxContainer>("AttributesRoot");
if (attributesRoot is not null)
{
for (var i = attributesRoot.GetChildCount() - 1; i >= 0; i--)
for (int i = attributesRoot.GetChildCount() - 1; i >= 0; i--)
{
Node child = attributesRoot.GetChild(i);
attributesRoot.RemoveChild(child);
@@ -169,7 +169,7 @@ public partial class AttributeSetValuesEditorProperty : EditorProperty, ISeriali
private static void FreeAllChildren(Node node)
{
for (var i = node.GetChildCount() - 1; i >= 0; i--)
for (int i = node.GetChildCount() - 1; i >= 0; i--)
{
node.GetChild(i).QueueFree();
}

View File

@@ -13,28 +13,22 @@ public partial class CueHandlerInspectorPlugin : EditorInspectorPlugin
public override bool _CanHandle(GodotObject @object)
{
// Find out if its an implementation of CueHandler without having to add [Tool] attribute to them.
try
if (@object?.GetScript().As<CSharpScript>() is CSharpScript script)
{
if (@object?.GetScript().As<CSharpScript>() is null)
return false;
}
catch (Exception e)
{
return false;
StringName className = script.GetGlobalName();
Type baseType = typeof(ForgeCueHandler);
System.Reflection.Assembly assembly = baseType.Assembly;
Type? implementationType =
Array.Find(assembly.GetTypes(), x =>
x.Name == className &&
baseType.IsAssignableFrom(x));
return implementationType is not null;
}
var script = @object?.GetScript().As<CSharpScript>();
StringName className = script.GetGlobalName();
Type baseType = typeof(ForgeCueHandler);
System.Reflection.Assembly assembly = baseType.Assembly;
Type? implementationType =
Array.Find(assembly.GetTypes(), x =>
x.Name == className &&
baseType.IsAssignableFrom(x));
return implementationType is not null;
return false;
}
public override bool _ParseProperty(

View File

@@ -9,12 +9,15 @@ using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Cues;
[Tool]
public partial class CueKeyEditorProperty : EditorProperty
public partial class CueKeyEditorProperty : EditorProperty, ISerializationListener
{
private const int ButtonSize = 26;
private const int PopupSize = 300;
private Label _label = null!;
private Label? _label;
private Button? _button;
private Popup? _popup;
private Tree? _tree;
public override void _Ready()
{
@@ -24,72 +27,63 @@ public partial class CueKeyEditorProperty : EditorProperty
var hbox = new HBoxContainer();
_label = new Label { Text = "None", SizeFlagsHorizontal = SizeFlags.ExpandFill };
var button = new Button { Icon = dropdownIcon, CustomMinimumSize = new Vector2(ButtonSize, 0) };
_button = new Button { Icon = dropdownIcon, CustomMinimumSize = new Vector2(ButtonSize, 0) };
hbox.AddChild(_label);
hbox.AddChild(button);
hbox.AddChild(_button);
AddChild(hbox);
var popup = new Popup { Size = new Vector2I(PopupSize, PopupSize) };
var tree = new Tree
_popup = new Popup { Size = new Vector2I(PopupSize, PopupSize) };
_tree = new Tree
{
HideRoot = true,
AnchorRight = 1,
AnchorBottom = 1,
};
popup.AddChild(tree);
_popup.AddChild(_tree);
var backgroundStyle = new StyleBoxFlat
{
BgColor = EditorInterface.Singleton.GetEditorTheme().GetColor("base_color", "Editor"),
};
tree.AddThemeStyleboxOverride("panel", backgroundStyle);
_tree.AddThemeStyleboxOverride("panel", backgroundStyle);
AddChild(popup);
AddChild(_popup);
ForgeData pluginData = ResourceLoader.Load<ForgeData>(ForgeData.ForgeDataResourcePath);
var tagsManager = new TagsManager([.. pluginData.RegisteredTags]);
TreeItem root = tree.CreateItem();
BuildTreeRecursively(tree, root, tagsManager.RootNode);
TreeItem root = _tree.CreateItem();
BuildTreeRecursively(_tree, root, tagsManager.RootNode);
button.Pressed += () =>
{
Window win = GetWindow();
popup.Position = (Vector2I)button.GlobalPosition
+ win.Position
- new Vector2I(PopupSize - ButtonSize, -30);
popup.Popup();
};
tree.ItemActivated += () =>
{
TreeItem item = tree.GetSelected();
if (item is null)
{
return;
}
// Build full path from root.
var segments = new List<string>();
TreeItem current = item;
while (current.GetParent() is not null)
{
segments.Insert(0, current.GetText(0));
current = current.GetParent();
}
var fullPath = string.Join(".", segments);
_label.Text = fullPath;
EmitChanged(GetEditedProperty(), fullPath);
popup.Hide();
};
_button.Pressed += OnButtonPressed;
_tree.ItemActivated += OnTreeItemActivated;
}
public override void _UpdateProperty()
{
var property = GetEditedObject().Get(GetEditedProperty()).AsString();
_label.Text = string.IsNullOrEmpty(property) ? "None" : property;
string property = GetEditedObject().Get(GetEditedProperty()).AsString();
if (_label is not null && IsInstanceValid(_label))
{
_label.Text = string.IsNullOrEmpty(property) ? "None" : property;
}
}
public override void _ExitTree()
{
ReleaseUiState();
FreeAllChildren();
base._ExitTree();
}
public void OnBeforeSerialize()
{
ReleaseUiState();
FreeAllChildren();
}
public void OnAfterDeserialize()
{
}
private static void BuildTreeRecursively(Tree tree, TreeItem currentTreeItem, TagNode currentNode)
@@ -102,5 +96,76 @@ public partial class CueKeyEditorProperty : EditorProperty
BuildTreeRecursively(tree, childTreeNode, childTagNode);
}
}
private void OnButtonPressed()
{
if (_button is null || _popup is null || !IsInstanceValid(_button) || !IsInstanceValid(_popup))
{
return;
}
Window win = GetWindow();
_popup.Position = (Vector2I)_button.GlobalPosition
+ win.Position
- new Vector2I(PopupSize - ButtonSize, -30);
_popup.Popup();
}
private void OnTreeItemActivated()
{
if (_tree is null || _popup is null || _label is null
|| !IsInstanceValid(_tree) || !IsInstanceValid(_popup) || !IsInstanceValid(_label))
{
return;
}
TreeItem item = _tree.GetSelected();
if (item is null)
{
return;
}
var segments = new List<string>();
TreeItem current = item;
while (current.GetParent() is not null)
{
segments.Insert(0, current.GetText(0));
current = current.GetParent();
}
string fullPath = string.Join(".", segments);
_label.Text = fullPath;
EmitChanged(GetEditedProperty(), fullPath);
_popup.Hide();
}
private void ReleaseUiState()
{
if (_button is not null && IsInstanceValid(_button))
{
_button.Pressed -= OnButtonPressed;
}
if (_tree is not null && IsInstanceValid(_tree))
{
_tree.ItemActivated -= OnTreeItemActivated;
}
_label = null;
_button = null;
_popup = null;
_tree = null;
}
private void FreeAllChildren()
{
for (int i = GetChildCount() - 1; i >= 0; i--)
{
Node child = GetChild(i);
RemoveChild(child);
child.Free();
}
}
}
#endif

View File

@@ -172,6 +172,26 @@ internal abstract partial class CustomNodeEditor : RefCounted
return _graphNode!.GetFoldStateInternal(key);
}
/// <summary>
/// Gets the persisted fold state for a given key, with a custom default when unset.
/// </summary>
/// <param name="key">The key used to persist the fold state.</param>
/// <param name="defaultValue">The default fold state when no persisted value exists.</param>
protected bool GetFoldState(string key, bool defaultValue)
{
return _graphNode!.GetFoldStateInternal(key, defaultValue);
}
/// <summary>
/// Persists a fold state change with undo support.
/// </summary>
/// <param name="key">The key used to persist the fold state.</param>
/// <param name="folded">The new folded state.</param>
protected void SetFoldStateWithUndo(string key, bool folded)
{
_graphNode!.SetFoldStateWithUndoInternal(key, folded);
}
/// <summary>
/// Finds an existing property binding by direction and index.
/// </summary>
@@ -245,6 +265,14 @@ internal abstract partial class CustomNodeEditor : RefCounted
_graphNode!.ResetSize();
}
/// <summary>
/// Refreshes standard input-property foldable summaries.
/// </summary>
protected void RefreshInputPropertyFoldableTitles()
{
_graphNode!.UpdateInputPropertyFoldableTitlesInternal();
}
/// <summary>
/// Raises the <see cref="StatescriptGraphNode.PropertyBindingChanged"/> event.
/// </summary>

View File

@@ -0,0 +1,656 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Gamesmiths.Forge.Godot.Resources.Statescript;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript;
internal static class InlineConstantSummaryFormatter
{
private const string SummaryBadgeMetaKey = "forge_inline_summary_badge";
private const string SummaryBadgeInstanceIdMetaKey = "forge_inline_summary_badge_instance_id";
private const string SummaryBadgeResizeHookMetaKey = "forge_inline_summary_badge_resize_hook";
private const string SummaryBadgeKindMetaKey = "forge_inline_summary_badge_kind";
private const string SummaryBadgeTextMetaKey = "forge_inline_summary_badge_text";
private const string SummaryBadgeSelectedVariableMetaKey = "forge_inline_summary_badge_selected_variable";
private const string SummaryBadgeSelectedSharedVariableSetPathMetaKey =
"forge_inline_summary_badge_selected_shared_set_path";
private const string SummaryBadgeSelectedSharedVariableMetaKey =
"forge_inline_summary_badge_selected_shared_variable";
private const float MinimumBadgeWidth = 76f;
private const float FoldableTitleChromeWidth = 30f;
private const float FoldableTitleBadgeGap = 6f;
private const float FoldableTitleRightPadding = 8f;
private static readonly Color _numericIconColor = new(0x3dbcc9ff);
private static readonly Color _numericBackgroundColor = new(0x3dbcc918);
private static readonly Color _numericBorderColor = new(0x3dbcc9ff);
private static readonly Color _vectorIconColor = new(0xd48a3aff);
private static readonly Color _vectorBackgroundColor = new(0xd48a3a18);
private static readonly Color _vectorBorderColor = new(0xd48a3aff);
private static readonly Color _booleanIconColor = new(0xc2a24fff);
private static readonly Color _booleanBackgroundColor = new(0xc2a24f18);
private static readonly Color _booleanBorderColor = new(0xc2a24fff);
private static readonly Color _resolverIconColor = new(0xc06bcfff);
private static readonly Color _resolverBackgroundColor = new(0xc06bcf18);
private static readonly Color _resolverBorderColor = new(0xc06bcfff);
private static readonly Color _variableIconColor = new(0x5d7be0ff);
private static readonly Color _variableBackgroundColor = new(0x5d7be018);
private static readonly Color _variableBorderColor = new(0x5d7be0ff);
private static readonly Color _sharedVariableIconColor = new(0x46a86fff);
private static readonly Color _sharedVariableBackgroundColor = new(0x46a86f18);
private static readonly Color _sharedVariableBorderColor = new(0x46a86fff);
private static readonly Color _enumIconColor = new(0xc0c6d1ff);
private static readonly Color _enumBackgroundColor = new(0xc0c6d118);
private static readonly Color _enumBorderColor = new(0xc0c6d1ff);
public static void ApplyFoldableTitle(
string baseTitle,
FoldableContainer foldable,
NodeEditorProperty? editor)
{
EnsureResizeSyncHook(foldable);
foldable.Title = baseTitle;
SummaryBadgeData badgeData = GetBadgeData(foldable, editor);
PanelContainer badge = GetOrCreateSummaryBadge(foldable);
ConfigureSummaryBadge(badge, badgeData);
SynchronizeSiblingBadgeWidths(foldable);
}
public static void ApplyFoldableTitle(
string baseTitle,
FoldableContainer foldable,
string? summary,
InlineSummaryBadgeKind badgeKind,
bool isConstant = false,
string? highlightedVariableName = null,
string? highlightedSharedVariableSetPath = null,
string? highlightedSharedVariableName = null)
{
EnsureResizeSyncHook(foldable);
foldable.Title = baseTitle;
SummaryBadgeData badgeData = foldable.Folded && !string.IsNullOrWhiteSpace(summary)
? CreateBadgeData(
summary,
badgeKind,
isConstant,
highlightedVariableName,
highlightedSharedVariableSetPath,
highlightedSharedVariableName)
: SummaryBadgeData.Hidden;
PanelContainer badge = GetOrCreateSummaryBadge(foldable);
ConfigureSummaryBadge(badge, badgeData);
SynchronizeSiblingBadgeWidths(foldable);
}
public static string GetFoldableTitle(
string baseTitle,
FoldableContainer foldable,
NodeEditorProperty? editor)
{
if (!foldable.Folded || editor is null)
{
return baseTitle;
}
if (editor.TryGetInlineSummary(out string summary) && !string.IsNullOrWhiteSpace(summary))
{
return $"{baseTitle} {summary}";
}
return string.IsNullOrWhiteSpace(editor.DisplayName)
? baseTitle
: $"{baseTitle} {editor.DisplayName}";
}
public static string FormatVariant(Variant value, StatescriptVariableType valueType)
{
return valueType switch
{
StatescriptVariableType.Bool => value.AsBool() ? "True" : "False",
StatescriptVariableType.Byte => value.AsInt32().ToString(CultureInfo.InvariantCulture),
StatescriptVariableType.SByte => value.AsInt32().ToString(CultureInfo.InvariantCulture),
StatescriptVariableType.Char => ((char)value.AsInt32()).ToString(),
StatescriptVariableType.Decimal => value.AsDouble().ToString("G", CultureInfo.InvariantCulture),
StatescriptVariableType.Double => value.AsDouble().ToString("G", CultureInfo.InvariantCulture),
StatescriptVariableType.Float => value.AsSingle().ToString("G", CultureInfo.InvariantCulture),
StatescriptVariableType.Int => value.AsInt32().ToString(CultureInfo.InvariantCulture),
StatescriptVariableType.UInt => value.AsInt64().ToString(CultureInfo.InvariantCulture),
StatescriptVariableType.Long => value.AsInt64().ToString(CultureInfo.InvariantCulture),
StatescriptVariableType.ULong => value.AsInt64().ToString(CultureInfo.InvariantCulture),
StatescriptVariableType.Short => value.AsInt32().ToString(CultureInfo.InvariantCulture),
StatescriptVariableType.UShort => value.AsInt32().ToString(CultureInfo.InvariantCulture),
StatescriptVariableType.Vector2 => FormatVector2(value.AsVector2()),
StatescriptVariableType.Vector3 => FormatVector3(value.AsVector3()),
StatescriptVariableType.Vector4 => FormatVector4(value.AsVector4()),
StatescriptVariableType.Plane => FormatPlane(value.AsPlane()),
StatescriptVariableType.Quaternion => FormatQuaternion(value.AsQuaternion()),
_ => value.ToString(),
};
}
public static InlineSummaryBadgeKind GetBadgeKind(StatescriptVariableType valueType)
{
return valueType switch
{
StatescriptVariableType.Bool => InlineSummaryBadgeKind.Boolean,
StatescriptVariableType.Vector2 => InlineSummaryBadgeKind.Vector,
StatescriptVariableType.Vector3 => InlineSummaryBadgeKind.Vector,
StatescriptVariableType.Vector4 => InlineSummaryBadgeKind.Vector,
StatescriptVariableType.Plane => InlineSummaryBadgeKind.Vector,
StatescriptVariableType.Quaternion => InlineSummaryBadgeKind.Vector,
_ => InlineSummaryBadgeKind.Numeric,
};
}
internal static bool TryGetSummaryBadgeForHighlighting(
FoldableContainer foldable,
[NotNullWhen(true)] out PanelContainer? badge)
{
return TryGetSummaryBadge(foldable, out badge);
}
private static SummaryBadgeData GetBadgeData(FoldableContainer foldable, NodeEditorProperty? editor)
{
if (!foldable.Folded || editor is null)
{
return SummaryBadgeData.Hidden;
}
string? highlightedVariableName = null;
string? highlightedSharedVariableSetPath = null;
string? highlightedSharedVariableName = null;
if (editor.TryGetHighlightedVariableName(out string propagatedVariableName)
&& !string.IsNullOrWhiteSpace(propagatedVariableName))
{
highlightedVariableName = propagatedVariableName;
}
if (editor.TryGetHighlightedSharedVariable(
out string propagatedSharedVariableSetPath,
out string propagatedSharedVariableName)
&& !string.IsNullOrWhiteSpace(propagatedSharedVariableSetPath)
&& !string.IsNullOrWhiteSpace(propagatedSharedVariableName))
{
highlightedSharedVariableSetPath = propagatedSharedVariableSetPath;
highlightedSharedVariableName = propagatedSharedVariableName;
}
if (editor.TryGetInlineSummary(out string summary) && !string.IsNullOrWhiteSpace(summary))
{
InlineSummaryBadgeKind badgeKind = editor.GetInlineSummaryBadgeKind();
return CreateBadgeData(
summary,
badgeKind,
IsConstantBadgeKind(badgeKind),
highlightedVariableName,
highlightedSharedVariableSetPath,
highlightedSharedVariableName);
}
return string.IsNullOrWhiteSpace(editor.DisplayName)
? SummaryBadgeData.Hidden
: CreateBadgeData(
editor.DisplayName,
InlineSummaryBadgeKind.Resolver,
false,
highlightedVariableName,
highlightedSharedVariableSetPath,
highlightedSharedVariableName);
}
private static SummaryBadgeData CreateBadgeData(
string text,
InlineSummaryBadgeKind badgeKind,
bool isConstant,
string? highlightedVariableName = null,
string? highlightedSharedVariableSetPath = null,
string? highlightedSharedVariableName = null)
{
BadgeVisualStyle style = GetBadgeStyle(badgeKind);
return new SummaryBadgeData(
GetBadgeIcon(badgeKind, isConstant),
text,
highlightedVariableName ?? string.Empty,
highlightedSharedVariableSetPath ?? string.Empty,
highlightedSharedVariableName ?? string.Empty,
badgeKind,
isConstant,
style.IconColor,
style.BackgroundColor,
style.BorderColor,
true);
}
private static PanelContainer GetOrCreateSummaryBadge(FoldableContainer foldable)
{
if (TryGetSummaryBadge(foldable, out PanelContainer? existingBadge))
{
return existingBadge;
}
var badge = new PanelContainer
{
Name = "InlineSummaryBadge",
Visible = false,
MouseFilter = Control.MouseFilterEnum.Ignore,
SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter,
};
var row = new HBoxContainer
{
Name = "Row",
MouseFilter = Control.MouseFilterEnum.Ignore,
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
};
row.AddThemeConstantOverride("separation", 4);
badge.AddChild(row);
var iconLabel = new Label
{
Name = "Icon",
MouseFilter = Control.MouseFilterEnum.Ignore,
SizeFlagsHorizontal = Control.SizeFlags.ShrinkBegin,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
};
row.AddChild(iconLabel);
var textLabel = new Label
{
Name = "Text",
MouseFilter = Control.MouseFilterEnum.Ignore,
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Center,
TextOverrunBehavior = TextServer.OverrunBehavior.TrimEllipsis,
};
row.AddChild(textLabel);
foldable.AddTitleBarControl(badge);
foldable.SetMeta(SummaryBadgeMetaKey, Variant.From(true));
foldable.SetMeta(SummaryBadgeInstanceIdMetaKey, Variant.From((long)badge.GetInstanceId()));
return badge;
}
private static void EnsureResizeSyncHook(FoldableContainer foldable)
{
if (foldable.HasMeta(SummaryBadgeResizeHookMetaKey)
&& foldable.GetMeta(SummaryBadgeResizeHookMetaKey).AsBool())
{
return;
}
foldable.Resized += () => SynchronizeSiblingBadgeWidths(foldable);
foldable.SetMeta(SummaryBadgeResizeHookMetaKey, Variant.From(true));
}
private static void ConfigureSummaryBadge(PanelContainer badge, SummaryBadgeData badgeData)
{
badge.Visible = badgeData.Visible;
if (!badgeData.Visible)
{
badge.SetMeta(SummaryBadgeKindMetaKey, Variant.From((int)InlineSummaryBadgeKind.Resolver));
badge.SetMeta(SummaryBadgeTextMetaKey, Variant.From(string.Empty));
badge.CustomMinimumSize = Vector2.Zero;
return;
}
badge.SetMeta(SummaryBadgeKindMetaKey, Variant.From((int)badgeData.BadgeKind));
badge.SetMeta(SummaryBadgeTextMetaKey, Variant.From(badgeData.Text));
badge.SetMeta(
"forge_inline_summary_badge_highlight_variable",
Variant.From(badgeData.HighlightVariableName));
badge.SetMeta(
"forge_inline_summary_badge_highlight_shared_set_path",
Variant.From(badgeData.HighlightSharedVariableSetPath));
badge.SetMeta(
"forge_inline_summary_badge_highlight_shared_variable",
Variant.From(badgeData.HighlightSharedVariableName));
StyleBoxFlat styleBox = new()
{
BgColor = badgeData.BackgroundColor,
BorderColor = badgeData.BorderColor,
CornerRadiusTopLeft = 8,
CornerRadiusTopRight = 8,
CornerRadiusBottomRight = 8,
CornerRadiusBottomLeft = 8,
BorderWidthLeft = 1,
BorderWidthTop = 1,
BorderWidthRight = 1,
BorderWidthBottom = 1,
ContentMarginLeft = 8,
ContentMarginTop = 3,
ContentMarginRight = 8,
ContentMarginBottom = 3,
};
badge.AddThemeStyleboxOverride("panel", styleBox);
Label? iconLabel = badge.GetNodeOrNull<Label>("Row/Icon");
Label? textLabel = badge.GetNodeOrNull<Label>("Row/Text");
if (iconLabel is null || textLabel is null)
{
return;
}
iconLabel.SizeFlagsHorizontal = Control.SizeFlags.ShrinkBegin;
textLabel.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
iconLabel.Text = badgeData.IconText;
textLabel.Text = badgeData.Text;
iconLabel.CustomMinimumSize = new Vector2(MeasureWidestBadgeIconWidth(iconLabel), 0);
iconLabel.AddThemeColorOverride("font_color", badgeData.IconColor);
textLabel.AddThemeColorOverride("font_color", textLabel.GetThemeColor("font_color", "Label"));
textLabel.CustomMinimumSize = Vector2.Zero;
if (badge.HasMeta(SummaryBadgeSelectedVariableMetaKey))
{
string selectedVariableName = badge.GetMeta(SummaryBadgeSelectedVariableMetaKey).AsString();
if (!string.IsNullOrEmpty(selectedVariableName)
&& selectedVariableName == badgeData.HighlightVariableName)
{
styleBox.BorderWidthLeft = Math.Max(styleBox.BorderWidthLeft, 2);
styleBox.BorderWidthTop = Math.Max(styleBox.BorderWidthTop, 2);
styleBox.BorderWidthRight = Math.Max(styleBox.BorderWidthRight, 2);
styleBox.BorderWidthBottom = Math.Max(styleBox.BorderWidthBottom, 2);
styleBox.BorderColor = new Color(0x56b6c2ff);
styleBox.BgColor = new Color(0x56b6c2ff);
iconLabel.AddThemeColorOverride("font_color", Colors.Black);
textLabel.AddThemeColorOverride("font_color", Colors.Black);
}
}
if (badge.HasMeta(SummaryBadgeSelectedSharedVariableSetPathMetaKey)
&& badge.HasMeta(SummaryBadgeSelectedSharedVariableMetaKey))
{
string selectedSharedVariableSetPath = badge.GetMeta(SummaryBadgeSelectedSharedVariableSetPathMetaKey)
.AsString();
string selectedSharedVariableName = badge.GetMeta(SummaryBadgeSelectedSharedVariableMetaKey)
.AsString();
if (!string.IsNullOrEmpty(selectedSharedVariableSetPath)
&& !string.IsNullOrEmpty(selectedSharedVariableName)
&& selectedSharedVariableSetPath == badgeData.HighlightSharedVariableSetPath
&& selectedSharedVariableName == badgeData.HighlightSharedVariableName)
{
styleBox.BorderWidthLeft = Math.Max(styleBox.BorderWidthLeft, 2);
styleBox.BorderWidthTop = Math.Max(styleBox.BorderWidthTop, 2);
styleBox.BorderWidthRight = Math.Max(styleBox.BorderWidthRight, 2);
styleBox.BorderWidthBottom = Math.Max(styleBox.BorderWidthBottom, 2);
styleBox.BorderColor = new Color(0x56b6c2ff);
styleBox.BgColor = new Color(0x56b6c2ff);
iconLabel.AddThemeColorOverride("font_color", Colors.Black);
textLabel.AddThemeColorOverride("font_color", Colors.Black);
}
}
}
private static float MeasureWidestBadgeIconWidth(Label label)
{
float maxWidth = 0f;
foreach (InlineSummaryBadgeKind badgeKind in Enum.GetValues<InlineSummaryBadgeKind>())
{
maxWidth = Math.Max(maxWidth, MeasureLabelTextWidth(label, GetBadgeIcon(badgeKind, false)));
}
return Math.Max(maxWidth, MeasureLabelTextWidth(label, GetBadgeIcon(InlineSummaryBadgeKind.Resolver, true)));
}
private static bool TryGetSummaryBadge(FoldableContainer foldable, [NotNullWhen(true)] out PanelContainer? badge)
{
badge = null;
if (!foldable.HasMeta(SummaryBadgeMetaKey) || !foldable.HasMeta(SummaryBadgeInstanceIdMetaKey))
{
return false;
}
ulong badgeInstanceId = (ulong)foldable.GetMeta(SummaryBadgeInstanceIdMetaKey).AsInt64();
if (badgeInstanceId == 0)
{
return false;
}
if (GodotObject.InstanceFromId(badgeInstanceId) is PanelContainer existingBadge
&& GodotObject.IsInstanceValid(existingBadge))
{
badge = existingBadge;
return true;
}
return false;
}
private static void SynchronizeSiblingBadgeWidths(FoldableContainer foldable)
{
if (foldable.GetParent() is not Node parent)
{
return;
}
var siblingFoldables = new List<FoldableContainer>();
var siblingBadges = new List<PanelContainer>();
foreach (Node child in parent.GetChildren())
{
if (child is FoldableContainer siblingFoldable
&& TryGetSummaryBadge(siblingFoldable, out PanelContainer? badge))
{
siblingFoldables.Add(siblingFoldable);
siblingBadges.Add(badge);
}
}
if (siblingBadges.Count == 0)
{
return;
}
foreach (PanelContainer badge in siblingBadges)
{
badge.CustomMinimumSize = Vector2.Zero;
}
float widestTitleWidth = 0;
foreach (FoldableContainer siblingFoldable in siblingFoldables)
{
widestTitleWidth = Math.Max(widestTitleWidth, MeasureFoldableTitleWidth(siblingFoldable));
}
float availableWidth = parent is Control parentControl
? parentControl.Size.X
- widestTitleWidth
- FoldableTitleChromeWidth
- FoldableTitleBadgeGap
- FoldableTitleRightPadding
: float.MaxValue;
float maxWidth = MinimumBadgeWidth;
if (!float.IsPositiveInfinity(availableWidth) && !float.IsNaN(availableWidth))
{
maxWidth = Math.Max(0f, availableWidth);
}
for (int i = 0; i < siblingBadges.Count; i++)
{
PanelContainer badge = siblingBadges[i];
badge.CustomMinimumSize = badge.Visible
? new Vector2(maxWidth, 0)
: Vector2.Zero;
}
}
private static BadgeVisualStyle GetBadgeStyle(InlineSummaryBadgeKind badgeKind)
{
return badgeKind switch
{
InlineSummaryBadgeKind.Numeric => new BadgeVisualStyle(
_numericIconColor,
_numericBackgroundColor,
_numericBorderColor),
InlineSummaryBadgeKind.Vector => new BadgeVisualStyle(
_vectorIconColor,
_vectorBackgroundColor,
_vectorBorderColor),
InlineSummaryBadgeKind.Boolean => new BadgeVisualStyle(
_booleanIconColor,
_booleanBackgroundColor,
_booleanBorderColor),
InlineSummaryBadgeKind.Variable => new BadgeVisualStyle(
_variableIconColor,
_variableBackgroundColor,
_variableBorderColor),
InlineSummaryBadgeKind.SharedVariable => new BadgeVisualStyle(
_sharedVariableIconColor,
_sharedVariableBackgroundColor,
_sharedVariableBorderColor),
InlineSummaryBadgeKind.Enum => new BadgeVisualStyle(
_enumIconColor,
_enumBackgroundColor,
_enumBorderColor),
_ => new BadgeVisualStyle(
_resolverIconColor,
_resolverBackgroundColor,
_resolverBorderColor),
};
}
private static bool IsConstantBadgeKind(InlineSummaryBadgeKind badgeKind)
{
return badgeKind is InlineSummaryBadgeKind.Numeric
or InlineSummaryBadgeKind.Vector
or InlineSummaryBadgeKind.Boolean;
}
private static float MeasureFoldableTitleWidth(FoldableContainer foldable)
{
Font? font = foldable.GetThemeDefaultFont();
if (font is null)
{
return 0;
}
int fontSize = foldable.GetThemeDefaultFontSize();
return font.GetStringSize(
foldable.Title,
HorizontalAlignment.Left,
-1,
fontSize,
TextServer.JustificationFlag.None,
TextServer.Direction.Auto,
TextServer.Orientation.Horizontal).X;
}
private static string GetBadgeIcon(InlineSummaryBadgeKind badgeKind, bool isConstant)
{
return badgeKind switch
{
InlineSummaryBadgeKind.Numeric => "#",
InlineSummaryBadgeKind.Vector => "▦",
InlineSummaryBadgeKind.Boolean => "●",
InlineSummaryBadgeKind.Variable => "𝑥",
InlineSummaryBadgeKind.SharedVariable => "𝑦",
InlineSummaryBadgeKind.Enum => "≡",
_ => isConstant ? "#" : "ƒ",
};
}
private static float MeasureLabelTextWidth(Label label, string text)
{
Font? font = label.GetThemeFont("font", "Label") ?? label.GetThemeDefaultFont();
if (font is null)
{
return 0;
}
int fontSize = label.GetThemeFontSize("font_size", "Label");
if (fontSize <= 0)
{
fontSize = label.GetThemeDefaultFontSize();
}
return font.GetStringSize(
text,
HorizontalAlignment.Left,
-1,
fontSize,
TextServer.JustificationFlag.None,
TextServer.Direction.Auto,
TextServer.Orientation.Horizontal).X;
}
private static string FormatVector2(Vector2 value)
{
return $"({FormatNumber(value.X)}, {FormatNumber(value.Y)})";
}
private static string FormatVector3(Vector3 value)
{
return $"({FormatNumber(value.X)}, {FormatNumber(value.Y)}, {FormatNumber(value.Z)})";
}
private static string FormatVector4(Vector4 value)
{
return $"({FormatNumber(value.X)}, {FormatNumber(value.Y)}, {FormatNumber(value.Z)}, {FormatNumber(value.W)})";
}
private static string FormatPlane(Plane value)
{
return $"({FormatNumber(value.Normal.X)}, {FormatNumber(value.Normal.Y)}, {FormatNumber(value.Normal.Z)}, " +
$"{FormatNumber(value.D)})";
}
private static string FormatQuaternion(Quaternion value)
{
return $"({FormatNumber(value.X)}, {FormatNumber(value.Y)}, {FormatNumber(value.Z)}, {FormatNumber(value.W)})";
}
private static string FormatNumber(float value)
{
return value.ToString("G", CultureInfo.InvariantCulture);
}
private readonly record struct BadgeVisualStyle(
Color IconColor,
Color BackgroundColor,
Color BorderColor);
private readonly record struct SummaryBadgeData(
string IconText,
string Text,
string HighlightVariableName,
string HighlightSharedVariableSetPath,
string HighlightSharedVariableName,
InlineSummaryBadgeKind BadgeKind,
bool IsConstant,
Color IconColor,
Color BackgroundColor,
Color BorderColor,
bool Visible)
{
public static SummaryBadgeData Hidden => new(
string.Empty,
string.Empty,
string.Empty,
string.Empty,
string.Empty,
InlineSummaryBadgeKind.Resolver,
false,
Colors.Transparent,
Colors.Transparent,
Colors.Transparent,
false);
}
}
#endif

View File

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

View File

@@ -0,0 +1,16 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
namespace Gamesmiths.Forge.Godot.Editor.Statescript;
internal enum InlineSummaryBadgeKind
{
Resolver = 0,
Numeric = 1,
Vector = 2,
Boolean = 3,
Variable = 4,
SharedVariable = 5,
Enum = 6,
}
#endif

View File

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

View File

@@ -0,0 +1,9 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript;
internal sealed record InputPropertyFoldableContext(FoldableContainer Foldable, string BaseTitle);
#endif

View File

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

View File

@@ -14,6 +14,8 @@ namespace Gamesmiths.Forge.Godot.Editor.Statescript;
[Tool]
internal abstract partial class NodeEditorProperty : PanelContainer
{
private Type[] _allowedExpectedTypes = [];
/// <summary>
/// Gets the display name shown in the resolver type dropdown (e.g., "Variable", "Constant", "Attribute").
/// </summary>
@@ -58,6 +60,58 @@ internal abstract partial class NodeEditorProperty : PanelContainer
/// </summary>
public event Action? LayoutSizeChanged;
/// <summary>
/// Configures the concrete input types allowed for this editor when the surrounding context accepts more than one.
/// </summary>
/// <param name="allowedExpectedTypes">The allowed expected types.</param>
public void ConfigureAllowedExpectedTypes(params Type[] allowedExpectedTypes)
{
_allowedExpectedTypes = allowedExpectedTypes;
}
/// <summary>
/// Tries to provide a short inline summary for the current editor state when embedded in a collapsed foldout.
/// </summary>
/// <param name="summary">The inline summary, when available.</param>
/// <returns><see langword="true"/> when an inline summary is available.</returns>
public virtual bool TryGetInlineSummary(out string summary)
{
summary = string.Empty;
return false;
}
/// <summary>
/// Gets the preferred badge style for inline foldout summaries.
/// </summary>
public virtual InlineSummaryBadgeKind GetInlineSummaryBadgeKind()
{
return InlineSummaryBadgeKind.Resolver;
}
/// <summary>
/// Tries to provide the graph-variable name represented by this editor or one of its nested editors for highlight
/// propagation in folded summaries.
/// </summary>
/// <param name="variableName">The variable name, when available.</param>
public virtual bool TryGetHighlightedVariableName(out string variableName)
{
variableName = string.Empty;
return false;
}
/// <summary>
/// Tries to provide the shared-variable identity represented by this editor or one of its nested editors for
/// highlight propagation in folded summaries.
/// </summary>
/// <param name="sharedVariableSetPath">The shared-variable set path, when available.</param>
/// <param name="variableName">The shared variable name, when available.</param>
public virtual bool TryGetHighlightedSharedVariable(out string sharedVariableSetPath, out string variableName)
{
sharedVariableSetPath = string.Empty;
variableName = string.Empty;
return false;
}
/// <summary>
/// Clears all delegate fields to prevent serialization issues during hot-reload. Called before the editor is
/// serialized or freed.
@@ -74,5 +128,10 @@ internal abstract partial class NodeEditorProperty : PanelContainer
{
LayoutSizeChanged?.Invoke();
}
protected Type[] GetAllowedExpectedTypes(Type fallbackExpectedType)
{
return _allowedExpectedTypes.Length > 0 ? _allowedExpectedTypes : [fallbackExpectedType];
}
}
#endif

View File

@@ -0,0 +1,95 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
namespace Gamesmiths.Forge.Godot.Editor.Statescript;
internal static class SharedVariableHighlightState
{
private static string? _selectedSetPath;
private static string? _selectedVariableName;
private static string? _activeInspectorSetPath;
public static event Action? Changed;
public static void SetInspectorContext(string? setPath)
{
string normalized = string.IsNullOrWhiteSpace(setPath) ? string.Empty : setPath;
if (string.Equals(_activeInspectorSetPath, normalized, StringComparison.Ordinal))
{
return;
}
_activeInspectorSetPath = normalized;
Changed?.Invoke();
}
public static void ClearInspectorContext(string? setPath = null)
{
if (setPath is not null
&& !string.Equals(_activeInspectorSetPath, setPath, StringComparison.Ordinal))
{
return;
}
if (string.IsNullOrEmpty(_activeInspectorSetPath))
{
return;
}
_activeInspectorSetPath = string.Empty;
Changed?.Invoke();
}
public static void SetSelection(string? setPath, string? variableName)
{
string normalizedSetPath = string.IsNullOrWhiteSpace(setPath) ? string.Empty : setPath;
string normalizedVariableName = string.IsNullOrWhiteSpace(variableName) ? string.Empty : variableName;
if (string.IsNullOrEmpty(normalizedSetPath) || string.IsNullOrEmpty(normalizedVariableName))
{
normalizedSetPath = string.Empty;
normalizedVariableName = string.Empty;
}
if (string.Equals(_selectedSetPath, normalizedSetPath, StringComparison.Ordinal)
&& string.Equals(_selectedVariableName, normalizedVariableName, StringComparison.Ordinal))
{
return;
}
_selectedSetPath = normalizedSetPath;
_selectedVariableName = normalizedVariableName;
Changed?.Invoke();
}
public static bool TryGetActiveSelection(out string setPath, out string variableName)
{
if (string.IsNullOrEmpty(_selectedSetPath)
|| string.IsNullOrEmpty(_selectedVariableName)
|| !string.Equals(_activeInspectorSetPath, _selectedSetPath, StringComparison.Ordinal))
{
setPath = string.Empty;
variableName = string.Empty;
return false;
}
setPath = _selectedSetPath;
variableName = _selectedVariableName;
return true;
}
public static bool HasAnySelection()
{
return !string.IsNullOrEmpty(_selectedSetPath) && !string.IsNullOrEmpty(_selectedVariableName);
}
public static bool IsSelected(string? setPath, string? variableName)
{
return TryGetActiveSelection(out string activeSetPath, out string activeVariableName)
&& string.Equals(activeSetPath, setPath, StringComparison.Ordinal)
&& string.Equals(activeVariableName, variableName, StringComparison.Ordinal);
}
}
#endif

View File

@@ -0,0 +1 @@
uid://2gcypje3sxpa

View File

@@ -16,7 +16,15 @@ namespace Gamesmiths.Forge.Godot.Editor.Statescript;
[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<string> _expandedArrays = [];
@@ -33,6 +41,7 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
private Texture2D? _addIcon;
private Texture2D? _removeIcon;
private string? _selectedVariableName;
/// <summary>
/// Sets the <see cref="EditorUndoRedoManager"/> used for undo/redo support.
@@ -45,11 +54,21 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
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,
};
@@ -70,14 +89,19 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
AddChild(backgroundPanel);
SetBottomEditor(backgroundPanel);
_root = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
_root = new VBoxContainer
{
Name = RootNodeName,
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
backgroundPanel.AddChild(_root);
var headerHBox = new HBoxContainer();
var headerHBox = new HBoxContainer { Name = HeaderRowNodeName };
_root.AddChild(headerHBox);
_addButton = new Button
{
Name = AddButtonNodeName,
Text = "Add Variable",
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
@@ -87,34 +111,55 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
_root.AddChild(new HSeparator());
_variableList = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
_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()
{
if (_addButton is not null)
{
_addButton.Pressed -= OnAddPressed;
}
ClearVariableList();
_creationDialog?.Free();
_creationDialog = null;
_newNameEdit = null;
_newTypeDropdown = null;
_newArrayToggle = null;
SharedVariableHighlightState.Changed -= OnSharedVariableHighlightChanged;
ReleaseUiState();
}
public void OnAfterDeserialize()
{
if (_addButton is not null)
EnsureControlsCached();
SharedVariableHighlightState.Changed += OnSharedVariableHighlightChanged;
SyncSelectedVariableFromHighlightState();
if (_addButton is not null && IsInstanceValid(_addButton))
{
_addButton.Pressed += OnAddPressed;
}
@@ -122,6 +167,28 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
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<ForgeSharedVariableDefinition> GetDefinitions()
{
GodotObject obj = GetEditedObject();
@@ -133,7 +200,18 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
private void NotifyChanged()
{
if (GetEditedObject() is Resource resource)
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();
}
@@ -141,35 +219,31 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
private void RebuildList()
{
EnsureControlsCached();
if (_variableList is null)
{
return;
}
// Defer the actual rebuild so that any in-progress signal emission (e.g. a button Pressed handler that
// triggered an add/remove) finishes before we free the emitting nodes.
CallDeferred(MethodName.RebuildListDeferred);
}
private void RebuildListDeferred()
{
if (_variableList is null)
{
return;
}
SyncSelectedVariableFromHighlightState();
ClearVariableList();
Array<ForgeSharedVariableDefinition> definitions = GetDefinitions();
for (var i = 0; i < definitions.Count; i++)
for (int i = 0; i < definitions.Count; i++)
{
AddVariableRow(definitions, i);
}
RefreshVariableSelectionVisuals();
}
private void ClearVariableList()
{
EnsureControlsCached();
if (_variableList is null)
{
return;
@@ -182,6 +256,75 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
}
}
private void EnsureControlsCached()
{
_root ??= GetNodeOrNull<VBoxContainer>(
$"{BackgroundPanelNodeName}/{RootNodeName}");
_addButton ??= GetNodeOrNull<Button>(
$"{BackgroundPanelNodeName}/{RootNodeName}/{HeaderRowNodeName}/{AddButtonNodeName}");
_variableList ??= GetNodeOrNull<VBoxContainer>(
$"{BackgroundPanelNodeName}/{RootNodeName}/{VariableListNodeName}");
}
private void ReleaseUiState()
{
if (_addButton is not null && IsInstanceValid(_addButton))
{
_addButton.Pressed -= OnAddPressed;
}
ClearVariableList();
if (_creationDialog is not null && IsInstanceValid(_creationDialog))
{
_creationDialog.Confirmed -= OnCreationConfirmed;
_creationDialog.Canceled -= OnCreationCanceled;
_creationDialog.Free();
}
_creationDialog = null;
_newNameEdit = null;
_newTypeDropdown = null;
_newArrayToggle = null;
_root = null;
_variableList = null;
_addButton = null;
}
private void FreeAllChildren()
{
for (int i = GetChildCount() - 1; i >= 0; i--)
{
Node child = GetChild(i);
RemoveChild(child);
child.Free();
}
}
private void OnSharedVariableHighlightChanged()
{
SyncSelectedVariableFromHighlightState();
RefreshVariableSelectionVisuals();
}
private void SyncSelectedVariableFromHighlightState()
{
if (GetEditedObject() is not ForgeSharedVariableSet sharedVariableSet)
{
_selectedVariableName = null;
return;
}
if (SharedVariableHighlightState.TryGetActiveSelection(out string selectedSetPath, out string variableName)
&& string.Equals(selectedSetPath, sharedVariableSet.ResourcePath, System.StringComparison.Ordinal))
{
_selectedVariableName = variableName;
return;
}
_selectedVariableName = null;
}
private void AddVariableRow(Array<ForgeSharedVariableDefinition> definitions, int index)
{
if (_variableList is null || index < 0 || index >= definitions.Count)
@@ -197,17 +340,25 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
var headerRow = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
rowContainer.AddChild(headerRow);
var nameLabel = new Label
bool isSelected = _selectedVariableName == def.VariableName;
var nameButton = new Button
{
Text = def.VariableName,
SizeFlagsHorizontal = SizeFlags.ExpandFill,
Flat = true,
ToggleMode = true,
ButtonPressed = isSelected,
Alignment = HorizontalAlignment.Left,
};
nameLabel.AddThemeColorOverride("font_color", _variableColor);
nameLabel.AddThemeFontOverride(
nameButton.SetMeta(VariableNameButtonMetaKey, def.VariableName);
UpdateVariableNameButtonAppearance(nameButton, isSelected);
nameButton.AddThemeFontOverride(
"font",
EditorInterface.Singleton.GetEditorTheme().GetFont("bold", "EditorFonts"));
headerRow.AddChild(nameLabel);
nameButton.Toggled += pressed => SetSelectedVariable(def.VariableName, pressed);
headerRow.AddChild(nameButton);
var typeLabel = new Label
{
@@ -218,7 +369,7 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
typeLabel.AddThemeColorOverride("font_color", new Color(0.6f, 0.6f, 0.6f));
headerRow.AddChild(typeLabel);
var capturedIndex = index;
int capturedIndex = index;
var deleteButton = new Button
{
@@ -245,6 +396,56 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
rowContainer.AddChild(new HSeparator());
}
private void SetSelectedVariable(string variableName, bool selected)
{
if (selected)
{
_selectedVariableName = variableName;
}
else if (_selectedVariableName == variableName)
{
_selectedVariableName = null;
}
if (GetEditedObject() is ForgeSharedVariableSet sharedVariableSet)
{
SharedVariableHighlightState.SetInspectorContext(sharedVariableSet.ResourcePath);
SharedVariableHighlightState.SetSelection(sharedVariableSet.ResourcePath, _selectedVariableName);
}
else
{
SharedVariableHighlightState.SetSelection(null, null);
}
RefreshVariableSelectionVisuals();
}
private void RefreshVariableSelectionVisuals()
{
if (_variableList is null)
{
return;
}
RefreshVariableSelectionVisualsRecursive(_variableList);
}
private void RefreshVariableSelectionVisualsRecursive(Node parent)
{
foreach (Node child in parent.GetChildren())
{
if (child is Button button && button.HasMeta(VariableNameButtonMetaKey))
{
string variableName = button.GetMeta(VariableNameButtonMetaKey).AsString();
bool isSelected = _selectedVariableName == variableName;
button.SetPressedNoSignal(isSelected);
UpdateVariableNameButtonAppearance(button, isSelected);
}
RefreshVariableSelectionVisualsRecursive(child);
}
}
private Control CreateValueEditor(ForgeSharedVariableDefinition def)
{
if (def.VariableType == StatescriptVariableType.Bool)
@@ -310,7 +511,7 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
var headerRow = new HBoxContainer();
vBox.AddChild(headerRow);
var isExpanded = _expandedArrays.Contains(def.VariableName);
bool isExpanded = _expandedArrays.Contains(def.VariableName);
var elementsContainer = new VBoxContainer
{
@@ -330,7 +531,7 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
{
elementsContainer.Visible = x;
var wasExpanded = !x;
bool wasExpanded = !x;
if (x)
{
@@ -379,9 +580,9 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
vBox.AddChild(elementsContainer);
for (var i = 0; i < def.InitialArrayValues.Count; i++)
for (int i = 0; i < def.InitialArrayValues.Count; i++)
{
var capturedIndex = i;
int capturedIndex = i;
if (def.VariableType == StatescriptVariableType.Bool)
{
@@ -502,7 +703,7 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
private void AddArrayElement(ForgeSharedVariableDefinition def, Variant value)
{
var wasExpanded = _expandedArrays.Contains(def.VariableName);
bool wasExpanded = _expandedArrays.Contains(def.VariableName);
if (_undoRedo is not null)
{
@@ -583,6 +784,8 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
(int)variableType);
}
_newTypeDropdown.Selected = FindTypeDropdownIndex(_newTypeDropdown, StatescriptVariableType.Int);
typeRow.AddChild(_newTypeDropdown);
var arrayRow = new HBoxContainer();
@@ -605,7 +808,7 @@ internal sealed partial class SharedVariableSetEditorProperty : EditorProperty,
return;
}
var name = _newNameEdit.Text.Trim();
string name = _newNameEdit.Text.Trim();
if (string.IsNullOrEmpty(name))
{

View File

@@ -206,7 +206,7 @@ internal sealed partial class StatescriptAddNodeDialog : ConfirmationDialog, ISe
IReadOnlyList<StatescriptNodeDiscovery.NodeTypeInfo> discoveredTypes =
StatescriptNodeDiscovery.GetDiscoveredNodeTypes();
var filterLower = filter.ToLowerInvariant();
string filterLower = filter.ToLowerInvariant();
TreeItem? actionCategory = null;
TreeItem? conditionCategory = null;
@@ -304,7 +304,7 @@ internal sealed partial class StatescriptAddNodeDialog : ConfirmationDialog, ISe
GetOkButton().Disabled = false;
var metadata = selected.GetMetadata(0).AsString();
string metadata = selected.GetMetadata(0).AsString();
UpdateDescription(metadata);
}
@@ -330,7 +330,7 @@ internal sealed partial class StatescriptAddNodeDialog : ConfirmationDialog, ISe
return;
}
var metadata = selected.GetMetadata(0).AsString();
string metadata = selected.GetMetadata(0).AsString();
if (metadata == "__exit__")
{

View File

@@ -139,8 +139,8 @@ internal static partial class StatescriptEditorControls
Func<int, double> getComponent,
Action<double[]>? onChanged)
{
var componentCount = GetVectorComponentCount(type);
var labels = GetVectorComponentLabels(type);
int componentCount = GetVectorComponentCount(type);
string[] labels = GetVectorComponentLabels(type);
var vBox = new VBoxContainer { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
var row = new HBoxContainer { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
@@ -153,11 +153,11 @@ internal static partial class StatescriptEditorControls
vBox.AddChild(panelContainer);
panelContainer.AddChild(row);
var values = new double[componentCount];
double[] values = new double[componentCount];
var handler = new VectorComponentHandler(values) { OnChanged = onChanged };
vBox.AddChild(handler);
for (var i = 0; i < componentCount; i++)
for (int i = 0; i < componentCount; i++)
{
values[i] = getComponent(i);

View File

@@ -73,7 +73,7 @@ public partial class StatescriptGraphEditorDock
return;
}
var path = _newStatescriptPathEdit.Text.Trim();
string path = _newStatescriptPathEdit.Text.Trim();
if (string.IsNullOrEmpty(path))
{
return;
@@ -270,7 +270,7 @@ public partial class StatescriptGraphEditorDock
{
if (_pendingConnectionIsOutput)
{
var inputPort = FindFirstEnabledInputPort(newNodeId);
int inputPort = FindFirstEnabledInputPort(newNodeId);
if (inputPort >= 0)
{
OnConnectionRequest(
@@ -282,7 +282,7 @@ public partial class StatescriptGraphEditorDock
}
else
{
var outputPort = FindFirstEnabledOutputPort(newNodeId);
int outputPort = FindFirstEnabledOutputPort(newNodeId);
if (outputPort >= 0)
{
OnConnectionRequest(

View File

@@ -238,6 +238,7 @@ public partial class StatescriptGraphEditorDock
if (CurrentGraph == graph)
{
InvalidateCachedGraphVisuals(graph);
LoadGraphIntoEditor(graph);
}
}
@@ -339,7 +340,7 @@ public partial class StatescriptGraphEditorDock
return string.Empty;
}
var nodeId = $"node_{_nextNodeId++}";
string nodeId = $"node_{_nextNodeId++}";
var nodeResource = new StatescriptNode
{
@@ -371,10 +372,9 @@ public partial class StatescriptGraphEditorDock
if (CurrentGraph == graph && _graphEdit is not null)
{
var graphNode = new StatescriptGraphNode();
_graphEdit.AddChild(graphNode);
graphNode.Initialize(nodeResource, graph);
graphNode.SetUndoRedo(_undoRedo);
GraphTab? tab = FindTab(graph);
StatescriptGraphNode graphNode = AddGraphNodeVisual(nodeResource, graph);
tab?.CachedGraphNodes.Add(graphNode);
}
}
@@ -384,6 +384,7 @@ public partial class StatescriptGraphEditorDock
if (CurrentGraph == graph)
{
InvalidateCachedGraphVisuals(graph);
LoadGraphIntoEditor(graph);
}
}
@@ -424,7 +425,7 @@ public partial class StatescriptGraphEditorDock
foreach (StatescriptGraphNode sgn in selectedNodes)
{
StatescriptNode original = sgn.NodeResource!;
var newNodeId = $"node_{_nextNodeId++}";
string newNodeId = $"node_{_nextNodeId++}";
duplicatedIds[original.NodeId] = newNodeId;
var duplicated = new StatescriptNode
@@ -457,16 +458,16 @@ public partial class StatescriptGraphEditorDock
graph.Nodes.Add(duplicated);
var graphNode = new StatescriptGraphNode();
_graphEdit.AddChild(graphNode);
graphNode.Initialize(duplicated, graph);
GraphTab? tab = FindTab(graph);
StatescriptGraphNode graphNode = AddGraphNodeVisual(duplicated, graph);
tab?.CachedGraphNodes.Add(graphNode);
graphNode.Selected = true;
}
foreach (StatescriptConnection connection in graph.Connections)
{
if (duplicatedIds.TryGetValue(connection.FromNode, out var newFrom)
&& duplicatedIds.TryGetValue(connection.ToNode, out var newTo))
if (duplicatedIds.TryGetValue(connection.FromNode, out string? newFrom)
&& duplicatedIds.TryGetValue(connection.ToNode, out string? newTo))
{
_graphEdit.ConnectNode(newFrom, connection.OutputPort, newTo, connection.InputPort);
}

View File

@@ -54,8 +54,11 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
private string[]? _serializedTabPaths;
private int _serializedActiveTab = -1;
private bool[]? _serializedVariablesStates;
private string?[]? _serializedSelectedVariables;
private string[]? _serializedConnections;
private int[]? _serializedConnectionCounts;
private bool _persistedVariablesPanelVisible = true;
private bool _sharedVariableHighlightSubscribed;
/// <summary>
/// Gets the currently active graph resource, if any.
@@ -92,13 +95,16 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
_filesystemChangedCallable = new Callable(this, nameof(OnFilesystemChanged));
_fileSystem.Connect(EditorFileSystem.SignalName.ResourcesReimported, _filesystemChangedCallable);
SubscribeSharedVariableHighlightState();
}
public override void _ExitTree()
{
base._ExitTree();
UnsubscribeSharedVariableHighlightState();
ClearGraphEditor();
DisposeCachedGraphVisuals();
_openTabs.Clear();
if (_fileSystem?.IsConnected(EditorFileSystem.SignalName.ResourcesReimported, _filesystemChangedCallable)
@@ -108,10 +114,14 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
}
DisconnectUISignals();
_fileSystem = null;
_filesystemChangedCallable = default;
}
public void OnBeforeSerialize()
{
UnsubscribeSharedVariableHighlightState();
if (_fileSystem?.IsConnected(EditorFileSystem.SignalName.ResourcesReimported, _filesystemChangedCallable)
== true)
{
@@ -121,6 +131,8 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
_serializedTabPaths = GetOpenResourcePaths();
_serializedActiveTab = GetActiveTabIndex();
_serializedVariablesStates = GetVariablesPanelStates();
_serializedSelectedVariables = GetSelectedVariableStates();
_persistedVariablesPanelVisible = _variablePanel?.Visible ?? _persistedVariablesPanelVisible;
SyncVisualNodePositionsToGraph();
SyncConnectionsToCurrentGraph();
@@ -133,10 +145,10 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
var allConnections = new List<string>();
_serializedConnectionCounts = new int[_openTabs.Count];
for (var i = 0; i < _openTabs.Count; i++)
for (int i = 0; i < _openTabs.Count; i++)
{
StatescriptGraph graph = _openTabs[i].GraphResource;
var count = 0;
int count = 0;
foreach (StatescriptConnection c in graph.Connections)
{
allConnections.Add($"{c.FromNode},{c.OutputPort},{c.ToNode},{c.InputPort}");
@@ -159,6 +171,8 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
}
}
DisposeCachedGraphVisuals();
_openTabs.Clear();
}
@@ -173,6 +187,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
}
ConnectUISignals();
SubscribeSharedVariableHighlightState();
if (_serializedTabPaths?.Length > 0)
{
@@ -210,23 +225,30 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return;
}
for (var i = 0; i < _openTabs.Count; i++)
PersistCurrentVariablePanelState();
for (int i = 0; i < _openTabs.Count; i++)
{
if (_openTabs[i].GraphResource == graph || (!string.IsNullOrEmpty(graph.ResourcePath)
&& _openTabs[i].ResourcePath == graph.ResourcePath))
{
_tabBar.CurrentTab = i;
SetCurrentTabWithoutLoading(i);
ApplyVariablesPanelState(i);
return;
}
}
graph.EnsureEntryNode();
var tab = new GraphTab(graph);
var tab = new GraphTab(graph)
{
VariablesPanelOpen = _variablePanel?.Visible ?? false,
};
_openTabs.Add(tab);
_tabBar.AddTab(graph.StatescriptName);
_tabBar.CurrentTab = _openTabs.Count - 1;
SetCurrentTabWithoutLoading(_openTabs.Count - 1);
LoadGraphIntoEditor(graph);
UpdateVisibility();
@@ -242,7 +264,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return;
}
var currentTab = _tabBar.CurrentTab;
int currentTab = _tabBar.CurrentTab;
if (currentTab < 0 || currentTab >= _openTabs.Count)
{
return;
@@ -257,7 +279,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
/// <returns>An array of resource paths.</returns>
public string[] GetOpenResourcePaths()
{
return [.. _openTabs.Select(x => x.ResourcePath)];
return [.. GetPersistedTabs().Select(x => x.ResourcePath)];
}
/// <summary>
@@ -266,7 +288,35 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
/// <returns>The active tab index, or -1 if no tabs are open.</returns>
public int GetActiveTabIndex()
{
return _tabBar?.CurrentTab ?? -1;
PersistCurrentVariablePanelState();
if (_tabBar is null)
{
return -1;
}
int currentTab = _tabBar.CurrentTab;
if (currentTab < 0 || currentTab >= _openTabs.Count)
{
return -1;
}
GraphTab current = _openTabs[currentTab];
if (string.IsNullOrEmpty(current.ResourcePath))
{
return -1;
}
GraphTab[] persistedTabs = GetPersistedTabs();
for (int i = 0; i < persistedTabs.Length; i++)
{
if (persistedTabs[i].ResourcePath == current.ResourcePath)
{
return i;
}
}
return -1;
}
/// <summary>
@@ -276,7 +326,14 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
/// </returns>
public bool[] GetVariablesPanelStates()
{
return [.. _openTabs.Select(x => x.VariablesPanelOpen)];
PersistCurrentVariablePanelState();
return [.. GetPersistedTabs().Select(x => x.VariablesPanelOpen)];
}
public string?[] GetSelectedVariableStates()
{
PersistCurrentVariablePanelState();
return [.. GetPersistedTabs().Select(x => x.SelectedVariableName)];
}
/// <summary>
@@ -331,10 +388,10 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
_tabBar.RemoveTab(0);
}
var skippedTabs = 0;
for (var i = 0; i < paths.Length; i++)
int skippedTabs = 0;
for (int i = 0; i < paths.Length; i++)
{
var path = paths[i];
string path = paths[i];
StatescriptGraph? graph = LoadGraphFromPath(path);
if (graph is null)
@@ -346,7 +403,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
graph.EnsureEntryNode();
var tab = new GraphTab(graph);
var currentTab = i - skippedTabs;
int currentTab = i - skippedTabs;
if (variablesStates is not null && currentTab < variablesStates.Length)
{
tab.VariablesPanelOpen = variablesStates[currentTab];
@@ -360,10 +417,17 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
if (activeIndex >= 0 && activeIndex < _openTabs.Count)
{
_tabBar.CurrentTab = activeIndex;
_openTabs[activeIndex].VariablesPanelOpen = _persistedVariablesPanelVisible;
SetCurrentTabWithoutLoading(activeIndex);
LoadGraphIntoEditor(_openTabs[activeIndex].GraphResource);
ApplyVariablesPanelState(activeIndex);
}
else if (_openTabs.Count > 0)
{
SetCurrentTabWithoutLoading(0);
LoadGraphIntoEditor(_openTabs[0].GraphResource);
ApplyVariablesPanelState(0);
}
UpdateVisibility();
}
@@ -388,7 +452,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
private static string GetBaseFilePath(string resourcePath)
{
var separatorIndex = resourcePath.IndexOf("::", StringComparison.Ordinal);
int separatorIndex = resourcePath.IndexOf("::", StringComparison.Ordinal);
return separatorIndex >= 0 ? resourcePath[..separatorIndex] : resourcePath;
}
@@ -401,7 +465,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
{
if (IsSubResourcePath(path))
{
var basePath = GetBaseFilePath(path);
string basePath = GetBaseFilePath(path);
if (!ResourceLoader.Exists(basePath))
{
return null;
@@ -426,7 +490,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
private static StatescriptGraph? FindSubResourceGraph(Resource parentResource, string subResourcePath)
{
foreach (var propertyName in parentResource.GetPropertyList()
foreach (string? propertyName in parentResource.GetPropertyList()
.Select(p => p["name"].AsString()))
{
Variant value = parentResource.Get(propertyName);
@@ -455,7 +519,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return graph;
}
foreach (var propertyName in resource.GetPropertyList()
foreach (string? propertyName in resource.GetPropertyList()
.Select(p => p["name"].AsString()))
{
Variant value = resource.Get(propertyName);
@@ -479,11 +543,11 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
private static void SaveGraphResource(StatescriptGraph graph)
{
var path = graph.ResourcePath;
string path = graph.ResourcePath;
if (IsSubResourcePath(path))
{
var basePath = GetBaseFilePath(path);
string basePath = GetBaseFilePath(path);
Resource? parentResource = ResourceLoader.Load(basePath);
if (parentResource is not null)
{
@@ -505,15 +569,17 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return;
}
var paths = _serializedTabPaths;
var activeTab = _serializedActiveTab;
var varStates = _serializedVariablesStates;
var savedConnections = _serializedConnections;
var connectionCounts = _serializedConnectionCounts;
string[] paths = _serializedTabPaths;
int activeTab = _serializedActiveTab;
bool[]? varStates = _serializedVariablesStates;
string?[]? selectedVariables = _serializedSelectedVariables;
string[]? savedConnections = _serializedConnections;
int[]? connectionCounts = _serializedConnectionCounts;
_serializedTabPaths = null;
_serializedActiveTab = -1;
_serializedVariablesStates = null;
_serializedSelectedVariables = null;
_serializedConnections = null;
_serializedConnectionCounts = null;
@@ -530,8 +596,8 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
_tabBar.RemoveTab(0);
}
var skippedTabs = 0;
for (var i = 0; i < paths.Length; i++)
int skippedTabs = 0;
for (int i = 0; i < paths.Length; i++)
{
if (!ResourceLoader.Exists(paths[i]))
{
@@ -549,12 +615,17 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
graph.EnsureEntryNode();
var tab = new GraphTab(graph);
var currentTab = i - skippedTabs;
int currentTab = i - skippedTabs;
if (varStates is not null && currentTab < varStates.Length)
{
tab.VariablesPanelOpen = varStates[currentTab];
}
if (selectedVariables is not null && currentTab < selectedVariables.Length)
{
tab.SelectedVariableName = selectedVariables[currentTab];
}
_openTabs.Add(tab);
_tabBar.AddTab(graph.StatescriptName);
}
@@ -563,18 +634,18 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
if (savedConnections is not null && connectionCounts is not null)
{
var offset = 0;
for (var i = 0; i < _openTabs.Count && i < connectionCounts.Length; i++)
int offset = 0;
for (int i = 0; i < _openTabs.Count && i < connectionCounts.Length; i++)
{
StatescriptGraph graph = _openTabs[i].GraphResource;
graph.Connections.Clear();
for (var j = 0; j < connectionCounts[i] && offset < savedConnections.Length; j++, offset++)
for (int j = 0; j < connectionCounts[i] && offset < savedConnections.Length; j++, offset++)
{
var parts = savedConnections[offset].Split(',');
string[] parts = savedConnections[offset].Split(',');
if (parts.Length != 4
|| !int.TryParse(parts[1], out var outPort)
|| !int.TryParse(parts[3], out var inPort))
|| !int.TryParse(parts[1], out int outPort)
|| !int.TryParse(parts[3], out int inPort))
{
continue;
}
@@ -592,10 +663,17 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
if (activeTab >= 0 && activeTab < _openTabs.Count)
{
_tabBar.CurrentTab = activeTab;
_openTabs[activeTab].VariablesPanelOpen = _persistedVariablesPanelVisible;
SetCurrentTabWithoutLoading(activeTab);
LoadGraphIntoEditor(_openTabs[activeTab].GraphResource);
ApplyVariablesPanelState(activeTab);
}
else if (_openTabs.Count > 0)
{
SetCurrentTabWithoutLoading(0);
LoadGraphIntoEditor(_openTabs[0].GraphResource);
ApplyVariablesPanelState(0);
}
UpdateVisibility();
}
@@ -607,13 +685,15 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return;
}
DisposeCachedGraphVisuals(_openTabs[tabIndex]);
_openTabs.RemoveAt(tabIndex);
_tabBar.RemoveTab(tabIndex);
if (_openTabs.Count > 0)
{
var newTab = Mathf.Min(tabIndex, _openTabs.Count - 1);
_tabBar.CurrentTab = newTab;
int newTab = Mathf.Min(tabIndex, _openTabs.Count - 1);
SetCurrentTabWithoutLoading(newTab);
LoadGraphIntoEditor(_openTabs[newTab].GraphResource);
ApplyVariablesPanelState(newTab);
}
@@ -820,7 +900,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
private void UpdateVisibility()
{
var hasOpenGraph = _openTabs.Count > 0;
bool hasOpenGraph = _openTabs.Count > 0;
if (_splitContainer is not null)
{
@@ -855,22 +935,37 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return;
}
var wasLoading = _isLoadingGraph;
GraphTab? tab = FindTab(graph);
if (tab is null)
{
return;
}
bool wasLoading = _isLoadingGraph;
_isLoadingGraph = true;
ClearGraphEditor();
DetachVisibleGraphNodes();
_graphEdit.Zoom = graph.Zoom;
UpdateNextNodeId(graph);
foreach (StatescriptNode nodeResource in graph.Nodes)
tab.CachedGraphNodes.RemoveAll(x => !IsInstanceValid(x));
if (tab.CachedGraphNodes.Count == 0)
{
var graphNode = new StatescriptGraphNode();
_graphEdit.AddChild(graphNode);
graphNode.Initialize(nodeResource, graph);
graphNode.SetUndoRedo(_undoRedo);
foreach (StatescriptNode nodeResource in graph.Nodes)
{
StatescriptGraphNode graphNode = AddGraphNodeVisual(nodeResource, graph);
tab.CachedGraphNodes.Add(graphNode);
}
}
else
{
AttachCachedGraphNodes(tab);
}
_graphEdit.ClearConnections();
foreach (StatescriptConnection connection in graph.Connections)
{
@@ -881,6 +976,8 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
connection.InputPort);
}
ReapplyCurrentNodeHighlights();
_isLoadingGraph = wasLoading;
_ = ApplyScrollNextFrame(graph.ScrollOffset);
@@ -912,21 +1009,137 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
{
if (node is StatescriptGraphNode graphNode)
{
graphNode.OnBeforeSerialize();
RemoveGraphNodeVisual(graphNode);
}
}
}
private void DetachVisibleGraphNodes()
{
if (_graphEdit is null)
{
return;
}
_graphEdit.ClearConnections();
var toDetach = new List<Node>();
toDetach.AddRange(_graphEdit.GetChildren().Where(x => x is GraphNode));
foreach (Node node in toDetach)
{
_graphEdit.RemoveChild(node);
}
}
private StatescriptGraphNode AddGraphNodeVisual(StatescriptNode nodeResource, StatescriptGraph graph)
{
var graphNode = new StatescriptGraphNode();
_graphEdit!.AddChild(graphNode);
graphNode.Initialize(nodeResource, graph);
graphNode.SetUndoRedo(_undoRedo);
graphNode.PropertyBindingChanged += OnGraphNodePropertyBindingChanged;
return graphNode;
}
private void AttachCachedGraphNodes(GraphTab tab)
{
if (_graphEdit is null)
{
return;
}
foreach (StatescriptGraphNode graphNode in tab.CachedGraphNodes)
{
if (!IsInstanceValid(graphNode))
{
continue;
}
_graphEdit.RemoveChild(node);
node.Free();
if (graphNode.GetParent() is Node parent)
{
parent.RemoveChild(graphNode);
}
_graphEdit.AddChild(graphNode);
}
}
private void RemoveGraphNodeVisual(StatescriptGraphNode graphNode)
{
graphNode.PropertyBindingChanged -= OnGraphNodePropertyBindingChanged;
graphNode.OnBeforeSerialize();
if (graphNode.GetParent() is Node parent)
{
parent.RemoveChild(graphNode);
}
for (int i = 0; i < _openTabs.Count; i++)
{
_openTabs[i].CachedGraphNodes.Remove(graphNode);
}
graphNode.Free();
}
private void DisposeCachedGraphVisuals()
{
for (int i = 0; i < _openTabs.Count; i++)
{
DisposeCachedGraphVisuals(_openTabs[i]);
}
}
private void DisposeCachedGraphVisuals(GraphTab tab)
{
for (int i = tab.CachedGraphNodes.Count - 1; i >= 0; i--)
{
StatescriptGraphNode graphNode = tab.CachedGraphNodes[i];
if (!IsInstanceValid(graphNode))
{
tab.CachedGraphNodes.RemoveAt(i);
continue;
}
RemoveGraphNodeVisual(graphNode);
}
}
private GraphTab? FindTab(StatescriptGraph graph)
{
for (int i = 0; i < _openTabs.Count; i++)
{
if (_openTabs[i].GraphResource == graph || (!string.IsNullOrEmpty(graph.ResourcePath)
&& _openTabs[i].ResourcePath == graph.ResourcePath))
{
return _openTabs[i];
}
}
return null;
}
private void SetCurrentTabWithoutLoading(int tabIndex)
{
if (_tabBar is null)
{
return;
}
bool wasLoading = _isLoadingGraph;
_isLoadingGraph = true;
_tabBar.CurrentTab = tabIndex;
_isLoadingGraph = wasLoading;
}
private void UpdateNextNodeId(StatescriptGraph graph)
{
var maxId = 0;
foreach (var nodeId in graph.Nodes.Select(x => x.NodeId))
int maxId = 0;
foreach (string? nodeId in graph.Nodes.Select(x => x.NodeId))
{
if (nodeId.StartsWith("node_", StringComparison.InvariantCultureIgnoreCase)
&& int.TryParse(nodeId["node_".Length..], out var id)
&& int.TryParse(nodeId["node_".Length..], out int id)
&& id >= maxId)
{
maxId = id + 1;
@@ -946,7 +1159,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return;
}
for (var i = 0; i < _openTabs.Count; i++)
for (int i = 0; i < _openTabs.Count; i++)
{
_tabBar.SetTabTitle(i, _openTabs[i].GraphResource.StatescriptName);
}
@@ -990,7 +1203,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return;
}
for (var i = 0; i < _openTabs.Count; i++)
for (int i = 0; i < _openTabs.Count; i++)
{
if (i == newTabIndex)
{
@@ -1007,6 +1220,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
if (_variablePanel is not null)
{
_openTabs[i].VariablesPanelOpen = _variablePanel.Visible;
_openTabs[i].SelectedVariableName = _variablePanel.GetSelectedVariableName();
}
return;
@@ -1098,10 +1312,16 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
_variablePanel.Visible = pressed;
var current = _tabBar.CurrentTab;
int current = _tabBar.CurrentTab;
if (current >= 0 && current < _openTabs.Count)
{
_openTabs[current].VariablesPanelOpen = pressed;
_persistedVariablesPanelVisible = pressed;
if (!pressed)
{
_openTabs[current].SelectedVariableName = null;
}
}
if (pressed)
@@ -1110,8 +1330,16 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
if (graph is not null)
{
_variablePanel.SetGraph(graph);
if (current >= 0 && current < _openTabs.Count)
{
_variablePanel.RestoreSelectedVariable(_openTabs[current].SelectedVariableName);
}
}
}
else
{
_variablePanel.ClearSelectedVariable();
}
}
private void OnGraphVariablesChanged()
@@ -1122,6 +1350,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return;
}
InvalidateCachedGraphVisuals(graph);
LoadGraphIntoEditor(graph);
}
@@ -1131,29 +1360,125 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
}
private void OnVariableHighlightChanged(string? variableName)
{
int current = _tabBar?.CurrentTab ?? -1;
if (current >= 0 && current < _openTabs.Count)
{
_openTabs[current].SelectedVariableName = variableName;
}
if (!string.IsNullOrEmpty(variableName))
{
SharedVariableHighlightState.SetSelection(null, null);
}
ReapplyCurrentNodeHighlights();
}
private void OnGraphNodePropertyBindingChanged()
{
ReapplyCurrentNodeHighlights();
}
private void SubscribeSharedVariableHighlightState()
{
if (_sharedVariableHighlightSubscribed)
{
return;
}
SharedVariableHighlightState.Changed += OnSharedVariableHighlightChanged;
_sharedVariableHighlightSubscribed = true;
}
private void UnsubscribeSharedVariableHighlightState()
{
if (!_sharedVariableHighlightSubscribed)
{
return;
}
SharedVariableHighlightState.Changed -= OnSharedVariableHighlightChanged;
_sharedVariableHighlightSubscribed = false;
}
private void OnSharedVariableHighlightChanged()
{
if (SharedVariableHighlightState.HasAnySelection())
{
ClearGraphVariableSelections();
return;
}
ApplySharedVariableHighlightToNodes();
}
private void ApplySharedVariableHighlightToNodes()
{
if (_graphEdit is null)
{
return;
}
bool hasSelection = SharedVariableHighlightState.TryGetActiveSelection(
out string sharedVariableSetPath,
out string sharedVariableName);
foreach (Node child in _graphEdit.GetChildren())
{
if (child is StatescriptGraphNode graphNode)
{
graphNode.SetHighlightedSharedVariable(
hasSelection ? sharedVariableSetPath : null,
hasSelection ? sharedVariableName : null);
}
}
}
private void ReapplyCurrentNodeHighlights()
{
if (_graphEdit is null)
{
return;
}
string? selectedVariableName = null;
int current = _tabBar?.CurrentTab ?? -1;
if (current >= 0 && current < _openTabs.Count)
{
selectedVariableName = _openTabs[current].SelectedVariableName;
}
foreach (Node child in _graphEdit.GetChildren())
{
if (child is StatescriptGraphNode graphNode)
{
graphNode.SetHighlightedVariable(variableName);
graphNode.SetHighlightedVariable(selectedVariableName);
}
}
ApplySharedVariableHighlightToNodes();
}
private void ClearGraphVariableSelections()
{
for (int i = 0; i < _openTabs.Count; i++)
{
_openTabs[i].SelectedVariableName = null;
}
if (_variablePanel is not null)
{
_variablePanel.ClearSelectedVariable();
return;
}
ReapplyCurrentNodeHighlights();
}
private void EnsureVariablesPanelVisible()
{
if (_variablePanel is null || _variablesToggleButton is null || _openTabs.Count == 0)
{
return;
}
if (_variablePanel.Visible)
if (_variablePanel is null || _variablesToggleButton is null)
{
return;
}
@@ -1161,7 +1486,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
_variablePanel.Visible = true;
_variablesToggleButton.SetPressedNoSignal(true);
var current = _tabBar?.CurrentTab ?? -1;
int current = _tabBar?.CurrentTab ?? -1;
if (current >= 0 && current < _openTabs.Count)
{
_openTabs[current].VariablesPanelOpen = true;
@@ -1174,23 +1499,44 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
}
}
private void InvalidateCachedGraphVisuals(StatescriptGraph graph)
{
GraphTab? tab = FindTab(graph);
if (tab is null)
{
return;
}
for (int i = tab.CachedGraphNodes.Count - 1; i >= 0; i--)
{
StatescriptGraphNode graphNode = tab.CachedGraphNodes[i];
if (!IsInstanceValid(graphNode))
{
tab.CachedGraphNodes.RemoveAt(i);
continue;
}
RemoveGraphNodeVisual(graphNode);
}
}
private void OnFilesystemChanged()
{
for (var i = 0; i < _openTabs.Count; i++)
for (int i = 0; i < _openTabs.Count; i++)
{
_openTabs[i].UpdateCachedPathIfMissing();
}
for (var i = _openTabs.Count - 1; i >= 0; i--)
for (int i = _openTabs.Count - 1; i >= 0; i--)
{
var path = _openTabs[i].ResourcePath;
string path = _openTabs[i].ResourcePath;
if (string.IsNullOrEmpty(path))
{
continue;
}
var filePath = GetBaseFilePath(path);
string filePath = GetBaseFilePath(path);
if (!FileAccess.FileExists(filePath))
{
CloseTabByIndex(i);
@@ -1208,14 +1554,50 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return;
}
var shouldShow = _openTabs[tabIndex].VariablesPanelOpen;
bool shouldShow = _openTabs[tabIndex].VariablesPanelOpen;
if ((_tabBar?.CurrentTab ?? -1) == tabIndex)
{
shouldShow = _persistedVariablesPanelVisible;
_openTabs[tabIndex].VariablesPanelOpen = shouldShow;
}
_variablePanel.Visible = shouldShow;
_variablesToggleButton.SetPressedNoSignal(shouldShow);
if (shouldShow)
{
_variablePanel.SetGraph(_openTabs[tabIndex].GraphResource);
_variablePanel.RestoreSelectedVariable(_openTabs[tabIndex].SelectedVariableName);
}
else
{
_variablePanel.ClearSelectedVariable();
}
}
private void PersistCurrentVariablePanelState()
{
if (_variablePanel is null || _tabBar is null)
{
return;
}
int current = _tabBar.CurrentTab;
if (current < 0 || current >= _openTabs.Count)
{
return;
}
_openTabs[current].VariablesPanelOpen = _variablePanel.Visible;
_openTabs[current].SelectedVariableName = _variablePanel.GetSelectedVariableName();
_persistedVariablesPanelVisible = _variablePanel.Visible;
}
private GraphTab[] GetPersistedTabs()
{
PersistCurrentVariablePanelState();
return [.. _openTabs.Where(x => !string.IsNullOrEmpty(x.ResourcePath))];
}
private void OnGraphEditGuiInput(InputEvent @event)
@@ -1350,7 +1732,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return -1;
}
for (var i = 0; i < graphNode.GetChildCount(); i++)
for (int i = 0; i < graphNode.GetChildCount(); i++)
{
if (graphNode.IsSlotEnabledLeft(i))
{
@@ -1374,7 +1756,7 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
return -1;
}
for (var i = 0; i < graphNode.GetChildCount(); i++)
for (int i = 0; i < graphNode.GetChildCount(); i++)
{
if (graphNode.IsSlotEnabledRight(i))
{
@@ -1391,12 +1773,16 @@ public partial class StatescriptGraphEditorDock : EditorDock, ISerializationList
public StatescriptGraph GraphResource { get; }
public List<StatescriptGraphNode> CachedGraphNodes { get; } = [];
public string ResourcePath => !string.IsNullOrEmpty(GraphResource?.ResourcePath)
? GraphResource.ResourcePath
: _cachedPath;
public bool VariablesPanelOpen { get; set; }
public string? SelectedVariableName { get; set; }
public GraphTab(StatescriptGraph graphResource)
{
GraphResource = graphResource;

View File

@@ -1,6 +1,10 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Gamesmiths.Forge.Godot.Resources.Statescript;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
@@ -9,17 +13,48 @@ namespace Gamesmiths.Forge.Godot.Editor.Statescript;
public partial class StatescriptGraphNode
{
private bool ReferencesVariable(string variableName)
private readonly Dictionary<OptionButton, StyleBoxFlat> _baseDropdownStyles = [];
private StyleBoxFlat? _basePanelStyle;
private StyleBoxFlat? _baseSelectedPanelStyle;
private static bool ResolverReferencesVariable(StatescriptResolverResource? resolver, string variableName)
{
if (NodeResource is null)
if (resolver is null || string.IsNullOrEmpty(variableName))
{
return false;
}
foreach (StatescriptNodeProperty binding in NodeResource.PropertyBindings)
var visited = new HashSet<nint>();
return ResolverReferencesVariableRecursive(resolver, variableName, visited);
}
private static bool ResolverReferencesVariableRecursive(
StatescriptResolverResource resolver,
string variableName,
HashSet<nint> visited)
{
nint instanceId = (nint)resolver.GetInstanceId();
if (!visited.Add(instanceId))
{
if (binding.Resolver is VariableResolverResource varRes
&& varRes.VariableName == variableName)
return false;
}
if (resolver is VariableResolverResource variableResolver
&& string.Equals(variableResolver.VariableName, variableName, StringComparison.Ordinal))
{
return true;
}
foreach (PropertyInfo property in resolver.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if (!property.CanRead || !typeof(StatescriptResolverResource).IsAssignableFrom(property.PropertyType))
{
continue;
}
if (property.GetValue(resolver) is StatescriptResolverResource nestedResolver
&& ResolverReferencesVariableRecursive(nestedResolver, variableName, visited))
{
return true;
}
@@ -28,23 +63,103 @@ public partial class StatescriptGraphNode
return false;
}
private void ApplyHighlightBorder()
private static bool ResolverReferencesSharedVariable(
StatescriptResolverResource? resolver,
string sharedVariableSetPath,
string variableName)
{
if (_isHighlighted)
if (resolver is null
|| string.IsNullOrEmpty(sharedVariableSetPath)
|| string.IsNullOrEmpty(variableName))
{
if (GetThemeStylebox("panel") is not StyleBoxFlat baseStyle)
return false;
}
var visited = new HashSet<nint>();
return ResolverReferencesSharedVariableRecursive(resolver, sharedVariableSetPath, variableName, visited);
}
private static bool ResolverReferencesSharedVariableRecursive(
StatescriptResolverResource resolver,
string sharedVariableSetPath,
string variableName,
HashSet<nint> visited)
{
nint instanceId = (nint)resolver.GetInstanceId();
if (!visited.Add(instanceId))
{
return false;
}
if (resolver is SharedVariableResolverResource sharedVariableResolver
&& string.Equals(sharedVariableResolver.SharedVariableSetPath, sharedVariableSetPath, StringComparison.Ordinal)
&& string.Equals(sharedVariableResolver.VariableName, variableName, StringComparison.Ordinal))
{
return true;
}
foreach (PropertyInfo property in resolver.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if (!property.CanRead || !typeof(StatescriptResolverResource).IsAssignableFrom(property.PropertyType))
{
return;
continue;
}
var highlightStyle = (StyleBoxFlat)baseStyle.Duplicate();
if (property.GetValue(resolver) is StatescriptResolverResource nestedResolver
&& ResolverReferencesSharedVariableRecursive(
nestedResolver,
sharedVariableSetPath,
variableName,
visited))
{
return true;
}
}
return false;
}
private bool ReferencesVariable(string variableName)
{
if (NodeResource is null)
{
return false;
}
return NodeResource.PropertyBindings.Any(binding => ResolverReferencesVariable(binding.Resolver, variableName));
}
private bool ReferencesSharedVariable(string? sharedVariableSetPath, string? variableName)
{
if (NodeResource is null
|| string.IsNullOrEmpty(sharedVariableSetPath)
|| string.IsNullOrEmpty(variableName))
{
return false;
}
return NodeResource.PropertyBindings.Any(binding =>
ResolverReferencesSharedVariable(binding.Resolver, sharedVariableSetPath, variableName));
}
private void ApplyHighlightBorder()
{
EnsureBasePanelStylesStored();
if (_basePanelStyle is null || _baseSelectedPanelStyle is null)
{
return;
}
if (_isHighlighted)
{
StyleBoxFlat baseStyle = _basePanelStyle;
var highlightStyle = (StyleBoxFlat)baseStyle.Duplicate();
highlightStyle.BorderColor = _highlightColor;
highlightStyle.BorderWidthTop = 2;
highlightStyle.BorderWidthBottom = 2;
highlightStyle.BorderWidthLeft = 2;
highlightStyle.BorderWidthRight = 2;
highlightStyle.BgColor = baseStyle.BgColor.Lerp(_highlightColor, 0.15f);
AddThemeStyleboxOverride("panel", highlightStyle);
@@ -52,10 +167,8 @@ public partial class StatescriptGraphNode
}
else
{
RemoveThemeStyleboxOverride("panel");
RemoveThemeStyleboxOverride("panel_selected");
ApplyBottomPadding();
AddThemeStyleboxOverride("panel", (StyleBoxFlat)_basePanelStyle.Duplicate());
AddThemeStyleboxOverride("panel_selected", (StyleBoxFlat)_baseSelectedPanelStyle.Duplicate());
}
}
@@ -76,48 +189,107 @@ public partial class StatescriptGraphNode
{
HighlightLabelIfMatches(label);
}
else if (child is FoldableContainer foldable)
{
HighlightFoldableSummaryBadgeIfPresent(foldable);
}
else if (child is PanelContainer badge && badge.HasMeta("forge_inline_summary_badge_kind"))
{
HighlightSummaryBadgeIfMatches(badge);
}
UpdateHighlightsRecursive(child);
}
}
private void HighlightFoldableSummaryBadgeIfPresent(FoldableContainer foldable)
{
if (!InlineConstantSummaryFormatter.TryGetSummaryBadgeForHighlighting(foldable, out PanelContainer? badge))
{
return;
}
HighlightSummaryBadgeIfMatches(badge);
}
private void HighlightOptionButtonIfMatches(OptionButton dropdown)
{
if (!dropdown.HasMeta("is_variable_dropdown"))
EnsureBaseDropdownStyleStored(dropdown);
if (!dropdown.HasMeta("is_variable_dropdown") && !dropdown.HasMeta("is_shared_variable_dropdown"))
{
return;
}
if (string.IsNullOrEmpty(_highlightedVariableName))
if (!_baseDropdownStyles.TryGetValue(dropdown, out StyleBoxFlat? baseStyle))
{
dropdown.RemoveThemeStyleboxOverride("normal");
return;
}
var selectedIdx = dropdown.Selected;
if (string.IsNullOrEmpty(_highlightedVariableName)
&& (string.IsNullOrEmpty(_highlightedSharedVariableSetPath)
|| string.IsNullOrEmpty(_highlightedSharedVariableName)))
{
dropdown.AddThemeStyleboxOverride("normal", (StyleBoxFlat)baseStyle.Duplicate());
return;
}
int selectedIdx = dropdown.Selected;
if (selectedIdx < 0)
{
dropdown.RemoveThemeStyleboxOverride("normal");
dropdown.AddThemeStyleboxOverride("normal", (StyleBoxFlat)baseStyle.Duplicate());
return;
}
var selectedText = dropdown.GetItemText(selectedIdx);
if (selectedText == _highlightedVariableName)
string selectedText = dropdown.GetItemText(selectedIdx);
bool isSharedVariableDropdown = dropdown.HasMeta("is_shared_variable_dropdown");
string dropdownSetPath = dropdown.HasMeta("shared_variable_set_path")
? dropdown.GetMeta("shared_variable_set_path").AsString()
: string.Empty;
bool isMatch = isSharedVariableDropdown
? !string.IsNullOrEmpty(_highlightedSharedVariableSetPath)
&& !string.IsNullOrEmpty(_highlightedSharedVariableName)
&& selectedText == _highlightedSharedVariableName
&& dropdownSetPath == _highlightedSharedVariableSetPath
: selectedText == _highlightedVariableName;
if (isMatch)
{
if (dropdown.GetThemeStylebox("normal") is not StyleBoxFlat baseStyle)
{
return;
}
var highlightStyle = (StyleBoxFlat)baseStyle.Duplicate();
highlightStyle.BgColor = baseStyle.BgColor.Lerp(_highlightColor, 0.25f);
dropdown.AddThemeStyleboxOverride("normal", highlightStyle);
}
else
{
dropdown.RemoveThemeStyleboxOverride("normal");
dropdown.AddThemeStyleboxOverride("normal", (StyleBoxFlat)baseStyle.Duplicate());
}
}
private void EnsureBasePanelStylesStored()
{
if (_basePanelStyle is null && GetThemeStylebox("panel") is StyleBoxFlat panelStyle)
{
_basePanelStyle = (StyleBoxFlat)panelStyle.Duplicate();
}
if (_baseSelectedPanelStyle is null
&& GetThemeStylebox("panel_selected") is StyleBoxFlat selectedPanelStyle)
{
_baseSelectedPanelStyle = (StyleBoxFlat)selectedPanelStyle.Duplicate();
}
}
private void EnsureBaseDropdownStyleStored(OptionButton dropdown)
{
if (_baseDropdownStyles.ContainsKey(dropdown))
{
return;
}
if (dropdown.GetThemeStylebox("normal") is StyleBoxFlat baseStyle)
{
_baseDropdownStyles[dropdown] = (StyleBoxFlat)baseStyle.Duplicate();
}
}
@@ -145,5 +317,75 @@ public partial class StatescriptGraphNode
label.RemoveMeta("is_highlight_colored");
}
}
private void HighlightSummaryBadgeIfMatches(PanelContainer badge)
{
if (badge.GetNodeOrNull<Label>("Row/Text") is not Label textLabel
|| badge.GetNodeOrNull<Label>("Row/Icon") is not Label iconLabel)
{
return;
}
if (!badge.HasMeta("forge_inline_summary_badge_base_stylebox")
&& badge.GetThemeStylebox("panel") is StyleBoxFlat baseStyle)
{
badge.SetMeta("forge_inline_summary_badge_base_stylebox", Variant.From(baseStyle.Duplicate()));
}
if (!badge.HasMeta("forge_inline_summary_badge_base_stylebox")
|| badge.GetMeta("forge_inline_summary_badge_base_stylebox").Obj is not StyleBoxFlat storedBase)
{
return;
}
string propagatedVariableName = badge.HasMeta("forge_inline_summary_badge_highlight_variable")
? badge.GetMeta("forge_inline_summary_badge_highlight_variable").AsString()
: string.Empty;
string propagatedSharedVariableName = badge.HasMeta("forge_inline_summary_badge_highlight_shared_variable")
? badge.GetMeta("forge_inline_summary_badge_highlight_shared_variable").AsString()
: string.Empty;
string propagatedSharedVariableSetPath = badge.HasMeta("forge_inline_summary_badge_highlight_shared_set_path")
? badge.GetMeta("forge_inline_summary_badge_highlight_shared_set_path").AsString()
: string.Empty;
bool isMatch = !string.IsNullOrEmpty(_highlightedVariableName)
&& (textLabel.Text == _highlightedVariableName
|| propagatedVariableName == _highlightedVariableName);
isMatch = isMatch
|| (!string.IsNullOrEmpty(_highlightedSharedVariableSetPath)
&& !string.IsNullOrEmpty(_highlightedSharedVariableName)
&& (textLabel.Text == _highlightedSharedVariableName
|| propagatedSharedVariableName == _highlightedSharedVariableName)
&& propagatedSharedVariableSetPath == _highlightedSharedVariableSetPath);
badge.SetMeta("forge_inline_summary_badge_selected_variable", Variant.From(_highlightedVariableName ?? string.Empty));
badge.SetMeta(
"forge_inline_summary_badge_selected_shared_set_path",
Variant.From(_highlightedSharedVariableSetPath ?? string.Empty));
badge.SetMeta(
"forge_inline_summary_badge_selected_shared_variable",
Variant.From(_highlightedSharedVariableName ?? string.Empty));
var style = (StyleBoxFlat)storedBase.Duplicate();
if (isMatch)
{
style.BorderWidthLeft = Math.Max(style.BorderWidthLeft, 2);
style.BorderWidthTop = Math.Max(style.BorderWidthTop, 2);
style.BorderWidthRight = Math.Max(style.BorderWidthRight, 2);
style.BorderWidthBottom = Math.Max(style.BorderWidthBottom, 2);
style.BorderColor = _highlightColor;
style.BgColor = _highlightColor;
iconLabel.AddThemeColorOverride("font_color", Colors.Black);
textLabel.AddThemeColorOverride("font_color", Colors.Black);
}
else
{
iconLabel.RemoveThemeColorOverride("font_color");
textLabel.RemoveThemeColorOverride("font_color");
}
badge.AddThemeStyleboxOverride("panel", style);
}
}
#endif

View File

@@ -24,23 +24,43 @@ public partial class StatescriptGraphNode
}
var container = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
sectionContainer.AddChild(container);
var key = new PropertySlotKey(StatescriptPropertyDirection.Input, index);
string baseTitle = $"{propInfo.Label}:";
var propertyFoldable = new FoldableContainer
{
Title = baseTitle,
Folded = GetFoldState(GetInputPropertyFoldKey(index), true),
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
_foldableKeys[propertyFoldable] = GetInputPropertyFoldKey(index);
_inputPropertyFoldables[key] = new InputPropertyFoldableContext(propertyFoldable, baseTitle);
propertyFoldable.FoldingChanged += OnSectionFoldingChanged;
sectionContainer.AddChild(propertyFoldable);
propertyFoldable.AddChild(container);
var headerRow = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
container.AddChild(headerRow);
var nameLabel = new Label
{
Text = propInfo.Label,
CustomMinimumSize = new Vector2(60, 0),
};
nameLabel.AddThemeColorOverride("font_color", _inputPropertyColor);
headerRow.AddChild(nameLabel);
List<Func<NodeEditorProperty>> resolverFactories =
StatescriptResolverRegistry.GetCompatibleFactories(propInfo.ExpectedType);
if (propInfo.IsArray)
{
resolverFactories.RemoveAll(factory =>
{
return StatescriptResolverRegistry.GetResolverTypeId(factory) != "ArrayVariable";
});
}
else
{
resolverFactories.RemoveAll(factory =>
{
return StatescriptResolverRegistry.GetResolverTypeId(factory) == "ArrayVariable";
});
}
if (resolverFactories.Count == 0)
{
var errorLabel = new Label
@@ -49,7 +69,8 @@ public partial class StatescriptGraphNode
};
errorLabel.AddThemeColorOverride("font_color", Colors.Red);
headerRow.AddChild(errorLabel);
container.AddChild(errorLabel);
UpdateInputPropertyFoldableTitle(key);
return;
}
@@ -61,20 +82,18 @@ public partial class StatescriptGraphNode
foreach (Func<NodeEditorProperty> factory in resolverFactories)
{
using NodeEditorProperty temp = factory();
resolverDropdown.AddItem(temp.DisplayName);
resolverDropdown.AddItem(StatescriptResolverRegistry.GetDisplayName(factory));
}
StatescriptNodeProperty? binding = FindBinding(StatescriptPropertyDirection.Input, index);
var selectedIndex = 0;
int selectedIndex = 0;
if (binding?.Resolver is not null)
{
for (var i = 0; i < resolverFactories.Count; i++)
for (int i = 0; i < resolverFactories.Count; i++)
{
using NodeEditorProperty temp = resolverFactories[i]();
if (temp.ResolverTypeId == GetResolverTypeId(binding.Resolver))
if (StatescriptResolverRegistry.GetResolverTypeId(resolverFactories[i])
== GetResolverTypeId(binding.Resolver))
{
selectedIndex = i;
break;
@@ -83,16 +102,7 @@ public partial class StatescriptGraphNode
}
else
{
for (var i = 0; i < resolverFactories.Count; i++)
{
using NodeEditorProperty temp = resolverFactories[i]();
if (temp.ResolverTypeId == "Variant")
{
selectedIndex = i;
break;
}
}
selectedIndex = StatescriptResolverRegistry.GetDefaultFactoryIndex(resolverFactories, propInfo.IsArray);
}
resolverDropdown.Selected = selectedIndex;
@@ -101,7 +111,6 @@ public partial class StatescriptGraphNode
var editorContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
container.AddChild(editorContainer);
var key = new PropertySlotKey(StatescriptPropertyDirection.Input, index);
_inputPropertyContexts[key] = new InputPropertyContext(resolverFactories, propInfo, editorContainer);
ShowResolverEditorUI(
@@ -113,8 +122,11 @@ public partial class StatescriptGraphNode
index,
propInfo.IsArray);
var capturedIndex = index;
resolverDropdown.ItemSelected += selectedItem => OnInputResolverDropdownItemSelected(selectedItem, capturedIndex);
UpdateInputPropertyFoldableTitle(key);
int capturedIndex = index;
resolverDropdown.ItemSelected +=
selectedItem => OnInputResolverDropdownItemSelected(selectedItem, capturedIndex);
}
private void OnInputResolverDropdownItemSelected(long x, int index)
@@ -155,24 +167,29 @@ public partial class StatescriptGraphNode
SaveResolverEditor(editor, StatescriptPropertyDirection.Input, index);
}
UpdateInputPropertyFoldableTitle(key);
StatescriptNodeProperty? updated = FindBinding(StatescriptPropertyDirection.Input, index);
var newResolver = updated?.Resolver?.Duplicate() as StatescriptResolverResource;
if (_undoRedo is not null)
{
_undoRedo.CreateAction("Change Resolver Type", customContext: _graph);
_undoRedo.AddDoMethod(
this,
MethodName.ApplyResolverBinding,
(int)StatescriptPropertyDirection.Input,
index,
newResolver ?? new StatescriptResolverResource());
Variant.From(newResolver));
_undoRedo.AddUndoMethod(
this,
MethodName.ApplyResolverBinding,
(int)StatescriptPropertyDirection.Input,
index,
oldResolver ?? new StatescriptResolverResource());
Variant.From(oldResolver));
_undoRedo.CommitAction(false);
}
@@ -212,12 +229,12 @@ public partial class StatescriptGraphNode
}
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)
{
@@ -233,14 +250,15 @@ public partial class StatescriptGraphNode
if (binding is null)
{
var variableName = _graph.Variables[selectedIndex].VariableName;
string variableName = _graph.Variables[selectedIndex].VariableName;
EnsureBinding(StatescriptPropertyDirection.Output, index).Resolver =
new VariableResolverResource { VariableName = variableName };
}
}
var capturedIndex = index;
variableDropdown.ItemSelected += selectedItem => OnOutputVariableDropdownItemSelected(selectedItem, capturedIndex);
int capturedIndex = index;
variableDropdown.ItemSelected +=
selectedItem => OnOutputVariableDropdownItemSelected(selectedItem, capturedIndex);
hBox.AddChild(variableDropdown);
}
@@ -255,25 +273,28 @@ public partial class StatescriptGraphNode
var oldResolver = FindBinding(StatescriptPropertyDirection.Output, index)?.Resolver?.Duplicate()
as StatescriptResolverResource;
var variableName = _graph.Variables[(int)x].VariableName;
string variableName = _graph.Variables[(int)x].VariableName;
var newResolver = new VariableResolverResource { VariableName = variableName };
EnsureBinding(StatescriptPropertyDirection.Output, index).Resolver = newResolver;
if (_undoRedo is not null)
{
_undoRedo.CreateAction("Change Output Variable", customContext: _graph);
_undoRedo.AddDoMethod(
this,
MethodName.ApplyResolverBinding,
(int)StatescriptPropertyDirection.Output,
index,
(StatescriptResolverResource)newResolver.Duplicate());
_undoRedo.AddUndoMethod(
this,
MethodName.ApplyResolverBinding,
(int)StatescriptPropertyDirection.Output,
index,
oldResolver ?? new StatescriptResolverResource());
Variant.From(oldResolver));
_undoRedo.CommitAction(false);
}
@@ -295,6 +316,7 @@ public partial class StatescriptGraphNode
}
NodeEditorProperty resolverEditor = factory();
resolverEditor.ConfigureAllowedExpectedTypes(expectedType);
var key = new PropertySlotKey(direction, propertyIndex);
@@ -333,18 +355,21 @@ public partial class StatescriptGraphNode
if (_undoRedo is not null)
{
_undoRedo.CreateAction("Change Node Property", customContext: _graph);
_undoRedo.AddDoMethod(
this,
MethodName.ApplyResolverBinding,
(int)direction,
propertyIndex,
newResolver ?? new StatescriptResolverResource());
Variant.From(newResolver));
_undoRedo.AddUndoMethod(
this,
MethodName.ApplyResolverBinding,
(int)direction,
propertyIndex,
oldResolver ?? new StatescriptResolverResource());
Variant.From(oldResolver));
_undoRedo.CommitAction(false);
}

View File

@@ -19,6 +19,7 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
{
private const string FoldInputKey = "_fold_input";
private const string FoldOutputKey = "_fold_output";
private const string FoldInputPropertyKeyPrefix = "_fold_input_property_";
private const string CustomWidthKey = "_custom_width";
private static readonly Color _entryColor = new(0x2a4a8dff);
@@ -34,6 +35,7 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
private readonly Dictionary<PropertySlotKey, NodeEditorProperty> _activeResolverEditors = [];
private readonly Dictionary<FoldableContainer, string> _foldableKeys = [];
private readonly Dictionary<PropertySlotKey, InputPropertyFoldableContext> _inputPropertyFoldables = [];
private StatescriptNodeDiscovery.NodeTypeInfo? _typeInfo;
private StatescriptGraph? _graph;
@@ -42,6 +44,8 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
private bool _resizeConnected;
private float _widthBeforeResize;
private string? _highlightedVariableName;
private string? _highlightedSharedVariableSetPath;
private string? _highlightedSharedVariableName;
private bool _isHighlighted;
/// <summary>
@@ -79,9 +83,14 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
public void SetHighlightedVariable(string? variableName)
{
_highlightedVariableName = variableName;
_isHighlighted = !string.IsNullOrEmpty(variableName) && ReferencesVariable(variableName!);
ApplyHighlightBorder();
UpdateChildHighlights();
RefreshHighlightState();
}
public void SetHighlightedSharedVariable(string? sharedVariableSetPath, string? variableName)
{
_highlightedSharedVariableSetPath = sharedVariableSetPath;
_highlightedSharedVariableName = variableName;
RefreshHighlightState();
}
/// <summary>
@@ -95,6 +104,7 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
_graph = graph;
_activeResolverEditors.Clear();
_foldableKeys.Clear();
_inputPropertyFoldables.Clear();
Name = resource.NodeId;
Title = resource.Title;
@@ -119,6 +129,7 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
{
SetupNodeByType(resource.NodeType);
ApplyBottomPadding();
RefreshHighlightState();
return;
}
@@ -133,12 +144,14 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
}
ApplyBottomPadding();
RefreshHighlightState();
}
public void OnBeforeSerialize()
{
_inputPropertyContexts.Clear();
_foldableKeys.Clear();
_inputPropertyFoldables.Clear();
_activeCustomEditor?.Unbind();
_activeCustomEditor = null;
@@ -187,6 +200,16 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
return GetFoldState(key);
}
internal bool GetFoldStateInternal(string key, bool defaultValue)
{
return GetFoldState(key, defaultValue);
}
internal void SetFoldStateWithUndoInternal(string key, bool folded)
{
SetFoldStateWithUndo(key, folded);
}
internal StatescriptNodeProperty? FindBindingInternal(
StatescriptPropertyDirection direction,
int propertyIndex)
@@ -226,13 +249,15 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
MethodName.ApplyResolverBinding,
(int)direction,
propertyIndex,
newResolver ?? new StatescriptResolverResource());
Variant.From(newResolver));
_undoRedo.AddUndoMethod(
this,
MethodName.ApplyResolverBinding,
(int)direction,
propertyIndex,
oldResolver ?? new StatescriptResolverResource());
Variant.From(oldResolver));
_undoRedo.CommitAction(false);
}
@@ -253,6 +278,11 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
PropertyBindingChanged?.Invoke();
}
internal void UpdateInputPropertyFoldableTitlesInternal()
{
UpdateInputPropertyFoldableTitles();
}
private static string GetResolverTypeId(StatescriptResolverResource resolver)
{
return resolver.ResolverTypeId;
@@ -267,11 +297,16 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
}
}
private static string GetInputPropertyFoldKey(int propertyIndex)
{
return $"{FoldInputPropertyKeyPrefix}{propertyIndex}";
}
private void SetupFromTypeInfo(StatescriptNodeDiscovery.NodeTypeInfo typeInfo)
{
var maxSlots = Math.Max(typeInfo.InputPortLabels.Length, typeInfo.OutputPortLabels.Length);
int maxSlots = Math.Max(typeInfo.InputPortLabels.Length, typeInfo.OutputPortLabels.Length);
for (var slot = 0; slot < maxSlots; slot++)
for (int slot = 0; slot < maxSlots; slot++)
{
var hBox = new HBoxContainer();
hBox.AddThemeConstantOverride("separation", 16);
@@ -342,14 +377,14 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
{
if (typeInfo.InputPropertiesInfo.Length > 0)
{
var folded = GetFoldState(FoldInputKey);
bool folded = GetFoldState(FoldInputKey);
FoldableContainer inputContainer = AddPropertySectionDivider(
"Input Properties",
_inputPropertyColor,
FoldInputKey,
folded);
for (var i = 0; i < typeInfo.InputPropertiesInfo.Length; i++)
for (int i = 0; i < typeInfo.InputPropertiesInfo.Length; i++)
{
AddInputPropertyRow(typeInfo.InputPropertiesInfo[i], i, inputContainer);
}
@@ -357,14 +392,14 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
if (typeInfo.OutputVariablesInfo.Length > 0)
{
var folded = GetFoldState(FoldOutputKey);
bool folded = GetFoldState(FoldOutputKey);
FoldableContainer outputContainer = AddPropertySectionDivider(
"Output Variables",
_outputVariableColor,
FoldOutputKey,
folded);
for (var i = 0; i < typeInfo.OutputVariablesInfo.Length; i++)
for (int i = 0; i < typeInfo.OutputVariablesInfo.Length; i++)
{
AddOutputVariableRow(typeInfo.OutputVariablesInfo[i], i, outputContainer);
}
@@ -384,6 +419,7 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
{
Title = sectionTitle,
Folded = folded,
CustomMinimumSize = new Vector2(192, 0),
};
sectionContainer.AddThemeColorOverride("font_color", color);
@@ -400,24 +436,52 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
{
foreach (KeyValuePair<FoldableContainer, string> kvp in _foldableKeys.Where(kvp => IsInstanceValid(kvp.Key)))
{
var stored = GetFoldState(kvp.Value);
bool stored = GetFoldState(kvp.Value);
if (kvp.Key.Folded != stored)
{
SetFoldStateWithUndo(kvp.Value, kvp.Key.Folded);
}
}
UpdateInputPropertyFoldableTitles();
RefreshHighlightState();
ResetSize();
}
private void UpdateInputPropertyFoldableTitle(PropertySlotKey key)
{
if (!_inputPropertyFoldables.TryGetValue(key, out InputPropertyFoldableContext? context)
|| !IsInstanceValid(context.Foldable))
{
return;
}
_activeResolverEditors.TryGetValue(key, out NodeEditorProperty? editor);
InlineConstantSummaryFormatter.ApplyFoldableTitle(context.BaseTitle, context.Foldable, editor);
}
private void UpdateInputPropertyFoldableTitles()
{
foreach (PropertySlotKey key in _inputPropertyFoldables.Keys.ToArray())
{
UpdateInputPropertyFoldableTitle(key);
}
}
private bool GetFoldState(string key)
{
return GetFoldState(key, false);
}
private bool GetFoldState(string key, bool defaultValue)
{
if (NodeResource is not null && NodeResource.CustomData.TryGetValue(key, out Variant value))
{
return value.AsBool();
}
return false;
return defaultValue;
}
private void SetFoldState(string key, bool folded)
@@ -437,7 +501,7 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
return;
}
var oldFolded = GetFoldState(key);
bool oldFolded = GetFoldState(key);
if (oldFolded == folded)
{
@@ -478,12 +542,12 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
private void OnResizeEnd(Vector2 newSize)
{
var newWidth = CustomMinimumSize.X;
float newWidth = CustomMinimumSize.X;
if (_undoRedo is not null && NodeResource is not null
&& !Mathf.IsEqualApprox(_widthBeforeResize, newWidth))
{
var oldWidth = _widthBeforeResize;
float oldWidth = _widthBeforeResize;
_undoRedo.CreateAction("Resize Node", customContext: _graph);
_undoRedo.AddDoMethod(
@@ -512,7 +576,7 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
if (NodeResource is not null
&& NodeResource.CustomData.TryGetValue(CustomWidthKey, out Variant value))
{
var width = (float)value.AsDouble();
float width = (float)value.AsDouble();
if (width > 0)
{
@@ -534,7 +598,7 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
private void ApplyResolverBinding(
int directionInt,
int propertyIndex,
StatescriptResolverResource resolver)
Variant resolverVariant)
{
if (NodeResource is null)
{
@@ -543,7 +607,9 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
var direction = (StatescriptPropertyDirection)directionInt;
StatescriptNodeProperty binding = EnsureBinding(direction, propertyIndex);
binding.Resolver = resolver;
binding.Resolver = resolverVariant.VariantType == Variant.Type.Nil
? null
: resolverVariant.AsGodotObject() as StatescriptResolverResource;
RebuildNode();
}
@@ -560,6 +626,14 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
Size = new Vector2(Size.X, 0);
}
private void RefreshHighlightState()
{
_isHighlighted = (!string.IsNullOrEmpty(_highlightedVariableName) && ReferencesVariable(_highlightedVariableName))
|| ReferencesSharedVariable(_highlightedSharedVariableSetPath, _highlightedSharedVariableName);
ApplyHighlightBorder();
UpdateChildHighlights();
}
private StatescriptNodeProperty? FindBinding(
StatescriptPropertyDirection direction,
int propertyIndex)
@@ -607,7 +681,7 @@ public partial class StatescriptGraphNode : GraphNode, ISerializationListener
return;
}
for (var i = NodeResource.PropertyBindings.Count - 1; i >= 0; i--)
for (int i = NodeResource.PropertyBindings.Count - 1; i >= 0; i--)
{
StatescriptNodeProperty binding = NodeResource.PropertyBindings[i];

View File

@@ -49,7 +49,7 @@ internal static class StatescriptNodeDiscovery
{
IReadOnlyList<NodeTypeInfo> types = GetDiscoveredNodeTypes();
for (var i = 0; i < types.Count; i++)
for (int i = 0; i < types.Count; i++)
{
if (types[i].RuntimeTypeName == runtimeTypeName)
{
@@ -131,11 +131,11 @@ internal static class StatescriptNodeDiscovery
private static NodeTypeInfo BuildNodeTypeInfo(Type type, StatescriptNodeType nodeType)
{
var displayName = FormatDisplayName(type.Name);
var runtimeTypeName = type.FullName!;
string displayName = FormatDisplayName(type.Name);
string runtimeTypeName = type.FullName!;
// Get constructor parameter names.
var constructorParamNames = GetConstructorParameterNames(type);
string[] constructorParamNames = GetConstructorParameterNames(type);
// Determine ports and description by instantiating a temporary node.
string[] inputLabels;
@@ -194,8 +194,8 @@ internal static class StatescriptNodeDiscovery
ConstructorInfo constructor = constructors.OrderBy(x => x.GetParameters().Length).First();
ParameterInfo[] parameters = constructor.GetParameters();
var args = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
object[] args = new object[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
Type paramType = parameters[i].ParameterType;
@@ -236,8 +236,8 @@ internal static class StatescriptNodeDiscovery
private static string[] GetInputPortLabels(Node node, StatescriptNodeType nodeType)
{
var count = node.InputPorts.Length;
var labels = new string[count];
int count = node.InputPorts.Length;
string[] labels = new string[count];
switch (nodeType)
{
@@ -268,7 +268,7 @@ internal static class StatescriptNodeDiscovery
labels[1] = "Abort";
}
for (var i = 2; i < count; i++)
for (int i = 2; i < count; i++)
{
labels[i] = $"Input {i}";
}
@@ -276,7 +276,7 @@ internal static class StatescriptNodeDiscovery
break;
default:
for (var i = 0; i < count; i++)
for (int i = 0; i < count; i++)
{
labels[i] = $"Input {i}";
}
@@ -289,8 +289,8 @@ internal static class StatescriptNodeDiscovery
private static string[] GetOutputPortLabels(Node node, StatescriptNodeType nodeType)
{
var count = node.OutputPorts.Length;
var labels = new string[count];
int count = node.OutputPorts.Length;
string[] labels = new string[count];
switch (nodeType)
{
@@ -336,7 +336,7 @@ internal static class StatescriptNodeDiscovery
labels[3] = "Subgraph";
}
for (var i = 4; i < count; i++)
for (int i = 4; i < count; i++)
{
labels[i] = $"Event {i}";
}
@@ -344,7 +344,7 @@ internal static class StatescriptNodeDiscovery
break;
default:
for (var i = 0; i < count; i++)
for (int i = 0; i < count; i++)
{
labels[i] = $"Output {i}";
}
@@ -357,10 +357,10 @@ internal static class StatescriptNodeDiscovery
private static bool[] GetSubgraphFlags(Node node)
{
var count = node.OutputPorts.Length;
var flags = new bool[count];
int count = node.OutputPorts.Length;
bool[] flags = new bool[count];
for (var i = 0; i < count; i++)
for (int i = 0; i < count; i++)
{
flags[i] = node.OutputPorts[i] is SubgraphPort;
}
@@ -371,7 +371,7 @@ internal static class StatescriptNodeDiscovery
private static InputPropertyInfo[] GetInputPropertiesInfo(Node node)
{
var propertiesInfo = new InputPropertyInfo[node.InputProperties.Length];
for (var i = 0; i < node.InputProperties.Length; i++)
for (int i = 0; i < node.InputProperties.Length; i++)
{
propertiesInfo[i] = new InputPropertyInfo(
node.InputProperties[i].Label,
@@ -384,7 +384,7 @@ internal static class StatescriptNodeDiscovery
private static OutputVariableInfo[] GetOutputVariablesInfo(Node node)
{
var variablesInfo = new OutputVariableInfo[node.OutputVariables.Length];
for (var i = 0; i < node.OutputVariables.Length; i++)
for (int i = 0; i < node.OutputVariables.Length; i++)
{
variablesInfo[i] = new OutputVariableInfo(
node.OutputVariables[i].Label,
@@ -425,7 +425,7 @@ internal static class StatescriptNodeDiscovery
// Insert spaces before capital letters for camelCase names.
var result = new System.Text.StringBuilder();
for (var i = 0; i < typeName.Length; i++)
for (int i = 0; i < typeName.Length; i++)
{
if (i > 0 && char.IsUpper(typeName[i]) && !char.IsUpper(typeName[i - 1]))
{

View File

@@ -36,19 +36,61 @@ internal static class StatescriptResolverRegistry
/// <returns>A list of compatible resolver editor factories.</returns>
public static List<Func<NodeEditorProperty>> GetCompatibleFactories(Type expectedType)
{
var result = new List<Func<NodeEditorProperty>>();
return [.. _factories.Where(factory => IsCompatibleFactory(factory, expectedType))];
}
foreach (Func<NodeEditorProperty> factory in _factories)
public static int GetDefaultFactoryIndex(List<Func<NodeEditorProperty>> factories, bool isArray)
{
for (int i = 0; i < factories.Count; i++)
{
using NodeEditorProperty temp = factory();
if (temp.IsCompatibleWith(expectedType))
if (isArray)
{
result.Add(factory);
if (GetResolverTypeId(factories[i]) == "ArrayVariable")
{
return i;
}
}
else if (GetResolverTypeId(factories[i]) == "Variant")
{
return i;
}
}
return result;
return 0;
}
public static string GetDisplayName(Func<NodeEditorProperty> factory)
{
return UseTemporaryEditor(factory, static editor => editor.DisplayName);
}
public static string GetResolverTypeId(Func<NodeEditorProperty> factory)
{
return UseTemporaryEditor(factory, static editor => editor.ResolverTypeId);
}
public static bool IsCompatibleFactory(Func<NodeEditorProperty> factory, Type expectedType)
{
return UseTemporaryEditor(factory, editor => editor.IsCompatibleWith(expectedType));
}
private static TResult UseTemporaryEditor<TResult>(
Func<NodeEditorProperty> factory,
Func<NodeEditorProperty, TResult> selector)
{
NodeEditorProperty editor = factory();
try
{
return selector(editor);
}
finally
{
if (global::Godot.GodotObject.IsInstanceValid(editor))
{
editor.Free();
}
}
}
}
#endif

View File

@@ -68,6 +68,12 @@ internal sealed partial class StatescriptVariablePanel
private void DoRemoveVariable(StatescriptGraph graph, StatescriptGraphVariable variable, int index)
{
if (_selectedVariableName == variable.VariableName)
{
_selectedVariableName = null;
VariableHighlightChanged?.Invoke(null);
}
graph.Variables.RemoveAt(index);
ClearReferencesToVariable(variable.VariableName);
RebuildList();

View File

@@ -73,7 +73,7 @@ internal sealed partial class StatescriptVariablePanel
var headerRow = new HBoxContainer();
vBox.AddChild(headerRow);
var isExpanded = _expandedArrays.Contains(variable.VariableName);
bool isExpanded = _expandedArrays.Contains(variable.VariableName);
var elementsContainer = new VBoxContainer
{
@@ -93,7 +93,7 @@ internal sealed partial class StatescriptVariablePanel
{
elementsContainer.Visible = x;
var wasExpanded = !x;
bool wasExpanded = !x;
if (x)
{
@@ -162,9 +162,9 @@ internal sealed partial class StatescriptVariablePanel
vBox.AddChild(elementsContainer);
for (var i = 0; i < variable.InitialArrayValues.Count; i++)
for (int i = 0; i < variable.InitialArrayValues.Count; i++)
{
var capturedIndex = i;
int capturedIndex = i;
if (variable.VariableType == StatescriptVariableType.Bool)
{

View File

@@ -17,6 +17,11 @@ namespace Gamesmiths.Forge.Godot.Editor.Statescript;
internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISerializationListener
{
private const string ExpandedArraysMetaKey = "_expanded_arrays";
private const string HeaderRowNodeName = "HeaderRow";
private const string AddButtonNodeName = "AddButton";
private const string VariablesScrollNodeName = "VariablesScroll";
private const string VariableListNodeName = "VariableList";
private const string VariableNameButtonMetaKey = "_variable_name_button";
private static readonly Color _variableColor = new(0xe5c07bff);
private static readonly Color _highlightColor = new(0x56b6c2ff);
@@ -64,7 +69,7 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
SizeFlagsVertical = SizeFlags.ExpandFill;
CustomMinimumSize = new Vector2(360, 0);
var headerHBox = new HBoxContainer();
var headerHBox = new HBoxContainer { Name = HeaderRowNodeName };
AddChild(headerHBox);
var titleLabel = new Label
@@ -77,6 +82,7 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
_addButton = new Button
{
Name = AddButtonNodeName,
Icon = _addIcon,
Flat = true,
TooltipText = "Add Variable",
@@ -91,6 +97,7 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
var scrollContainer = new ScrollContainer
{
Name = VariablesScrollNodeName,
SizeFlagsVertical = SizeFlags.ExpandFill,
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
@@ -99,6 +106,7 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
_variableList = new VBoxContainer
{
Name = VariableListNodeName,
SizeFlagsHorizontal = SizeFlags.ExpandFill,
};
@@ -108,45 +116,19 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
public override void _ExitTree()
{
base._ExitTree();
if (_addButton is not null)
{
_addButton.Pressed -= OnAddPressed;
}
_creationDialog?.QueueFree();
_creationDialog = null;
_newNameEdit = null;
_newTypeDropdown = null;
_newArrayToggle = null;
ReleaseUiState();
}
public void OnBeforeSerialize()
{
if (_addButton is not null)
{
_addButton.Pressed -= OnAddPressed;
}
if (_variableList is not null)
{
foreach (Node child in _variableList.GetChildren())
{
_variableList.RemoveChild(child);
child.Free();
}
}
_creationDialog?.Free();
_creationDialog = null;
_newNameEdit = null;
_newTypeDropdown = null;
_newArrayToggle = null;
ReleaseUiState();
}
public void OnAfterDeserialize()
{
if (_addButton is not null)
EnsureControlsCached();
if (_addButton is not null && IsInstanceValid(_addButton))
{
_addButton.Pressed += OnAddPressed;
}
@@ -165,6 +147,31 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
RebuildList();
}
public string? GetSelectedVariableName()
{
return _selectedVariableName;
}
public void RestoreSelectedVariable(string? variableName)
{
if (string.IsNullOrEmpty(variableName) || !HasVariableNamed(variableName))
{
_selectedVariableName = null;
}
else
{
_selectedVariableName = variableName;
}
RefreshVariableSelectionVisuals();
VariableHighlightChanged?.Invoke(_selectedVariableName);
}
public void ClearSelectedVariable()
{
RestoreSelectedVariable(null);
}
/// <summary>
/// Sets the <see cref="EditorUndoRedoManager"/> used for undo/redo support.
/// </summary>
@@ -179,26 +186,95 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
/// </summary>
public void RebuildList()
{
EnsureControlsCached();
if (_variableList is null)
{
return;
}
ClearVariableList();
if (_graph is null)
{
return;
}
for (int i = 0; i < _graph.Variables.Count; i++)
{
AddVariableRow(_graph.Variables[i], i);
}
}
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", _highlightColor);
button.AddThemeColorOverride("font_hover_color", buttonColor.Lightened(0.2f));
button.AddThemeColorOverride("font_hover_pressed_color", _highlightColor.Lightened(0.2f));
}
private void EnsureControlsCached()
{
_addButton ??= GetNodeOrNull<Button>($"{HeaderRowNodeName}/{AddButtonNodeName}");
_variableList ??= GetNodeOrNull<VBoxContainer>($"{VariablesScrollNodeName}/{VariableListNodeName}");
}
private void ClearVariableList()
{
EnsureControlsCached();
if (_variableList is null || !IsInstanceValid(_variableList))
{
return;
}
foreach (Node child in _variableList.GetChildren())
{
_variableList.RemoveChild(child);
child.Free();
}
}
if (_graph is null)
private void ReleaseUiState()
{
if (_addButton is not null && IsInstanceValid(_addButton))
{
return;
_addButton.Pressed -= OnAddPressed;
}
for (var i = 0; i < _graph.Variables.Count; i++)
ClearVariableList();
if (_creationDialog is not null && IsInstanceValid(_creationDialog))
{
AddVariableRow(_graph.Variables[i], i);
if (_creationDialog is AcceptDialog acceptDialog)
{
acceptDialog.Confirmed -= OnCreationConfirmed;
}
_creationDialog.Free();
}
_creationDialog = null;
_newNameEdit = null;
_newTypeDropdown = null;
_newArrayToggle = null;
_variableList = null;
_addButton = null;
}
private void SaveExpandedArrayState()
@@ -208,7 +284,7 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
return;
}
var packed = new string[_expandedArrays.Count];
string[] packed = new string[_expandedArrays.Count];
_expandedArrays.CopyTo(packed);
_graph.SetMeta(ExpandedArraysMetaKey, Variant.From(packed));
}
@@ -226,7 +302,7 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
if (meta.VariantType == Variant.Type.PackedStringArray)
{
foreach (var name in meta.AsStringArray())
foreach (string name in meta.AsStringArray())
{
_expandedArrays.Add(name);
}
@@ -254,7 +330,7 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
rowContainer.AddChild(headerRow);
var isSelected = _selectedVariableName == variable.VariableName;
bool isSelected = _selectedVariableName == variable.VariableName;
var nameButton = new Button
{
@@ -266,28 +342,15 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
Alignment = HorizontalAlignment.Left,
};
Color buttonColor = isSelected ? _highlightColor : _variableColor;
nameButton.AddThemeColorOverride("font_color", buttonColor);
nameButton.AddThemeColorOverride("font_pressed_color", _highlightColor);
nameButton.AddThemeColorOverride("font_hover_color", buttonColor.Lightened(0.2f));
nameButton.AddThemeColorOverride("font_hover_pressed_color", _highlightColor.Lightened(0.2f));
nameButton.SetMeta(VariableNameButtonMetaKey, variable.VariableName);
UpdateVariableNameButtonAppearance(nameButton, isSelected);
nameButton.AddThemeFontOverride(
"font",
EditorInterface.Singleton.GetEditorTheme().GetFont("bold", "EditorFonts"));
nameButton.Toggled += pressed =>
{
if (pressed)
{
_selectedVariableName = variable.VariableName;
}
else if (_selectedVariableName == variable.VariableName)
{
_selectedVariableName = null;
}
RebuildList();
VariableHighlightChanged?.Invoke(_selectedVariableName);
SetSelectedVariable(variable.VariableName, pressed);
};
headerRow.AddChild(nameButton);
@@ -301,7 +364,7 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
typeLabel.AddThemeColorOverride("font_color", new Color(0.6f, 0.6f, 0.6f));
headerRow.AddChild(typeLabel);
var capturedIndex = index;
int capturedIndex = index;
var deleteButton = new Button
{
@@ -328,6 +391,47 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
rowContainer.AddChild(new HSeparator());
}
private void SetSelectedVariable(string variableName, bool selected)
{
if (selected)
{
_selectedVariableName = variableName;
}
else if (_selectedVariableName == variableName)
{
_selectedVariableName = null;
}
RefreshVariableSelectionVisuals();
VariableHighlightChanged?.Invoke(_selectedVariableName);
}
private void RefreshVariableSelectionVisuals()
{
if (_variableList is null)
{
return;
}
RefreshVariableSelectionVisualsRecursive(_variableList);
}
private void RefreshVariableSelectionVisualsRecursive(Node parent)
{
foreach (Node child in parent.GetChildren())
{
if (child is Button button && button.HasMeta(VariableNameButtonMetaKey))
{
string variableName = button.GetMeta(VariableNameButtonMetaKey).AsString();
bool isSelected = _selectedVariableName == variableName;
button.SetPressedNoSignal(isSelected);
UpdateVariableNameButtonAppearance(button, isSelected);
}
RefreshVariableSelectionVisualsRecursive(child);
}
}
private void OnAddPressed()
{
if (_graph is null)
@@ -373,12 +477,14 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
StatescriptVariableType[] allTypes = StatescriptVariableTypeConverter.GetAllTypes();
for (var t = 0; t < allTypes.Length; t++)
for (int t = 0; t < allTypes.Length; t++)
{
_newTypeDropdown.AddItem(StatescriptVariableTypeConverter.GetDisplayName(allTypes[t]), t);
_newTypeDropdown.AddItem(
StatescriptVariableTypeConverter.GetDisplayName(allTypes[t]),
(int)allTypes[t]);
}
_newTypeDropdown.Selected = (int)StatescriptVariableType.Int;
_newTypeDropdown.Selected = FindTypeDropdownIndex(_newTypeDropdown, StatescriptVariableType.Int);
typeRow.AddChild(_newTypeDropdown);
var arrayRow = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
@@ -404,20 +510,20 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
return;
}
var name = _newNameEdit.Text.Trim();
string name = _newNameEdit.Text.Trim();
if (string.IsNullOrEmpty(name) || HasVariableNamed(name))
{
return;
}
var selectedIndex = _newTypeDropdown.Selected;
int selectedIndex = _newTypeDropdown.Selected;
if (selectedIndex < 0)
{
return;
}
var selectedId = _newTypeDropdown.GetItemId(selectedIndex);
int selectedId = _newTypeDropdown.GetItemId(selectedIndex);
var varType = (StatescriptVariableType)selectedId;
var newVariable = new StatescriptGraphVariable
@@ -431,8 +537,8 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
if (_undoRedo is not null)
{
_undoRedo.CreateAction("Add Graph Variable", customContext: _graph);
_undoRedo.AddDoMethod(this, MethodName.DoAddVariable, _graph!, newVariable);
_undoRedo.AddUndoMethod(this, MethodName.UndoAddVariable, _graph!, newVariable);
_undoRedo.AddDoMethod(this, MethodName.DoAddVariable, _graph, newVariable);
_undoRedo.AddUndoMethod(this, MethodName.UndoAddVariable, _graph, newVariable);
_undoRedo.CommitAction();
}
else
@@ -459,8 +565,8 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
if (_undoRedo is not null)
{
_undoRedo.CreateAction("Remove Graph Variable", customContext: _graph);
_undoRedo.AddDoMethod(this, MethodName.DoRemoveVariable, _graph!, variable, index);
_undoRedo.AddUndoMethod(this, MethodName.UndoRemoveVariable, _graph!, variable, index);
_undoRedo.AddDoMethod(this, MethodName.DoRemoveVariable, _graph, variable, index);
_undoRedo.AddUndoMethod(this, MethodName.UndoRemoveVariable, _graph, variable, index);
_undoRedo.CommitAction();
}
else
@@ -497,8 +603,8 @@ internal sealed partial class StatescriptVariablePanel : VBoxContainer, ISeriali
}
const string baseName = "variable";
var counter = 1;
var name = baseName;
int counter = 1;
string name = baseName;
while (HasVariableNamed(name))
{

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)

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ACosHResolverEditor : ScalarUnaryResolverEditorBase<ACosHResolverResource>
{
public override string DisplayName => "ACosH";
public override string ResolverTypeId => "ACosH";
}
#endif

View File

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

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ACosResolverEditor : ScalarUnaryResolverEditorBase<ACosResolverResource>
{
public override string DisplayName => "ACos";
public override string ResolverTypeId => "ACos";
}
#endif

View File

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

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ASinHResolverEditor : ScalarUnaryResolverEditorBase<ASinHResolverResource>
{
public override string DisplayName => "ASinH";
public override string ResolverTypeId => "ASinH";
}
#endif

View File

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

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ASinResolverEditor : ScalarUnaryResolverEditorBase<ASinResolverResource>
{
public override string DisplayName => "ASin";
public override string ResolverTypeId => "ASin";
}
#endif

View File

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

View File

@@ -0,0 +1,21 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ATan2ResolverEditor : ScalarBinaryResolverEditorBase<ATan2ResolverResource>
{
public override string DisplayName => "ATan2";
public override string ResolverTypeId => "ATan2";
protected override string LeftTitle => "Y:";
protected override string RightTitle => "X:";
}
#endif

View File

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

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ATanHResolverEditor : ScalarUnaryResolverEditorBase<ATanHResolverResource>
{
public override string DisplayName => "ATanH";
public override string ResolverTypeId => "ATanH";
}
#endif

View File

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

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ATanResolverEditor : ScalarUnaryResolverEditorBase<ATanResolverResource>
{
public override string DisplayName => "ATan";
public override string ResolverTypeId => "ATan";
}
#endif

View File

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

View File

@@ -0,0 +1,34 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysVector2 = System.Numerics.Vector2;
using SysVector3 = System.Numerics.Vector3;
using SysVector4 = System.Numerics.Vector4;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class AbsResolverEditor : NumericOrVectorUnaryResolverEditorBase<AbsResolverResource>
{
public override string DisplayName => "Abs";
public override string ResolverTypeId => "Abs";
protected override Type[] GetFactoryExpectedTypes(Type expectedType)
{
return expectedType == typeof(ForgeVariant128)
? [typeof(int), typeof(float), typeof(double), typeof(SysVector2), typeof(SysVector3), typeof(SysVector4)]
: [expectedType];
}
protected override Type GetNestedExpectedType(Type expectedType)
{
return expectedType;
}
}
#endif

View File

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

View File

@@ -129,6 +129,11 @@ internal sealed partial class ActivationDataResolverEditor : NodeEditorProperty
_onChanged = null;
}
private static bool IsCompatibleType(Type expectedType, StatescriptVariableType fieldType)
{
return StatescriptVariableTypeConverter.IsCompatible(expectedType, fieldType);
}
private static string FindExistingProvider(StatescriptGraph graph, StatescriptNodeProperty? currentProperty)
{
foreach (StatescriptNode node in graph.Nodes)
@@ -158,13 +163,7 @@ internal sealed partial class ActivationDataResolverEditor : NodeEditorProperty
return null;
}
Type? type = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.FirstOrDefault(
x => typeof(IActivationDataProvider).IsAssignableFrom(x)
&& !x.IsAbstract
&& !x.IsInterface
&& x.Name == className);
Type? type = ResolveProviderType(className);
if (type is null)
{
@@ -174,6 +173,43 @@ internal sealed partial class ActivationDataResolverEditor : NodeEditorProperty
return Activator.CreateInstance(type) as IActivationDataProvider;
}
private static IEnumerable<Type> GetProviderTypes()
{
return AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(x => typeof(IActivationDataProvider).IsAssignableFrom(x)
&& !x.IsAbstract
&& !x.IsInterface);
}
private static string GetProviderIdentifier(Type type)
{
return type.FullName ?? type.Name;
}
private static bool ProviderMatches(Type type, string providerIdentifier)
{
return type.AssemblyQualifiedName == providerIdentifier
|| type.FullName == providerIdentifier
|| type.Name == providerIdentifier;
}
private static Type? ResolveProviderType(string providerIdentifier)
{
return GetProviderTypes().FirstOrDefault(x => ProviderMatches(x, providerIdentifier));
}
private static bool ProviderIdentifiersMatch(string left, string right)
{
if (left == right)
{
return true;
}
Type? leftType = ResolveProviderType(left);
return leftType is not null && ProviderMatches(leftType, right);
}
private void OnProviderDropdownItemSelected(long index)
{
if (_providerDropdown is null)
@@ -181,7 +217,7 @@ internal sealed partial class ActivationDataResolverEditor : NodeEditorProperty
return;
}
var idx = _providerDropdown.Selected;
int idx = _providerDropdown.Selected;
_selectedProviderClassName = idx >= 0 && idx < _providerClassNames.Count
? _providerClassNames[idx]
: string.Empty;
@@ -200,7 +236,7 @@ internal sealed partial class ActivationDataResolverEditor : NodeEditorProperty
return;
}
var dropdownIndex = _fieldDropdown.Selected;
int dropdownIndex = _fieldDropdown.Selected;
if (dropdownIndex >= 0 && dropdownIndex < _fieldNames.Count)
{
@@ -239,37 +275,39 @@ internal sealed partial class ActivationDataResolverEditor : NodeEditorProperty
_providerClassNames.Add(string.Empty);
// Re-scan the graph each time to pick up changes from other editors.
var graphLockedProvider = _graph is not null
string graphLockedProvider = _graph is not null
? FindExistingProvider(_graph, _currentProperty)
: string.Empty;
if (!string.IsNullOrEmpty(graphLockedProvider))
{
// Another node already uses a provider: only show that one (plus None).
_providerDropdown.AddItem(graphLockedProvider);
_providerClassNames.Add(graphLockedProvider);
Type? lockedProviderType = ResolveProviderType(graphLockedProvider);
string lockedProviderIdentifier = lockedProviderType is null
? graphLockedProvider
: GetProviderIdentifier(lockedProviderType);
string lockedProviderDisplayName = lockedProviderType?.Name ?? graphLockedProvider;
_providerDropdown.AddItem(lockedProviderDisplayName);
_providerClassNames.Add(lockedProviderIdentifier);
}
else
{
foreach (var name in AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(x => typeof(IActivationDataProvider).IsAssignableFrom(x)
&& !x.IsAbstract
&& !x.IsInterface)
.Select(x => x.Name))
foreach (Type providerType in GetProviderTypes())
{
_providerDropdown.AddItem(name);
_providerClassNames.Add(name);
_providerDropdown.AddItem(providerType.Name);
_providerClassNames.Add(GetProviderIdentifier(providerType));
}
}
// Restore selection.
if (!string.IsNullOrEmpty(_selectedProviderClassName))
{
for (var i = 0; i < _providerClassNames.Count; i++)
for (int i = 0; i < _providerClassNames.Count; i++)
{
if (_providerClassNames[i] == _selectedProviderClassName)
if (ProviderIdentifiersMatch(_providerClassNames[i], _selectedProviderClassName))
{
_selectedProviderClassName = _providerClassNames[i];
_providerDropdown.Selected = i;
return;
}
@@ -306,8 +344,7 @@ internal sealed partial class ActivationDataResolverEditor : NodeEditorProperty
continue;
}
if (_expectedType != typeof(Variant128)
&& !StatescriptVariableTypeConverter.IsCompatible(_expectedType, field.FieldType))
if (!IsCompatibleType(_expectedType, field.FieldType))
{
continue;
}
@@ -320,7 +357,7 @@ internal sealed partial class ActivationDataResolverEditor : NodeEditorProperty
// Restore selection.
if (!string.IsNullOrEmpty(_selectedFieldName))
{
for (var i = 0; i < _fieldNames.Count; i++)
for (int i = 0; i < _fieldNames.Count; i++)
{
if (_fieldNames[i] == _selectedFieldName)
{

View File

@@ -0,0 +1,42 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysVector2 = System.Numerics.Vector2;
using SysVector3 = System.Numerics.Vector3;
using SysVector4 = System.Numerics.Vector4;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class AddResolverEditor : NumericVectorOrQuaternionBinaryResolverEditorBase<AddResolverResource>
{
public override string DisplayName => "Add";
public override string ResolverTypeId => "Add";
protected override Type[] GetFactoryExpectedTypes(Type expectedType)
{
return expectedType == typeof(ForgeVariant128)
? [
typeof(int),
typeof(float),
typeof(double),
typeof(SysVector2),
typeof(SysVector3),
typeof(SysVector4),
typeof(System.Numerics.Quaternion)
]
: [expectedType];
}
protected override Type GetNestedExpectedType(Type expectedType)
{
return expectedType;
}
}
#endif

View File

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

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class AndResolverEditor : BooleanBinaryResolverEditorBase<AndResolverResource>
{
public override string DisplayName => "And";
public override string ResolverTypeId => "And";
}
#endif

View File

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

View File

@@ -0,0 +1,37 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysVector2 = System.Numerics.Vector2;
using SysVector3 = System.Numerics.Vector3;
using SysVector4 = System.Numerics.Vector4;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class AngleResolverEditor
: VectorOrQuaternionBinaryFloatResolverEditorBase<AngleResolverResource>
{
public override string DisplayName => "Angle";
public override string ResolverTypeId => "Angle";
protected override string LeftTitle => "From:";
protected override string RightTitle => "To:";
protected override Type[] GetFactoryExpectedTypes(Type expectedType)
{
if (expectedType == typeof(ForgeVariant128) || ResolverEditorCompatibility.IsFloatType(expectedType))
{
return [typeof(SysVector2), typeof(SysVector3), typeof(SysVector4), typeof(System.Numerics.Quaternion)];
}
return [expectedType];
}
}
#endif

View File

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

View File

@@ -0,0 +1,85 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Resources.Statescript;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ArrayVariableResolverEditor : NodeEditorProperty
{
private VariantResolverEditor? _innerEditor;
public override string DisplayName => "Array";
public override string ResolverTypeId => "ArrayVariable";
public override bool IsCompatibleWith(Type expectedType)
{
return expectedType.IsArray;
}
public override void Setup(
StatescriptGraph graph,
StatescriptNodeProperty? property,
Type expectedType,
Action onChanged,
bool isArray)
{
_innerEditor = new VariantResolverEditor();
Type elementExpectedType = expectedType.IsArray ? expectedType.GetElementType() ?? typeof(int) : expectedType;
VariantResolverResource? tempResolver = null;
if (property?.Resolver is ArrayVariableResolverResource existing)
{
tempResolver = new VariantResolverResource
{
IsArray = true,
ValueType = existing.ValueType,
ArrayValues = [.. existing.ArrayValues],
IsArrayExpanded = existing.IsArrayExpanded,
};
}
StatescriptNodeProperty? tempProperty = tempResolver is null
? null
: new StatescriptNodeProperty { Resolver = tempResolver };
_innerEditor.Setup(graph, tempProperty, elementExpectedType, onChanged, true);
_innerEditor.LayoutSizeChanged += RaiseLayoutSizeChanged;
AddChild(_innerEditor);
}
public override void SaveTo(StatescriptNodeProperty property)
{
if (_innerEditor is null)
{
return;
}
var tempProperty = new StatescriptNodeProperty();
_innerEditor.SaveTo(tempProperty);
if (tempProperty.Resolver is not VariantResolverResource variant)
{
return;
}
property.Resolver = new ArrayVariableResolverResource
{
ValueType = variant.ValueType,
ArrayValues = [.. variant.ArrayValues],
IsArrayExpanded = variant.IsArrayExpanded,
};
}
public override void ClearCallbacks()
{
base.ClearCallbacks();
_innerEditor?.ClearCallbacks();
}
}
#endif

View File

@@ -0,0 +1 @@
uid://30cl3jyw0pvd

View File

@@ -135,7 +135,7 @@ internal sealed partial class AttributeResolverEditor : NodeEditorProperty
_setDropdown.Clear();
foreach (var option in EditorUtils.GetAttributeSetOptions())
foreach (string option in EditorUtils.GetAttributeSetOptions())
{
_setDropdown.AddItem(option);
}
@@ -143,7 +143,7 @@ internal sealed partial class AttributeResolverEditor : NodeEditorProperty
// Restore selection.
if (!string.IsNullOrEmpty(_selectedSetClass))
{
for (var i = 0; i < _setDropdown.GetItemCount(); i++)
for (int i = 0; i < _setDropdown.GetItemCount(); i++)
{
if (_setDropdown.GetItemText(i) == _selectedSetClass)
{
@@ -170,14 +170,14 @@ internal sealed partial class AttributeResolverEditor : NodeEditorProperty
_attributeDropdown.Clear();
foreach (var option in EditorUtils.GetAttributeOptions(_selectedSetClass))
foreach (string option in EditorUtils.GetAttributeOptions(_selectedSetClass))
{
_attributeDropdown.AddItem(option);
}
if (!string.IsNullOrEmpty(_selectedAttribute))
{
for (var i = 0; i < _attributeDropdown.GetItemCount(); i++)
for (int i = 0; i < _attributeDropdown.GetItemCount(); i++)
{
if (_attributeDropdown.GetItemText(i) == _selectedAttribute)
{

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class CbrtResolverEditor : ScalarUnaryResolverEditorBase<CbrtResolverResource>
{
public override string DisplayName => "Cbrt";
public override string ResolverTypeId => "Cbrt";
}
#endif

View File

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

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class CeilResolverEditor : FloatUnaryResolverEditorBase<CeilResolverResource>
{
public override string DisplayName => "Ceil";
public override string ResolverTypeId => "Ceil";
}
#endif

View File

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

View File

@@ -0,0 +1,47 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysVector2 = System.Numerics.Vector2;
using SysVector3 = System.Numerics.Vector3;
using SysVector4 = System.Numerics.Vector4;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ClampMagnitudeResolverEditor
: AsymmetricBinaryNestedResolverEditorBase<ClampMagnitudeResolverResource>
{
public override string DisplayName => "Clamp Magnitude";
public override string ResolverTypeId => "ClampMagnitude";
protected override Type[] LeftFactoryExpectedTypes => [typeof(SysVector2), typeof(SysVector3), typeof(SysVector4)];
protected override Type[] RightFactoryExpectedTypes => ResolverEditorCompatibility.FloatOperandExpectedTypes;
protected override Type LeftNestedExpectedType => typeof(ForgeVariant128);
protected override Type RightNestedExpectedType => typeof(float);
protected override string LeftTitle => "Value:";
protected override string RightTitle => "Max Length:";
public override bool IsCompatibleWith(Type expectedType)
{
return expectedType == typeof(ForgeVariant128) || ResolverEditorCompatibility.IsVectorType(expectedType);
}
protected override Type[] GetLeftFactoryExpectedTypes(Type expectedType)
{
return expectedType == typeof(ForgeVariant128)
? [typeof(SysVector2), typeof(SysVector3), typeof(SysVector4)]
: [expectedType];
}
}
#endif

View File

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

View File

@@ -0,0 +1,40 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysVector2 = System.Numerics.Vector2;
using SysVector3 = System.Numerics.Vector3;
using SysVector4 = System.Numerics.Vector4;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ClampResolverEditor : NumericOrVectorTernaryResolverEditorBase<ClampResolverResource>
{
public override string DisplayName => "Clamp";
public override string ResolverTypeId => "Clamp";
protected override string FirstTitle => "Value:";
protected override string SecondTitle => "Min:";
protected override string ThirdTitle => "Max:";
protected override Type[] GetFirstFactoryExpectedTypes(Type expectedType)
{
return expectedType == typeof(ForgeVariant128)
? [typeof(int), typeof(float), typeof(double), typeof(SysVector2), typeof(SysVector3), typeof(SysVector4)]
: [expectedType];
}
protected override Type[] GetSecondFactoryExpectedTypes(Type expectedType)
{
return GetFirstFactoryExpectedTypes(expectedType);
}
}
#endif

View File

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

View File

@@ -3,6 +3,7 @@
#if TOOLS
using System;
using System.Collections.Generic;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Gamesmiths.Forge.Statescript.Properties;
@@ -18,12 +19,16 @@ namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ComparisonResolverEditor : NodeEditorProperty
{
private static readonly Type[] _numericExpectedTypes = [typeof(int), typeof(float), typeof(double)];
private StatescriptGraph? _graph;
private Action? _onChanged;
private OptionButton? _operationDropdown;
private VBoxContainer? _leftContainer;
private VBoxContainer? _rightContainer;
private FoldableContainer? _leftFoldable;
private FoldableContainer? _rightFoldable;
private OptionButton? _leftResolverDropdown;
private OptionButton? _rightResolverDropdown;
@@ -63,7 +68,7 @@ internal sealed partial class ComparisonResolverEditor : NodeEditorProperty
var vBox = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
AddChild(vBox);
_numericFactories = StatescriptResolverRegistry.GetCompatibleFactories(typeof(ForgeVariant128));
_numericFactories = ResolverEditorFactoryCatalog.GetCompatibleFactories(_numericExpectedTypes);
_numericFactories.RemoveAll(x =>
{
@@ -78,12 +83,17 @@ internal sealed partial class ComparisonResolverEditor : NodeEditorProperty
_operation = comparisonResolver.Operation;
}
var leftFoldable = new FoldableContainer { Title = "Left:" };
leftFoldable.FoldingChanged += OnFoldingChanged;
vBox.AddChild(leftFoldable);
_leftFoldable = new FoldableContainer
{
Title = "Left:",
Folded = comparisonResolver?.LeftFolded ?? true,
};
_leftFoldable.FoldingChanged += OnFoldingChanged;
vBox.AddChild(_leftFoldable);
_leftContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
leftFoldable.AddChild(_leftContainer);
_leftFoldable.AddChild(_leftContainer);
_leftEditorContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
_leftResolverDropdown = CreateResolverDropdownControl(comparisonResolver?.Left);
@@ -117,12 +127,16 @@ internal sealed partial class ComparisonResolverEditor : NodeEditorProperty
opRow.AddChild(_operationDropdown);
var rightFoldable = new FoldableContainer { Title = "Right:" };
rightFoldable.FoldingChanged += OnFoldingChanged;
vBox.AddChild(rightFoldable);
_rightFoldable = new FoldableContainer
{
Title = "Right:",
Folded = comparisonResolver?.RightFolded ?? true,
};
_rightFoldable.FoldingChanged += OnFoldingChanged;
vBox.AddChild(_rightFoldable);
_rightContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
rightFoldable.AddChild(_rightContainer);
_rightFoldable.AddChild(_rightContainer);
_rightEditorContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
_rightResolverDropdown = CreateResolverDropdownControl(comparisonResolver?.Right);
@@ -135,12 +149,18 @@ internal sealed partial class ComparisonResolverEditor : NodeEditorProperty
x => _rightEditor = x);
_rightResolverDropdown.ItemSelected += OnRightResolverDropdownItemSelected;
UpdateFoldableTitles();
}
/// <inheritdoc/>
public override void SaveTo(StatescriptNodeProperty property)
{
var comparisonResolver = new ComparisonResolverResource { Operation = _operation };
var comparisonResolver = new ComparisonResolverResource
{
Operation = _operation,
LeftFolded = _leftFoldable?.Folded ?? false,
RightFolded = _rightFoldable?.Folded ?? false,
};
if (_leftEditor is not null)
{
@@ -167,10 +187,23 @@ internal sealed partial class ComparisonResolverEditor : NodeEditorProperty
_leftEditor?.ClearCallbacks();
_rightEditor?.ClearCallbacks();
_operationDropdown = null;
_leftContainer = null;
_rightContainer = null;
_leftFoldable = null;
_rightFoldable = null;
_leftResolverDropdown = null;
_rightResolverDropdown = null;
_leftEditor = null;
_rightEditor = null;
_leftEditorContainer = null;
_rightEditorContainer = null;
}
private void OnFoldingChanged(bool isFolded)
{
UpdateFoldableTitles();
_onChanged?.Invoke();
RaiseLayoutSizeChanged();
}
@@ -208,31 +241,14 @@ internal sealed partial class ComparisonResolverEditor : NodeEditorProperty
setEditor(null);
ShowNestedEditor(selectedIndex, null, editorContainer, setEditor);
UpdateFoldableTitles();
_onChanged?.Invoke();
RaiseLayoutSizeChanged();
}
private int GetSelectedIndex(StatescriptResolverResource? existingResolver)
{
var selectedIndex = 0;
if (existingResolver is not null)
{
var existingTypeId = existingResolver.ResolverTypeId;
for (var i = 0; i < _numericFactories.Count; i++)
{
using NodeEditorProperty temp = _numericFactories[i]();
if (temp.ResolverTypeId == existingTypeId)
{
selectedIndex = i;
break;
}
}
}
return selectedIndex;
return ResolverEditorFactoryCatalog.GetDefaultFactoryIndex(_numericFactories, existingResolver, "Variant");
}
private OptionButton CreateResolverDropdownControl(StatescriptResolverResource? existingResolver)
@@ -241,8 +257,7 @@ internal sealed partial class ComparisonResolverEditor : NodeEditorProperty
foreach (Func<NodeEditorProperty> factory in _numericFactories)
{
using NodeEditorProperty temp = factory();
dropdown.AddItem(temp.DisplayName);
dropdown.AddItem(StatescriptResolverRegistry.GetDisplayName(factory));
}
dropdown.Selected = GetSelectedIndex(existingResolver);
@@ -261,26 +276,41 @@ internal sealed partial class ComparisonResolverEditor : NodeEditorProperty
return;
}
NodeEditorProperty editor = _numericFactories[factoryIndex]();
NodeEditorProperty? editor = NestedResolverEditorUtilities.CreateNestedEditor(
_graph,
_numericFactories,
factoryIndex,
existingResolver,
_numericExpectedTypes,
OnNestedEditorChanged,
RaiseLayoutSizeChanged);
StatescriptNodeProperty? tempProperty = null;
if (existingResolver is not null)
if (editor is null)
{
tempProperty = new StatescriptNodeProperty { Resolver = existingResolver };
return;
}
editor.Setup(_graph, tempProperty, typeof(ForgeVariant128), OnNestedEditorChanged, false);
editor.LayoutSizeChanged += RaiseLayoutSizeChanged;
container.AddChild(editor);
setEditor(editor);
}
private void OnNestedEditorChanged()
{
UpdateFoldableTitles();
_onChanged?.Invoke();
}
private void UpdateFoldableTitles()
{
if (_leftFoldable is not null)
{
InlineConstantSummaryFormatter.ApplyFoldableTitle("Left:", _leftFoldable, _leftEditor);
}
if (_rightFoldable is not null)
{
InlineConstantSummaryFormatter.ApplyFoldableTitle("Right:", _rightFoldable, _rightEditor);
}
}
}
#endif

View File

@@ -0,0 +1,29 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysQuaternion = System.Numerics.Quaternion;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ConcatenateResolverEditor : BinaryNestedResolverEditorBase<ConcatenateResolverResource>
{
public override string DisplayName => "Concatenate";
public override string ResolverTypeId => "Concatenate";
protected override Type[] FactoryExpectedTypes => [typeof(SysQuaternion)];
protected override Type NestedExpectedType => typeof(SysQuaternion);
public override bool IsCompatibleWith(Type expectedType)
{
return expectedType == typeof(SysQuaternion) || expectedType == typeof(ForgeVariant128);
}
}
#endif

View File

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

View File

@@ -0,0 +1,29 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysQuaternion = System.Numerics.Quaternion;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class ConjugateResolverEditor : UnaryNestedResolverEditorBase<ConjugateResolverResource>
{
public override string DisplayName => "Conjugate";
public override string ResolverTypeId => "Conjugate";
protected override Type[] FactoryExpectedTypes => [typeof(SysQuaternion)];
protected override Type NestedExpectedType => typeof(SysQuaternion);
public override bool IsCompatibleWith(Type expectedType)
{
return expectedType == typeof(SysQuaternion) || expectedType == typeof(ForgeVariant128);
}
}
#endif

View File

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

View File

@@ -0,0 +1,21 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class CopySignResolverEditor : ScalarBinaryResolverEditorBase<CopySignResolverResource>
{
public override string DisplayName => "Copy Sign";
public override string ResolverTypeId => "CopySign";
protected override string LeftTitle => "Magnitude:";
protected override string RightTitle => "Sign:";
}
#endif

View File

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

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class CosHResolverEditor : ScalarUnaryResolverEditorBase<CosHResolverResource>
{
public override string DisplayName => "CosH";
public override string ResolverTypeId => "CosH";
}
#endif

View File

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

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class CosResolverEditor : ScalarUnaryResolverEditorBase<CosResolverResource>
{
public override string DisplayName => "Cos";
public override string ResolverTypeId => "Cos";
}
#endif

View File

@@ -0,0 +1 @@
uid://3iou2ih6sonq

View File

@@ -0,0 +1,25 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysVector3 = System.Numerics.Vector3;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class CrossResolverEditor : VectorBinaryResolverEditorBase<CrossResolverResource>
{
public override string DisplayName => "Cross";
public override string ResolverTypeId => "Cross";
public override bool IsCompatibleWith(Type expectedType)
{
return expectedType == typeof(SysVector3) || expectedType == typeof(ForgeVariant128);
}
}
#endif

View File

@@ -0,0 +1 @@
uid://00ltb2mbsruh

View File

@@ -0,0 +1,17 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class DegToRadResolverEditor : NumericOrVectorUnaryResolverEditorBase<DegToRadResolverResource>
{
public override string DisplayName => "Deg To Rad";
public override string ResolverTypeId => "DegToRad";
}
#endif

View File

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

View File

@@ -0,0 +1,33 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysVector2 = System.Numerics.Vector2;
using SysVector3 = System.Numerics.Vector3;
using SysVector4 = System.Numerics.Vector4;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class DistanceResolverEditor
: VectorOrQuaternionBinaryFloatResolverEditorBase<DistanceResolverResource>
{
public override string DisplayName => "Distance";
public override string ResolverTypeId => "Distance";
protected override Type[] GetFactoryExpectedTypes(Type expectedType)
{
if (expectedType == typeof(ForgeVariant128) || ResolverEditorCompatibility.IsFloatType(expectedType))
{
return [typeof(SysVector2), typeof(SysVector3), typeof(SysVector4), typeof(System.Numerics.Quaternion)];
}
return [expectedType];
}
}
#endif

View File

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

View File

@@ -0,0 +1,33 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysVector2 = System.Numerics.Vector2;
using SysVector3 = System.Numerics.Vector3;
using SysVector4 = System.Numerics.Vector4;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class DistanceSquaredResolverEditor
: VectorOrQuaternionBinaryFloatResolverEditorBase<DistanceSquaredResolverResource>
{
public override string DisplayName => "Distance Squared";
public override string ResolverTypeId => "DistanceSquared";
protected override Type[] GetFactoryExpectedTypes(Type expectedType)
{
if (expectedType == typeof(ForgeVariant128) || ResolverEditorCompatibility.IsFloatType(expectedType))
{
return [typeof(SysVector2), typeof(SysVector3), typeof(SysVector4), typeof(System.Numerics.Quaternion)];
}
return [expectedType];
}
}
#endif

View File

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

View File

@@ -0,0 +1,43 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysVector2 = System.Numerics.Vector2;
using SysVector3 = System.Numerics.Vector3;
using SysVector4 = System.Numerics.Vector4;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class DivideResolverEditor
: NumericVectorOrQuaternionBinaryResolverEditorBase<DivideResolverResource>
{
public override string DisplayName => "Divide";
public override string ResolverTypeId => "Divide";
protected override Type[] GetFactoryExpectedTypes(Type expectedType)
{
return expectedType == typeof(ForgeVariant128)
? [
typeof(int),
typeof(float),
typeof(double),
typeof(SysVector2),
typeof(SysVector3),
typeof(SysVector4),
typeof(System.Numerics.Quaternion)
]
: [expectedType];
}
protected override Type GetNestedExpectedType(Type expectedType)
{
return expectedType;
}
}
#endif

View File

@@ -0,0 +1 @@
uid://2gj0lejcirb6

View File

@@ -0,0 +1,39 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysPlane = System.Numerics.Plane;
using SysVector3 = System.Numerics.Vector3;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class DotCoordinateResolverEditor
: AsymmetricBinaryNestedResolverEditorBase<DotCoordinateResolverResource>
{
public override string DisplayName => "Dot Coordinate";
public override string ResolverTypeId => "DotCoordinate";
protected override Type[] LeftFactoryExpectedTypes => [typeof(SysPlane)];
protected override Type[] RightFactoryExpectedTypes => [typeof(SysVector3)];
protected override Type LeftNestedExpectedType => typeof(SysPlane);
protected override Type RightNestedExpectedType => typeof(SysVector3);
protected override string LeftTitle => "Plane:";
protected override string RightTitle => "Coordinate:";
public override bool IsCompatibleWith(Type expectedType)
{
return expectedType == typeof(float) || expectedType == typeof(ForgeVariant128);
}
}
#endif

View File

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

View File

@@ -0,0 +1,39 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
using SysPlane = System.Numerics.Plane;
using SysVector3 = System.Numerics.Vector3;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
[Tool]
internal sealed partial class DotNormalResolverEditor
: AsymmetricBinaryNestedResolverEditorBase<DotNormalResolverResource>
{
public override string DisplayName => "Dot Normal";
public override string ResolverTypeId => "DotNormal";
protected override Type[] LeftFactoryExpectedTypes => [typeof(SysPlane)];
protected override Type[] RightFactoryExpectedTypes => [typeof(SysVector3)];
protected override Type LeftNestedExpectedType => typeof(SysPlane);
protected override Type RightNestedExpectedType => typeof(SysVector3);
protected override string LeftTitle => "Plane:";
protected override string RightTitle => "Normal:";
public override bool IsCompatibleWith(Type expectedType)
{
return expectedType == typeof(float) || expectedType == typeof(ForgeVariant128);
}
}
#endif

View File

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

Some files were not shown because too many files have changed in this diff Show More