Merge branch 'FlaxEngine:master' into Nodes
This commit is contained in:
@@ -2,8 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.ContextMenu;
|
||||
@@ -89,188 +87,7 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
};
|
||||
|
||||
internal static class NodesCache
|
||||
{
|
||||
private static readonly object _locker = new object();
|
||||
private static int _version;
|
||||
private static Task _task;
|
||||
private static VisjectCM _taskContextMenu;
|
||||
private static Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache;
|
||||
|
||||
public static void Wait()
|
||||
{
|
||||
_task?.Wait();
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
Wait();
|
||||
|
||||
if (_cache != null && _cache.Count != 0)
|
||||
{
|
||||
OnCodeEditingTypesCleared();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Get(VisjectCM contextMenu)
|
||||
{
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (_cache == null)
|
||||
_cache = new Dictionary<KeyValuePair<string, ushort>, GroupArchetype>();
|
||||
contextMenu.LockChildrenRecursive();
|
||||
|
||||
// Check if has cached groups
|
||||
if (_cache.Count != 0)
|
||||
{
|
||||
// Check if context menu doesn't have the recent cached groups
|
||||
if (!contextMenu.Groups.Any(g => g.Archetypes[0].Tag is int asInt && asInt == _version))
|
||||
{
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
foreach (var g in _cache.Values)
|
||||
contextMenu.AddGroup(g);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove any old groups from context menu
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
|
||||
// Register for scripting types reload
|
||||
Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared;
|
||||
|
||||
// Run caching on an async
|
||||
_task = Task.Run(OnActiveContextMenuShowAsync);
|
||||
_taskContextMenu = contextMenu;
|
||||
}
|
||||
|
||||
contextMenu.UnlockChildrenRecursive();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnActiveContextMenuShowAsync()
|
||||
{
|
||||
Profiler.BeginEvent("Setup Anim Graph Context Menu (async)");
|
||||
|
||||
foreach (var scriptType in Editor.Instance.CodeEditing.All.Get())
|
||||
{
|
||||
if (!SurfaceUtils.IsValidVisualScriptType(scriptType))
|
||||
continue;
|
||||
|
||||
// Skip Newtonsoft.Json stuff
|
||||
var scriptTypeTypeName = scriptType.TypeName;
|
||||
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
|
||||
continue;
|
||||
var scriptTypeName = scriptType.Name;
|
||||
|
||||
// Enum
|
||||
if (scriptType.IsEnum)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
|
||||
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = scriptTypeName;
|
||||
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(243, 156, 18),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Structure
|
||||
if (scriptType.IsValueType)
|
||||
{
|
||||
if (scriptType.IsVoid)
|
||||
continue;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(155, 89, 182),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create Pack node archetype
|
||||
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Pack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
|
||||
// Create Unpack node archetype
|
||||
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Unpack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Add group to context menu (on a main thread)
|
||||
FlaxEngine.Scripting.InvokeOnUpdate(() =>
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
_taskContextMenu.AddGroups(_cache.Values);
|
||||
_taskContextMenu = null;
|
||||
}
|
||||
});
|
||||
|
||||
Profiler.EndEvent();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_task = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnCodeEditingTypesCleared()
|
||||
{
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_cache.Clear();
|
||||
_version++;
|
||||
}
|
||||
|
||||
Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared;
|
||||
}
|
||||
}
|
||||
private static NodesCache _nodesCache = new NodesCache(IterateNodesCache);
|
||||
|
||||
/// <summary>
|
||||
/// The state machine editing context menu.
|
||||
@@ -345,7 +162,7 @@ namespace FlaxEditor.Surface
|
||||
_cmStateMachineMenu = new VisjectCM(new VisjectCM.InitInfo
|
||||
{
|
||||
Groups = StateMachineGroupArchetypes,
|
||||
CanSpawnNode = arch => true,
|
||||
CanSpawnNode = (_, _) => true,
|
||||
});
|
||||
_cmStateMachineMenu.ShowExpanded = true;
|
||||
}
|
||||
@@ -378,9 +195,7 @@ namespace FlaxEditor.Surface
|
||||
// Check if show additional nodes in the current surface context
|
||||
if (activeCM != _cmStateMachineMenu)
|
||||
{
|
||||
Profiler.BeginEvent("Setup Anim Graph Context Menu");
|
||||
NodesCache.Get(activeCM);
|
||||
Profiler.EndEvent();
|
||||
_nodesCache.Get(activeCM);
|
||||
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBox);
|
||||
|
||||
@@ -394,7 +209,86 @@ namespace FlaxEditor.Surface
|
||||
|
||||
private void OnActiveContextMenuVisibleChanged(Control activeCM)
|
||||
{
|
||||
NodesCache.Wait();
|
||||
_nodesCache.Wait();
|
||||
}
|
||||
|
||||
private static void IterateNodesCache(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version)
|
||||
{
|
||||
// Skip Newtonsoft.Json stuff
|
||||
var scriptTypeTypeName = scriptType.TypeName;
|
||||
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
|
||||
return;
|
||||
var scriptTypeName = scriptType.Name;
|
||||
|
||||
// Enum
|
||||
if (scriptType.IsEnum)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
|
||||
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = scriptTypeName;
|
||||
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(243, 156, 18),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
// Structure
|
||||
if (scriptType.IsValueType)
|
||||
{
|
||||
if (scriptType.IsVoid)
|
||||
return;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(155, 89, 182),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create Pack node archetype
|
||||
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Pack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
|
||||
// Create Unpack node archetype
|
||||
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Unpack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -406,9 +300,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
return (nodeArchetype.Flags & NodeFlags.AnimGraph) != 0 && base.CanUseNodeType(nodeArchetype);
|
||||
return (nodeArchetype.Flags & NodeFlags.AnimGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -488,7 +382,7 @@ namespace FlaxEditor.Surface
|
||||
_cmStateMachineTransitionMenu = null;
|
||||
}
|
||||
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||
NodesCache.Wait();
|
||||
_nodesCache.Wait();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
if (nodeArchetype.Title == "Function Input")
|
||||
return true;
|
||||
@@ -44,7 +44,7 @@ namespace FlaxEditor.Surface
|
||||
if (Context == RootContext && nodeArchetype.Title == "Function Output")
|
||||
return true;
|
||||
|
||||
return base.CanUseNodeType(nodeArchetype);
|
||||
return base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -542,9 +542,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
@@ -174,17 +174,17 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Ensure to have unique name
|
||||
var title = StateMachineTitle;
|
||||
@@ -484,7 +484,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
var startPos = PointToParent(ref center);
|
||||
targetState.GetConnectionEndPoint(ref startPos, out var endPos);
|
||||
var color = style.Foreground;
|
||||
StateMachineState.DrawConnection(Surface, ref startPos, ref endPos, ref color);
|
||||
StateMachineState.DrawConnection(ref startPos, ref endPos, ref color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,7 +514,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
StateMachineState.DrawConnection(Surface, ref startPos, ref endPos, ref color);
|
||||
StateMachineState.DrawConnection(ref startPos, ref endPos, ref color);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -680,11 +680,10 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <summary>
|
||||
/// Draws the connection between two state machine nodes.
|
||||
/// </summary>
|
||||
/// <param name="surface">The surface.</param>
|
||||
/// <param name="startPos">The start position.</param>
|
||||
/// <param name="endPos">The end position.</param>
|
||||
/// <param name="color">The line color.</param>
|
||||
public static void DrawConnection(VisjectSurface surface, ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
public static void DrawConnection(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
var sub = endPos - startPos;
|
||||
var length = sub.Length;
|
||||
@@ -695,11 +694,14 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
float rotation = Float2.Dot(dir, Float2.UnitY);
|
||||
if (endPos.X < startPos.X)
|
||||
rotation = 2 - rotation;
|
||||
// TODO: make it look better (fix the math)
|
||||
var arrowTransform = Matrix3x3.Translation2D(new Float2(-16.0f, -8.0f)) * Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) * Matrix3x3.Translation2D(endPos);
|
||||
var sprite = Editor.Instance.Icons.VisjectArrowClosed32;
|
||||
var arrowTransform =
|
||||
Matrix3x3.Translation2D(-6.5f, -8) *
|
||||
Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) *
|
||||
Matrix3x3.Translation2D(endPos - dir * 8);
|
||||
|
||||
Render2D.PushTransform(ref arrowTransform);
|
||||
Render2D.DrawSprite(Editor.Instance.Icons.VisjectArrowClosed32, arrowRect, color);
|
||||
Render2D.DrawSprite(sprite, arrowRect, color);
|
||||
Render2D.PopTransform();
|
||||
|
||||
endPos -= dir * 4.0f;
|
||||
@@ -742,9 +744,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
LoadTransitions();
|
||||
|
||||
@@ -1293,7 +1295,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
isMouseOver = Float2.DistanceSquared(ref mousePosition, ref point) < 25.0f;
|
||||
}
|
||||
var color = isMouseOver ? Color.Wheat : t.LineColor;
|
||||
DrawConnection(Surface, ref t.StartPos, ref t.EndPos, ref color);
|
||||
DrawConnection(ref t.StartPos, ref t.EndPos, ref color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1322,7 +1324,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
DrawConnection(Surface, ref startPos, ref endPos, ref color);
|
||||
DrawConnection(ref startPos, ref endPos, ref color);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1433,9 +1435,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
public override int TransitionsDataIndex => 2;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Ensure to have unique name
|
||||
var title = StateTitle;
|
||||
@@ -1453,9 +1455,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
|
||||
public class Sample : SurfaceNode
|
||||
{
|
||||
private AssetSelect _assetSelect;
|
||||
private Box _assetBox;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Sample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
@@ -49,21 +52,47 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
{
|
||||
_assetSelect = GetChild<AssetSelect>();
|
||||
if (TryGetBox(8, out var box))
|
||||
{
|
||||
_assetBox = box;
|
||||
_assetSelect.Visible = !_assetBox.HasAnyConnection;
|
||||
}
|
||||
UpdateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTitle()
|
||||
{
|
||||
var asset = Editor.Instance.ContentDatabase.Find((Guid)Values[0]);
|
||||
Title = asset?.ShortName ?? "Animation";
|
||||
if (_assetBox != null)
|
||||
Title = _assetBox.HasAnyConnection || asset == null ? "Animation" : asset.ShortName;
|
||||
else
|
||||
Title = asset?.ShortName ?? "Animation";
|
||||
|
||||
var style = Style.Current;
|
||||
Resize(Mathf.Max(230, style.FontLarge.MeasureText(Title).X + 30), 160);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ConnectionTick(Box box)
|
||||
{
|
||||
base.ConnectionTick(box);
|
||||
|
||||
if (_assetBox == null)
|
||||
return;
|
||||
if (box.ID != _assetBox.ID)
|
||||
return;
|
||||
|
||||
_assetSelect.Visible = !box.HasAnyConnection;
|
||||
UpdateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -162,9 +191,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Peek deserialized boxes
|
||||
_blendPoses.Clear();
|
||||
@@ -305,7 +334,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
NodeElementArchetype.Factory.Input(0, "Speed", true, typeof(float), 5, 1),
|
||||
NodeElementArchetype.Factory.Input(1, "Loop", true, typeof(bool), 6, 2),
|
||||
NodeElementArchetype.Factory.Input(2, "Start Position", true, typeof(float), 7, 3),
|
||||
NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 3, 0, typeof(FlaxEngine.Animation)),
|
||||
NodeElementArchetype.Factory.Input(3, "Animation Asset", true, typeof(FlaxEngine.Animation), 8),
|
||||
NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 4, 0, typeof(FlaxEngine.Animation)),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
|
||||
887
Source/Editor/Surface/Archetypes/BehaviorTree.cs
Normal file
887
Source/Editor/Surface/Archetypes/BehaviorTree.cs
Normal file
@@ -0,0 +1,887 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.CustomEditors.Dedicated;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains archetypes for nodes from the Behavior Tree group.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public static class BehaviorTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for Behavior Tree nodes wrapped inside <see cref="SurfaceNode" />.
|
||||
/// </summary>
|
||||
internal class NodeBase : SurfaceNode
|
||||
{
|
||||
protected const float ConnectionAreaMargin = 12.0f;
|
||||
protected const float ConnectionAreaHeight = 12.0f;
|
||||
protected const float DecoratorsMarginX = 5.0f;
|
||||
protected const float DecoratorsMarginY = 2.0f;
|
||||
|
||||
protected bool _debugRelevant;
|
||||
protected string _debugInfo;
|
||||
protected Float2 _debugInfoSize;
|
||||
protected ScriptType _type;
|
||||
internal bool _isValueEditing;
|
||||
|
||||
public BehaviorTreeNode Instance;
|
||||
|
||||
protected NodeBase(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
|
||||
public static string GetTitle(ScriptType scriptType)
|
||||
{
|
||||
var title = scriptType.Name;
|
||||
if (title.StartsWith("BehaviorTree"))
|
||||
title = title.Substring(12);
|
||||
if (title.EndsWith("Node"))
|
||||
title = title.Substring(0, title.Length - 4);
|
||||
if (title.EndsWith("Decorator"))
|
||||
title = title.Substring(0, title.Length - 9);
|
||||
title = Utilities.Utils.GetPropertyNameUI(title);
|
||||
return title;
|
||||
}
|
||||
|
||||
public virtual void UpdateDebug(Behavior behavior)
|
||||
{
|
||||
BehaviorTreeNode instance = null;
|
||||
if (behavior)
|
||||
{
|
||||
// Try to use instance from the currently debugged behavior
|
||||
// TODO: support nodes from nested trees
|
||||
instance = behavior.Tree.GetNodeInstance(ID);
|
||||
}
|
||||
var size = _debugInfoSize;
|
||||
UpdateDebugInfo(instance, behavior);
|
||||
if (size != _debugInfoSize)
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
protected virtual void UpdateTitle()
|
||||
{
|
||||
string title = null;
|
||||
if (Instance != null)
|
||||
{
|
||||
title = Instance.Name;
|
||||
if (string.IsNullOrEmpty(title))
|
||||
title = GetTitle(_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
var typeName = (string)Values[0];
|
||||
title = "Missing Type " + typeName;
|
||||
}
|
||||
Title = title;
|
||||
}
|
||||
|
||||
protected virtual void UpdateDebugInfo(BehaviorTreeNode instance = null, Behavior behavior = null)
|
||||
{
|
||||
_debugRelevant = false;
|
||||
_debugInfo = null;
|
||||
_debugInfoSize = Float2.Zero;
|
||||
if (!instance)
|
||||
instance = Instance;
|
||||
if (instance)
|
||||
{
|
||||
// Get debug description for the node based on the current settings
|
||||
_debugRelevant = Behavior.GetNodeDebugRelevancy(instance, behavior);
|
||||
_debugInfo = Behavior.GetNodeDebugInfo(instance, behavior);
|
||||
if (!string.IsNullOrEmpty(_debugInfo))
|
||||
_debugInfoSize = Style.Current.FontSmall.MeasureText(_debugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Setup node type and data
|
||||
var typeName = (string)Values[0];
|
||||
_type = TypeUtils.GetType(typeName);
|
||||
if (_type != null)
|
||||
{
|
||||
TooltipText = Editor.Instance.CodeDocs.GetTooltip(_type);
|
||||
try
|
||||
{
|
||||
// Load node instance from data
|
||||
Instance = (BehaviorTreeNode)_type.CreateInstance();
|
||||
var instanceData = (byte[])Values[1];
|
||||
FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogError("Failed to load Behavior Tree node of type " + typeName);
|
||||
Editor.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
UpdateDebugInfo();
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
// Skip updating instance when it's being edited by user via UI
|
||||
if (!_isValueEditing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
// Reload node instance from data
|
||||
var instanceData = (byte[])Values[1];
|
||||
if (instanceData == null || instanceData.Length == 0)
|
||||
{
|
||||
// Recreate instance data to default state if previous state was empty
|
||||
var defaultInstance = (BehaviorTreeNode)_type.CreateInstance(); // TODO: use default instance from native ScriptingType
|
||||
instanceData = FlaxEngine.Json.JsonSerializer.SaveToBytes(defaultInstance);
|
||||
}
|
||||
FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, instanceData, Globals.EngineBuildNumber);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Editor.LogError("Failed to load Behavior Tree node of type " + _type);
|
||||
Editor.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateDebugInfo();
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned(action);
|
||||
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
// Debug Info
|
||||
if (!string.IsNullOrEmpty(_debugInfo))
|
||||
{
|
||||
var style = Style.Current;
|
||||
Render2D.DrawText(style.FontSmall, _debugInfo, new Rectangle(4, _headerRect.Bottom + 4, _debugInfoSize), style.Foreground);
|
||||
}
|
||||
|
||||
// Debug relevancy outline
|
||||
if (_debugRelevant)
|
||||
{
|
||||
var colorTop = Color.LightYellow;
|
||||
var colorBottom = Color.Yellow;
|
||||
var backgroundRect = new Rectangle(Float2.One, Size - new Float2(2.0f));
|
||||
Render2D.DrawRectangle(backgroundRect, colorTop, colorTop, colorBottom, colorBottom);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
_debugInfo = null;
|
||||
_type = ScriptType.Null;
|
||||
FlaxEngine.Object.Destroy(ref Instance);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customized <see cref="SurfaceNode" /> for the Behavior Tree node.
|
||||
/// </summary>
|
||||
internal class Node : NodeBase
|
||||
{
|
||||
private InputBox _input;
|
||||
private OutputBox _output;
|
||||
internal List<Decorator> _decorators;
|
||||
|
||||
internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
{
|
||||
return new Node(id, context, nodeArch, groupArch);
|
||||
}
|
||||
|
||||
internal Node(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
|
||||
public unsafe List<uint> DecoratorIds
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = new List<uint>();
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
for (int i = 0; i < count; i++)
|
||||
result.Add(ptr[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
set => SetDecoratorIds(value, true);
|
||||
}
|
||||
|
||||
public unsafe List<Decorator> Decorators
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_decorators == null)
|
||||
{
|
||||
_decorators = new List<Decorator>();
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var decorator = Surface.FindNode(ptr[i]) as Decorator;
|
||||
if (decorator != null)
|
||||
_decorators.Add(decorator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _decorators;
|
||||
}
|
||||
set
|
||||
{
|
||||
_decorators = null;
|
||||
var ids = new byte[sizeof(uint) * value.Count];
|
||||
if (value != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
for (var i = 0; i < value.Count; i++)
|
||||
ptr[i] = value[i].ID;
|
||||
}
|
||||
}
|
||||
SetValue(2, ids);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetDecoratorIds(List<uint> value, bool withUndo)
|
||||
{
|
||||
var ids = new byte[sizeof(uint) * value.Count];
|
||||
if (value != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
for (var i = 0; i < value.Count; i++)
|
||||
ptr[i] = value[i];
|
||||
}
|
||||
}
|
||||
if (withUndo)
|
||||
SetValue(2, ids);
|
||||
else
|
||||
{
|
||||
Values[2] = ids;
|
||||
OnValuesChanged();
|
||||
Surface?.MarkAsEdited();
|
||||
}
|
||||
}
|
||||
|
||||
public override unsafe SurfaceNode[] SealedNodes
|
||||
{
|
||||
get
|
||||
{
|
||||
// Return decorator nodes attached to this node to be moved/copied/pasted as a one
|
||||
SurfaceNode[] result = null;
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
result = new SurfaceNode[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var decorator = Surface.FindNode(ptr[i]) as Decorator;
|
||||
if (decorator != null)
|
||||
result[i] = decorator;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnShowSecondaryContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu menu, Float2 location)
|
||||
{
|
||||
base.OnShowSecondaryContextMenu(menu, location);
|
||||
|
||||
if (!Surface.CanEdit)
|
||||
return;
|
||||
|
||||
menu.AddSeparator();
|
||||
|
||||
var nodeTypes = Editor.Instance.CodeEditing.BehaviorTreeNodes.Get();
|
||||
|
||||
if (_input.Enabled) // Root node cannot have decorators
|
||||
{
|
||||
var decorators = menu.AddChildMenu("Add Decorator");
|
||||
var decoratorType = new ScriptType(typeof(BehaviorTreeDecorator));
|
||||
foreach (var nodeType in nodeTypes)
|
||||
{
|
||||
if (nodeType != decoratorType && decoratorType.IsAssignableFrom(nodeType))
|
||||
{
|
||||
var button = decorators.ContextMenu.AddButton(GetTitle(nodeType));
|
||||
button.Tag = nodeType;
|
||||
button.TooltipText = Editor.Instance.CodeDocs.GetTooltip(nodeType);
|
||||
button.ButtonClicked += OnAddDecoratorButtonClicked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddDecoratorButtonClicked(ContextMenuButton button)
|
||||
{
|
||||
var nodeType = (ScriptType)button.Tag;
|
||||
|
||||
// Spawn decorator
|
||||
var decorator = Context.SpawnNode(19, 3, Location, new object[]
|
||||
{
|
||||
nodeType.TypeName,
|
||||
Utils.GetEmptyArray<byte>(),
|
||||
});
|
||||
|
||||
// Add decorator to the node
|
||||
var decorators = Decorators;
|
||||
decorators.Add((Decorator)decorator);
|
||||
Decorators = decorators;
|
||||
}
|
||||
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
// Reject cached value
|
||||
_decorators = null;
|
||||
|
||||
base.OnValuesChanged();
|
||||
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Setup boxes
|
||||
_input = (InputBox)GetBox(0);
|
||||
_output = (OutputBox)GetBox(1);
|
||||
_input.ConnectionOffset = new Float2(0, FlaxEditor.Surface.Constants.BoxSize * -0.5f);
|
||||
_output.ConnectionOffset = new Float2(0, FlaxEditor.Surface.Constants.BoxSize * 0.5f);
|
||||
|
||||
// Setup node type and data
|
||||
var flagsRoot = NodeFlags.NoRemove | NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaPaste;
|
||||
var flags = Archetype.Flags & ~flagsRoot;
|
||||
if (_type != null)
|
||||
{
|
||||
bool isRoot = _type.Type == typeof(BehaviorTreeRootNode);
|
||||
_input.Enabled = _input.Visible = !isRoot;
|
||||
_output.Enabled = _output.Visible = new ScriptType(typeof(BehaviorTreeCompoundNode)).IsAssignableFrom(_type);
|
||||
if (isRoot)
|
||||
flags |= flagsRoot;
|
||||
}
|
||||
if (Archetype.Flags != flags)
|
||||
{
|
||||
// Apply custom flags
|
||||
Archetype = (NodeArchetype)Archetype.Clone();
|
||||
Archetype.Flags = flags;
|
||||
}
|
||||
|
||||
ResizeAuto();
|
||||
}
|
||||
|
||||
public override unsafe void OnPasted(Dictionary<uint, uint> idsMapping)
|
||||
{
|
||||
base.OnPasted(idsMapping);
|
||||
|
||||
// Update decorators
|
||||
var ids = Values.Length >= 3 ? Values[2] as byte[] : null;
|
||||
if (ids != null)
|
||||
{
|
||||
_decorators = null;
|
||||
fixed (byte* data = ids)
|
||||
{
|
||||
uint* ptr = (uint*)data;
|
||||
int count = ids.Length / sizeof(uint);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (idsMapping.TryGetValue(ptr[i], out var id))
|
||||
{
|
||||
// Fix previous parent node to re-apply layout (in case it was forced by spawned decorator)
|
||||
var decorator = Surface.FindNode(ptr[i]) as Decorator;
|
||||
var decoratorNode = decorator?.Node;
|
||||
if (decoratorNode != null)
|
||||
decoratorNode.ResizeAuto();
|
||||
|
||||
// Update mapping to the new node
|
||||
ptr[i] = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
Values[2] = ids;
|
||||
ResizeAuto();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
ResizeAuto();
|
||||
Surface.NodeDeleted += OnNodeDeleted;
|
||||
}
|
||||
|
||||
private void OnNodeDeleted(SurfaceNode node)
|
||||
{
|
||||
if (node is Decorator decorator && Decorators.Contains(decorator))
|
||||
{
|
||||
// Decorator was spawned (eg. via undo)
|
||||
_decorators = null;
|
||||
ResizeAuto();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ResizeAuto()
|
||||
{
|
||||
if (Surface == null)
|
||||
return;
|
||||
var width = 0.0f;
|
||||
var height = 0.0f;
|
||||
var titleLabelFont = Style.Current.FontLarge;
|
||||
width = Mathf.Max(width, 100.0f);
|
||||
width = Mathf.Max(width, titleLabelFont.MeasureText(Title).X + 30);
|
||||
if (_debugInfoSize.X > 0)
|
||||
{
|
||||
width = Mathf.Max(width, _debugInfoSize.X + 8.0f);
|
||||
height += _debugInfoSize.Y + 8.0f;
|
||||
}
|
||||
if (_input != null && _input.Visible)
|
||||
height += ConnectionAreaHeight;
|
||||
if (_output != null && _output.Visible)
|
||||
height += ConnectionAreaHeight;
|
||||
var decorators = Decorators;
|
||||
foreach (var decorator in decorators)
|
||||
{
|
||||
decorator.ResizeAuto();
|
||||
height += decorator.Height + DecoratorsMarginY;
|
||||
width = Mathf.Max(width, decorator.Width - FlaxEditor.Surface.Constants.NodeCloseButtonSize - 2 * DecoratorsMarginX);
|
||||
}
|
||||
Size = new Float2(width + FlaxEditor.Surface.Constants.NodeMarginX * 2 + FlaxEditor.Surface.Constants.NodeCloseButtonSize, height + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.NodeFooterSize);
|
||||
UpdateRectangles();
|
||||
}
|
||||
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
Rectangle bounds = Bounds;
|
||||
if (_input != null && _input.Visible)
|
||||
{
|
||||
_input.Bounds = new Rectangle(ConnectionAreaMargin, 0, Width - ConnectionAreaMargin * 2, ConnectionAreaHeight);
|
||||
bounds.Location.Y += _input.Height;
|
||||
}
|
||||
var decorators = Decorators;
|
||||
var indexInParent = IndexInParent;
|
||||
foreach (var decorator in decorators)
|
||||
{
|
||||
decorator.Bounds = new Rectangle(bounds.Location.X + DecoratorsMarginX, bounds.Location.Y, bounds.Width - 2 * DecoratorsMarginX, decorator.Height);
|
||||
bounds.Location.Y += decorator.Height + DecoratorsMarginY;
|
||||
if (decorator.IndexInParent < indexInParent)
|
||||
decorator.IndexInParent = indexInParent + 1; // Push elements above the node
|
||||
}
|
||||
const float footerSize = FlaxEditor.Surface.Constants.NodeFooterSize;
|
||||
const float headerSize = FlaxEditor.Surface.Constants.NodeHeaderSize;
|
||||
const float closeButtonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
|
||||
const float closeButtonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
|
||||
_headerRect = new Rectangle(0, bounds.Y - Y, bounds.Width, headerSize);
|
||||
_closeButtonRect = new Rectangle(bounds.Width - closeButtonSize - closeButtonMargin, _headerRect.Y + closeButtonMargin, closeButtonSize, closeButtonSize);
|
||||
_footerRect = new Rectangle(0, bounds.Height - footerSize, bounds.Width, footerSize);
|
||||
if (_output != null && _output.Visible)
|
||||
{
|
||||
_footerRect.Y -= ConnectionAreaHeight;
|
||||
_output.Bounds = new Rectangle(ConnectionAreaMargin, bounds.Height - ConnectionAreaHeight, bounds.Width - ConnectionAreaMargin * 2, ConnectionAreaHeight);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLocationChanged()
|
||||
{
|
||||
base.OnLocationChanged();
|
||||
|
||||
// Sync attached elements placement
|
||||
UpdateRectangles();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customized <see cref="SurfaceNode" /> for the Behavior Tree decorator.
|
||||
/// </summary>
|
||||
internal class Decorator : NodeBase
|
||||
{
|
||||
private sealed class DragDecorator : DragHelper<uint, DragEventArgs>
|
||||
{
|
||||
public const string DragPrefix = "DECORATOR!?";
|
||||
|
||||
public DragDecorator(Func<uint, bool> validateFunction)
|
||||
: base(validateFunction)
|
||||
{
|
||||
}
|
||||
|
||||
public override DragData ToDragData(uint item) => new DragDataText(DragPrefix + item);
|
||||
|
||||
public override DragData ToDragData(IEnumerable<uint> items)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IEnumerable<uint> FromDragData(DragData data)
|
||||
{
|
||||
if (data is DragDataText dataText)
|
||||
{
|
||||
if (dataText.Text.StartsWith(DragPrefix))
|
||||
{
|
||||
var id = dataText.Text.Remove(0, DragPrefix.Length).Split('\n');
|
||||
return new[] { uint.Parse(id[0]) };
|
||||
}
|
||||
}
|
||||
return Utils.GetEmptyArray<uint>();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ReorderDecoratorAction : IUndoAction
|
||||
{
|
||||
public VisjectSurface Surface;
|
||||
public uint DecoratorId, PrevNodeId, NewNodeId;
|
||||
public int PrevIndex, NewIndex;
|
||||
|
||||
public string ActionString => "Reorder decorator";
|
||||
|
||||
private void Do(uint nodeId, int index)
|
||||
{
|
||||
var decorator = Surface.FindNode(DecoratorId) as Decorator;
|
||||
if (decorator == null)
|
||||
throw new Exception("Missing decorator");
|
||||
var node = decorator.Node;
|
||||
var decorators = node.DecoratorIds;
|
||||
decorators.Remove(DecoratorId);
|
||||
if (node.ID != nodeId)
|
||||
{
|
||||
node.SetDecoratorIds(decorators, false);
|
||||
node = Surface.FindNode(nodeId) as Node;
|
||||
decorators = node.DecoratorIds;
|
||||
}
|
||||
if (index < 0 || index >= decorators.Count)
|
||||
decorators.Add(DecoratorId);
|
||||
else
|
||||
decorators.Insert(index, DecoratorId);
|
||||
node.SetDecoratorIds(decorators, false);
|
||||
}
|
||||
|
||||
public void Do()
|
||||
{
|
||||
Do(NewNodeId, NewIndex);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
Do(PrevNodeId, PrevIndex);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Surface = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static SurfaceNode Create(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
{
|
||||
return new Decorator(id, context, nodeArch, groupArch);
|
||||
}
|
||||
|
||||
private DragImage _dragIcon;
|
||||
private DragDecorator _dragDecorator;
|
||||
private float _dragLocation = -1;
|
||||
|
||||
internal Decorator(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
_dragDecorator = new DragDecorator(ValidateDrag);
|
||||
}
|
||||
|
||||
public Node Node
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var node in Surface.Nodes)
|
||||
{
|
||||
if (node is Node n && n.DecoratorIds.Contains(ID))
|
||||
return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Color FooterColor => Color.Transparent;
|
||||
|
||||
protected override Float2 CalculateNodeSize(float width, float height)
|
||||
{
|
||||
if (_debugInfoSize.X > 0)
|
||||
{
|
||||
width = Mathf.Max(width, _debugInfoSize.X + 8.0f);
|
||||
height += _debugInfoSize.Y + 8.0f;
|
||||
}
|
||||
return new Float2(width + FlaxEditor.Surface.Constants.NodeCloseButtonSize * 2 + DecoratorsMarginX * 2, height + FlaxEditor.Surface.Constants.NodeHeaderSize);
|
||||
}
|
||||
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
base.UpdateRectangles();
|
||||
|
||||
_footerRect = Rectangle.Empty;
|
||||
if (_dragIcon != null)
|
||||
_dragIcon.Bounds = new Rectangle(_closeButtonRect.X - _closeButtonRect.Width, _closeButtonRect.Y, _closeButtonRect.Size);
|
||||
}
|
||||
|
||||
protected override void UpdateTitle()
|
||||
{
|
||||
// Update parent node on title change
|
||||
var title = Title;
|
||||
base.UpdateTitle();
|
||||
if (title != Title)
|
||||
Node?.ResizeAuto();
|
||||
}
|
||||
|
||||
protected override void UpdateDebugInfo(BehaviorTreeNode instance, Behavior behavior)
|
||||
{
|
||||
// Update parent node on debug text change
|
||||
var debugInfoSize = _debugInfoSize;
|
||||
base.UpdateDebugInfo(instance, behavior);
|
||||
if (debugInfoSize != _debugInfoSize)
|
||||
Node?.ResizeAuto();
|
||||
}
|
||||
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
// Add drag button to reorder decorator
|
||||
_dragIcon = new DragImage
|
||||
{
|
||||
AnchorPreset = AnchorPresets.TopRight,
|
||||
Color = Style.Current.ForegroundGrey,
|
||||
Parent = this,
|
||||
Margin = new Margin(1),
|
||||
Visible = Surface.CanEdit,
|
||||
Brush = new SpriteBrush(Editor.Instance.Icons.DragBar12),
|
||||
Tag = this,
|
||||
Drag = img => { img.DoDragDrop(_dragDecorator.ToDragData(ID)); }
|
||||
};
|
||||
|
||||
base.OnLoaded(action);
|
||||
}
|
||||
|
||||
private bool ValidateDrag(uint id)
|
||||
{
|
||||
return Surface.FindNode(id) != null;
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = Style.Current;
|
||||
|
||||
// Outline
|
||||
if (!_isSelected)
|
||||
{
|
||||
var rect = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.DrawRectangle(rect, style.BorderHighlighted);
|
||||
}
|
||||
|
||||
// Drag hint
|
||||
if (IsDragOver && _dragDecorator.HasValidDrag)
|
||||
{
|
||||
var rect = new Rectangle(0, _dragLocation < Height * 0.5f ? 0 : Height - 6, Width, 6);
|
||||
Render2D.FillRectangle(rect, style.BackgroundSelected);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
if (action == SurfaceNodeActions.Undo)
|
||||
{
|
||||
// Update parent node layout when restoring decorator from undo
|
||||
var node = Node;
|
||||
if (node != null)
|
||||
{
|
||||
node._decorators = null;
|
||||
node.ResizeAuto();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceCanEditChanged(bool canEdit)
|
||||
{
|
||||
base.OnSurfaceCanEditChanged(canEdit);
|
||||
|
||||
if (_dragIcon != null)
|
||||
_dragIcon.Visible = canEdit;
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragEnter(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
if (_dragDecorator.OnDragEnter(data))
|
||||
{
|
||||
_dragLocation = location.Y;
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragMove(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
if (_dragDecorator.HasValidDrag)
|
||||
{
|
||||
_dragLocation = location.Y;
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_dragLocation = -1;
|
||||
_dragDecorator.OnDragLeave();
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragDrop(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
if (_dragDecorator.HasValidDrag)
|
||||
{
|
||||
// Reorder or reparent decorator
|
||||
var decorator = (Decorator)Surface.FindNode(_dragDecorator.Objects[0]);
|
||||
var prevNode = decorator.Node;
|
||||
var prevIndex = prevNode.Decorators.IndexOf(decorator);
|
||||
var newNode = Node;
|
||||
var newIndex = newNode.Decorators.IndexOf(this);
|
||||
if (_dragLocation >= Height * 0.5f)
|
||||
newIndex++;
|
||||
if (prevIndex != newIndex || prevNode != newNode)
|
||||
{
|
||||
var action = new ReorderDecoratorAction
|
||||
{
|
||||
Surface = Surface,
|
||||
DecoratorId = decorator.ID,
|
||||
PrevNodeId = prevNode.ID,
|
||||
PrevIndex = prevIndex,
|
||||
NewNodeId = newNode.ID,
|
||||
NewIndex = newIndex,
|
||||
};
|
||||
action.Do();
|
||||
Surface.Undo?.AddAction(action);
|
||||
}
|
||||
_dragLocation = -1;
|
||||
_dragDecorator.OnDragDrop();
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The nodes for that group.
|
||||
/// </summary>
|
||||
public static NodeArchetype[] Nodes =
|
||||
{
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 1, // Task Node
|
||||
Create = Node.Create,
|
||||
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
string.Empty, // Type Name
|
||||
Utils.GetEmptyArray<byte>(), // Instance Data
|
||||
null, // List of Decorator Nodes IDs
|
||||
},
|
||||
Size = new Float2(100, 0),
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Input(0, string.Empty, true, ScriptType.Void, 0),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, ScriptType.Void, 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 2, // Root Node
|
||||
Create = Node.Create,
|
||||
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
typeof(BehaviorTreeRootNode).FullName, // Root node
|
||||
Utils.GetEmptyArray<byte>(), // Instance Data
|
||||
},
|
||||
Size = new Float2(100, 0),
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Input(0, string.Empty, true, ScriptType.Void, 0),
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, ScriptType.Void, 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 3, // Decorator Node
|
||||
Create = Decorator.Create,
|
||||
Flags = NodeFlags.BehaviorTreeGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoMove,
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
string.Empty, // Type Name
|
||||
Utils.GetEmptyArray<byte>(), // Instance Data
|
||||
},
|
||||
Size = new Float2(100, 0),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -50,9 +50,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Restore saved input boxes layout
|
||||
if (Values[0] is byte[] data)
|
||||
@@ -62,9 +62,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateBoxes();
|
||||
GetBox(0).CurrentTypeChanged += box => UpdateBoxes();
|
||||
|
||||
@@ -43,9 +43,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
box.CurrentType = new ScriptType(Values[0].GetType());
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
var box = (OutputBox)GetBox(0);
|
||||
if (Values[0] == null)
|
||||
@@ -100,9 +100,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
base.OnValuesChanged();
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
_output = (OutputBox)Elements[0];
|
||||
_typePicker = new TypePickerControl
|
||||
@@ -238,9 +238,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
base.OnValuesChanged();
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
_output = (OutputBox)Elements[0];
|
||||
_keyTypePicker = new TypePickerControl
|
||||
|
||||
@@ -25,9 +25,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Restore saved output boxes layout
|
||||
var count = (int)Values[0];
|
||||
@@ -35,9 +35,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
AddBox(true, i + 1, i, string.Empty, new ScriptType(typeof(void)), true);
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_removeButton = new Button(0, 0, 20, 20)
|
||||
{
|
||||
@@ -107,9 +107,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Restore saved output boxes layout
|
||||
if (Values[0] is byte[] data)
|
||||
@@ -119,9 +119,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateBoxes();
|
||||
GetBox(1).CurrentTypeChanged += box => UpdateBoxes();
|
||||
|
||||
@@ -54,9 +54,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
protected abstract Asset LoadSignature(Guid id, out string[] types, out string[] names);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
FlaxEngine.Content.AssetReloading += OnAssetReloading;
|
||||
FlaxEngine.Content.AssetDisposing += OnContentAssetDisposing;
|
||||
@@ -275,17 +275,17 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_nameField.Text = SignatureName;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Ensure to have unique name
|
||||
var name = SignatureName;
|
||||
@@ -397,9 +397,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_outputBox = GetBox(0);
|
||||
_outputBox.CurrentType = SignatureType;
|
||||
@@ -466,9 +466,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_inputBox = GetBox(0);
|
||||
_inputBox.CurrentType = SignatureType;
|
||||
@@ -634,18 +634,18 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
protected override Color FooterColor => new Color(200, 11, 112);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
Title = SurfaceUtils.GetMethodDisplayName((string)Values[0]);
|
||||
UpdateSignature();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Update the boxes connection types to match the signature
|
||||
// Do it after surface load so connections can receive update on type changes of the method parameter
|
||||
@@ -663,9 +663,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
var method = GetMethod();
|
||||
_parameters = null;
|
||||
@@ -745,12 +745,12 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return inputType.IsVoid;
|
||||
}
|
||||
@@ -1009,9 +1009,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
var method = GetMethod(out _, out _, out var parameters);
|
||||
if (method && parameters != null)
|
||||
@@ -1038,18 +1038,18 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
Title = SurfaceUtils.GetMethodDisplayName((string)Values[1]);
|
||||
UpdateSignature();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Update the boxes connection types to match the signature
|
||||
// Do it after surface load so connections can receive update on type changes of the method parameter
|
||||
@@ -1162,7 +1162,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
|
||||
return false;
|
||||
@@ -1188,7 +1188,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
|
||||
return false;
|
||||
@@ -1240,9 +1240,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
UpdateSignature();
|
||||
}
|
||||
@@ -1758,9 +1758,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
LoadSignature();
|
||||
|
||||
@@ -1773,9 +1773,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Setup initial signature
|
||||
var defaultSignature = _signature.Node == null;
|
||||
@@ -1801,7 +1801,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDeleted()
|
||||
public override void OnDeleted(SurfaceNodeActions action)
|
||||
{
|
||||
// Send event
|
||||
for (int i = 0; i < Surface.Nodes.Count; i++)
|
||||
@@ -1810,7 +1810,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
node.OnFunctionDeleted(this);
|
||||
}
|
||||
|
||||
base.OnDeleted();
|
||||
base.OnDeleted(action);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1836,12 +1836,12 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return inputType.IsVoid;
|
||||
}
|
||||
@@ -1974,15 +1974,15 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
Title = "Get " + SurfaceUtils.GetMethodDisplayName((string)Values[1]);
|
||||
UpdateSignature();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
|
||||
if (scriptType == ScriptType.Null)
|
||||
@@ -2011,7 +2011,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
|
||||
if (scriptType == ScriptType.Null)
|
||||
@@ -2085,15 +2085,15 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
Title = "Set " + SurfaceUtils.GetMethodDisplayName((string)Values[1]);
|
||||
UpdateSignature();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (outputType.IsVoid)
|
||||
return true;
|
||||
@@ -2130,7 +2130,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return inputType.IsVoid;
|
||||
}
|
||||
@@ -2303,9 +2303,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Find reflection information about event
|
||||
_signature = null;
|
||||
@@ -2353,7 +2353,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
// Event based nodes always have a pulse input, so it's always compatible with void
|
||||
if (outputType.IsVoid)
|
||||
@@ -2373,7 +2373,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
// Event based nodes always have a pulse output, so it's always compatible with void
|
||||
if (inputType.IsVoid)
|
||||
@@ -2398,11 +2398,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
Title = "Bind " + (string)Values[1];
|
||||
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2413,11 +2413,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
Title = "Unbind " + (string)Values[1];
|
||||
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Size = new Float2(200, 100),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
0.0f,
|
||||
0.5f,
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
|
||||
@@ -197,9 +197,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Fix emissive box (it's a strange error)
|
||||
GetBox(3).CurrentType = new ScriptType(typeof(Float3));
|
||||
|
||||
@@ -30,9 +30,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_in0 = (InputBox)GetBox(0);
|
||||
_in1 = (InputBox)GetBox(1);
|
||||
@@ -111,9 +111,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
// Update title and the tooltip
|
||||
var typeName = (string)Values[0];
|
||||
@@ -217,7 +217,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var typeName = (string)nodeArch.DefaultValues[0];
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
@@ -234,7 +234,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var typeName = (string)nodeArch.DefaultValues[0];
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
@@ -255,7 +255,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var typeName = (string)nodeArch.DefaultValues[0];
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
@@ -267,7 +267,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
var typeName = (string)nodeArch.DefaultValues[0];
|
||||
var type = TypeUtils.GetType(typeName);
|
||||
|
||||
@@ -426,9 +426,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
{
|
||||
@@ -438,9 +438,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateTitle();
|
||||
}
|
||||
@@ -490,6 +490,56 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_combobox.Width = Width - 30;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (inputType == ScriptType.Object)
|
||||
return true;
|
||||
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
if (parameter == null || type == ScriptType.Null || parameter.Type.Type == null)
|
||||
return false;
|
||||
if (DefaultPrototypes == null || !DefaultPrototypes.TryGetValue(parameter.Type.Type, out var elements))
|
||||
{
|
||||
return VisjectSurface.FullCastCheck(inputType, type, hint);
|
||||
}
|
||||
if (elements == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < elements.Length; i++)
|
||||
{
|
||||
if (elements[i].Type != NodeElementType.Output)
|
||||
continue;
|
||||
|
||||
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
if (parameter == null || type == ScriptType.Null)
|
||||
return false;
|
||||
if (parameter.Type.Type == null || DefaultPrototypes == null || !DefaultPrototypes.TryGetValue(parameter.Type.Type, out var elements))
|
||||
return false;
|
||||
if (elements == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < elements.Length; i++)
|
||||
{
|
||||
if (elements[i].Type != NodeElementType.Input)
|
||||
continue;
|
||||
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -675,6 +725,53 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool UseNormalMaps => false;
|
||||
|
||||
internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (inputType == ScriptType.Object)
|
||||
return true;
|
||||
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
if (parameter == null || type == ScriptType.Null)
|
||||
return false;
|
||||
if (parameter.Type.Type == null || DefaultPrototypesParticleEmitter == null || !DefaultPrototypesParticleEmitter.TryGetValue(parameter.Type.Type, out var elements))
|
||||
return false;
|
||||
if (elements == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < elements.Length; i++)
|
||||
{
|
||||
if (elements[i].Type != NodeElementType.Output)
|
||||
continue;
|
||||
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal new static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
if (parameter == null || type == ScriptType.Null)
|
||||
return false;
|
||||
if (parameter.Type.Type == null || DefaultPrototypesParticleEmitter == null || !DefaultPrototypesParticleEmitter.TryGetValue(parameter.Type.Type, out var elements))
|
||||
return false;
|
||||
if (elements == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < elements.Length; i++)
|
||||
{
|
||||
if (elements[i].Type != NodeElementType.Input)
|
||||
continue;
|
||||
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -692,6 +789,22 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool UseNormalMaps => false;
|
||||
|
||||
internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (inputType == ScriptType.Object)
|
||||
return true;
|
||||
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
return VisjectSurface.FullCastCheck(inputType, type, hint);
|
||||
}
|
||||
|
||||
internal new static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -832,9 +945,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
UpdateCombo();
|
||||
@@ -842,9 +955,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
{
|
||||
@@ -874,6 +987,22 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_combobox.Width = Width - 50;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
return inputType == ScriptType.Void;
|
||||
}
|
||||
|
||||
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
|
||||
{
|
||||
if (outputType == ScriptType.Void)
|
||||
return true;
|
||||
|
||||
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
|
||||
ScriptType type = parameter?.Type ?? ScriptType.Null;
|
||||
|
||||
return VisjectSurface.FullCastCheck(outputType, type, hint);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -885,6 +1014,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 1,
|
||||
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGet(id, context, arch, groupArch),
|
||||
IsInputCompatible = SurfaceNodeParamsGet.IsInputCompatible,
|
||||
IsOutputCompatible = SurfaceNodeParamsGet.IsOutputCompatible,
|
||||
Title = "Get Parameter",
|
||||
Description = "Parameter value getter",
|
||||
Flags = NodeFlags.MaterialGraph | NodeFlags.AnimGraph,
|
||||
@@ -902,6 +1033,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 2,
|
||||
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGetParticleEmitter(id, context, arch, groupArch),
|
||||
IsInputCompatible = SurfaceNodeParamsGetParticleEmitter.IsInputCompatible,
|
||||
IsOutputCompatible = SurfaceNodeParamsGetParticleEmitter.IsOutputCompatible,
|
||||
Title = "Get Parameter",
|
||||
Description = "Parameter value getter",
|
||||
Flags = NodeFlags.ParticleEmitterGraph,
|
||||
@@ -919,6 +1052,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 3,
|
||||
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGetVisualScript(id, context, arch, groupArch),
|
||||
IsInputCompatible = SurfaceNodeParamsGetVisualScript.IsInputCompatible,
|
||||
IsOutputCompatible = SurfaceNodeParamsGetVisualScript.IsOutputCompatible,
|
||||
Title = "Get Parameter",
|
||||
Description = "Parameter value getter",
|
||||
Flags = NodeFlags.VisualScriptGraph,
|
||||
@@ -936,6 +1071,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
TypeID = 4,
|
||||
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsSet(id, context, arch, groupArch),
|
||||
IsInputCompatible = SurfaceNodeParamsSet.IsInputCompatible,
|
||||
IsOutputCompatible = SurfaceNodeParamsSet.IsOutputCompatible,
|
||||
Title = "Set Parameter",
|
||||
Description = "Parameter value setter",
|
||||
Flags = NodeFlags.VisualScriptGraph,
|
||||
|
||||
@@ -268,20 +268,20 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
_enabled.Checked = ModuleEnabled;
|
||||
_enabled.StateChanged += OnEnabledStateChanged;
|
||||
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
ParticleSurface?.ArrangeModulesNodes();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
ParticleSurface.ArrangeModulesNodes();
|
||||
}
|
||||
@@ -295,11 +295,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDeleted()
|
||||
public override void OnDeleted(SurfaceNodeActions action)
|
||||
{
|
||||
ParticleSurface.ArrangeModulesNodes();
|
||||
|
||||
base.OnDeleted();
|
||||
base.OnDeleted(action);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -324,9 +324,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateOutputBoxType();
|
||||
}
|
||||
@@ -381,9 +381,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateInputBox();
|
||||
}
|
||||
@@ -416,9 +416,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateTextBox();
|
||||
}
|
||||
|
||||
@@ -214,9 +214,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
if (Surface == null)
|
||||
return;
|
||||
@@ -265,9 +265,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateOutputBoxType();
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
@@ -193,9 +193,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
var upperLeft = GetBox(0).BottomLeft;
|
||||
var upperRight = GetBox(1).BottomRight;
|
||||
@@ -477,9 +477,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
var upperLeft = GetBox(0).BottomLeft;
|
||||
var upperRight = GetBox(1).BottomRight;
|
||||
@@ -657,9 +657,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
UpdateCombo();
|
||||
}
|
||||
@@ -682,9 +682,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
var type = ScriptType.Null;
|
||||
if (Context.Surface is VisualScriptSurface visjectSurface)
|
||||
@@ -710,9 +710,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
UpdateOutputBox();
|
||||
}
|
||||
@@ -763,9 +763,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
UpdateOutputBox();
|
||||
}
|
||||
@@ -822,9 +822,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
_picker.ValueTypeName = (string)Values[0];
|
||||
@@ -890,9 +890,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
_picker.ValueTypeName = (string)Values[0];
|
||||
@@ -941,9 +941,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
_picker.ValueTypeName = (string)Values[0];
|
||||
@@ -993,9 +993,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnLoaded()
|
||||
public override void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnLoaded();
|
||||
base.OnLoaded(action);
|
||||
|
||||
if (Surface != null)
|
||||
_picker.ValueTypeName = (string)Values[0];
|
||||
@@ -1065,9 +1065,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
_input = (InputBox)GetBox(0);
|
||||
_output = (OutputBox)GetBox(1);
|
||||
@@ -1255,7 +1255,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
OutputBox.DrawConnection(ref startPos, ref endPos, ref color, 2);
|
||||
OutputBox.DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, 2);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
199
Source/Editor/Surface/BehaviorTreeSurface.cs
Normal file
199
Source/Editor/Surface/BehaviorTreeSurface.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.ContextMenu;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Surface
|
||||
{
|
||||
/// <summary>
|
||||
/// The Visject Surface implementation for the Behavior Tree graphs.
|
||||
/// </summary>
|
||||
/// <seealso cref="VisjectSurface" />
|
||||
[HideInEditor]
|
||||
public class BehaviorTreeSurface : VisjectSurface
|
||||
{
|
||||
private static NodesCache _nodesCache = new NodesCache(IterateNodesCache);
|
||||
|
||||
/// <inheritdoc />
|
||||
public BehaviorTreeSurface(IVisjectSurfaceOwner owner, Action onSave, FlaxEditor.Undo undo)
|
||||
: base(owner, onSave, undo, CreateStyle())
|
||||
{
|
||||
}
|
||||
|
||||
private static SurfaceStyle CreateStyle()
|
||||
{
|
||||
var editor = Editor.Instance;
|
||||
var style = SurfaceStyle.CreateStyleHandler(editor);
|
||||
style.DrawBox = DrawBox;
|
||||
style.DrawConnection = DrawConnection;
|
||||
return style;
|
||||
}
|
||||
|
||||
private static void DrawBox(Box box)
|
||||
{
|
||||
var rect = new Rectangle(Float2.Zero, box.Size);
|
||||
const float minBoxSize = 5.0f;
|
||||
if (rect.Size.LengthSquared < minBoxSize * minBoxSize)
|
||||
return;
|
||||
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
var color = style.LightBackground;
|
||||
if (box.IsMouseOver)
|
||||
color *= 1.2f;
|
||||
Render2D.FillRectangle(rect, color);
|
||||
}
|
||||
|
||||
private static void DrawConnection(Float2 start, Float2 end, Color color, float thickness)
|
||||
{
|
||||
Archetypes.Animation.StateMachineStateBase.DrawConnection(ref start, ref end, ref color);
|
||||
}
|
||||
|
||||
private void OnActiveContextMenuVisibleChanged(Control activeCM)
|
||||
{
|
||||
_nodesCache.Wait();
|
||||
}
|
||||
|
||||
private static void IterateNodesCache(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version)
|
||||
{
|
||||
// Filter by BT node types only
|
||||
if (!new ScriptType(typeof(BehaviorTreeNode)).IsAssignableFrom(scriptType))
|
||||
return;
|
||||
|
||||
// Skip in-built types
|
||||
if (scriptType == typeof(BehaviorTreeNode) ||
|
||||
scriptType == typeof(BehaviorTreeCompoundNode) ||
|
||||
scriptType == typeof(BehaviorTreeRootNode))
|
||||
return;
|
||||
|
||||
// Nodes-only
|
||||
if (new ScriptType(typeof(BehaviorTreeDecorator)).IsAssignableFrom(scriptType))
|
||||
return;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>("Behavior Tree", 19);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(70, 220, 181),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.BehaviorTree.Nodes[0].Clone();
|
||||
node.DefaultValues[0] = scriptType.TypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = Archetypes.BehaviorTree.Node.GetTitle(scriptType);
|
||||
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
|
||||
{
|
||||
activeCM.ShowExpanded = true;
|
||||
_nodesCache.Get(activeCM);
|
||||
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBox);
|
||||
|
||||
activeCM.VisibleChanged += OnActiveContextMenuVisibleChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetTypeName(ScriptType type)
|
||||
{
|
||||
if (type == ScriptType.Void)
|
||||
return string.Empty; // Remove `Void` tooltip from connection areas
|
||||
return base.GetTypeName(type);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Load()
|
||||
{
|
||||
if (base.Load())
|
||||
return true;
|
||||
|
||||
// Ensure to have Root node created (UI blocks spawning of it)
|
||||
if (RootContext.FindNode(19, 2) == null)
|
||||
{
|
||||
RootContext.SpawnNode(19, 2, Float2.Zero);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
// Comments
|
||||
if (groupArchetype.GroupID == 7 && nodeArchetype.TypeID == 11)
|
||||
return true;
|
||||
|
||||
// Single root node
|
||||
if (groupArchetype.GroupID == 19 && nodeArchetype.TypeID == 2 && RootContext.FindNode(19, 2) != null)
|
||||
return false;
|
||||
|
||||
// Behavior Tree nodes only
|
||||
return (nodeArchetype.Flags & NodeFlags.BehaviorTreeGraph) != 0 &&
|
||||
groupArchetype.GroupID == 19 &&
|
||||
base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool ValidateDragItem(AssetItem assetItem)
|
||||
{
|
||||
if (assetItem.IsOfType<BehaviorTree>())
|
||||
return true;
|
||||
return base.ValidateDragItem(assetItem);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void HandleDragDropAssets(List<AssetItem> objects, DragDropEventArgs args)
|
||||
{
|
||||
for (int i = 0; i < objects.Count; i++)
|
||||
{
|
||||
var assetItem = objects[i];
|
||||
SurfaceNode node = null;
|
||||
|
||||
if (assetItem.IsOfType<BehaviorTree>())
|
||||
{
|
||||
var instance = new BehaviorTreeSubTreeNode();
|
||||
instance.Name = Utilities.Utils.GetPropertyNameUI(assetItem.ShortName);
|
||||
instance.Tree = (BehaviorTree)assetItem.LoadAsync();
|
||||
node = Context.SpawnNode(19, 1, args.SurfaceLocation, new object[]
|
||||
{
|
||||
typeof(BehaviorTreeSubTreeNode).FullName,
|
||||
FlaxEngine.Json.JsonSerializer.SaveToBytes(instance),
|
||||
null,
|
||||
});
|
||||
FlaxEngine.Object.Destroy(instance);
|
||||
}
|
||||
|
||||
if (node != null)
|
||||
args.SurfaceLocation.X += node.Width + 10;
|
||||
}
|
||||
base.HandleDragDropAssets(objects, args);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
_nodesCache.Wait();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,10 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
/// <summary>
|
||||
/// Visject Surface node archetype spawn ability checking delegate.
|
||||
/// </summary>
|
||||
/// <param name="groupArch">The nodes group archetype to check.</param>
|
||||
/// <param name="arch">The node archetype to check.</param>
|
||||
/// <returns>True if can use this node to spawn it on a surface, otherwise false..</returns>
|
||||
public delegate bool NodeSpawnCheckDelegate(NodeArchetype arch);
|
||||
public delegate bool NodeSpawnCheckDelegate(GroupArchetype groupArch, NodeArchetype arch);
|
||||
|
||||
/// <summary>
|
||||
/// Visject Surface parameters getter delegate.
|
||||
@@ -209,7 +210,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
nodes.Clear();
|
||||
foreach (var nodeArchetype in groupArchetype.Archetypes)
|
||||
{
|
||||
if ((nodeArchetype.Flags & NodeFlags.NoSpawnViaGUI) == 0 && info.CanSpawnNode(nodeArchetype))
|
||||
if ((nodeArchetype.Flags & NodeFlags.NoSpawnViaGUI) == 0 && info.CanSpawnNode(groupArchetype, nodeArchetype))
|
||||
{
|
||||
nodes.Add(nodeArchetype);
|
||||
}
|
||||
@@ -307,6 +308,8 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
{
|
||||
group.UnlockChildrenRecursive();
|
||||
SortGroups();
|
||||
if (ShowExpanded)
|
||||
group.Open(false);
|
||||
group.PerformLayout();
|
||||
if (_searchBox.TextLength != 0)
|
||||
{
|
||||
@@ -353,6 +356,8 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
if (_contextSensitiveSearchEnabled)
|
||||
group.EvaluateVisibilityWithBox(_selectedBox);
|
||||
group.SortChildren();
|
||||
if (ShowExpanded)
|
||||
group.Open(false);
|
||||
group.Parent = _groupsPanel;
|
||||
_groups.Add(group);
|
||||
|
||||
@@ -618,7 +623,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
Archetypes = archetypes
|
||||
};
|
||||
|
||||
var group = CreateGroup(groupArchetype);
|
||||
var group = CreateGroup(groupArchetype, false);
|
||||
group.ArrowImageOpened = new SpriteBrush(Style.Current.ArrowDown);
|
||||
group.ArrowImageClosed = new SpriteBrush(Style.Current.ArrowRight);
|
||||
group.Close(false);
|
||||
|
||||
@@ -110,11 +110,11 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
bool isCompatible = false;
|
||||
if (startBox.IsOutput && _archetype.IsInputCompatible != null)
|
||||
{
|
||||
isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints);
|
||||
isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints, startBox.ParentNode.Context);
|
||||
}
|
||||
else if (!startBox.IsOutput && _archetype.IsOutputCompatible != null)
|
||||
{
|
||||
isCompatible |= _archetype.IsOutputCompatible.Invoke(_archetype, startBox.CurrentType, startBox.ParentNode.Archetype.ConnectionsHints);
|
||||
isCompatible |= _archetype.IsOutputCompatible.Invoke(_archetype, startBox.CurrentType, startBox.ParentNode.Archetype.ConnectionsHints, startBox.ParentNode.Context);
|
||||
}
|
||||
else if (_archetype.Elements != null)
|
||||
{
|
||||
|
||||
@@ -133,6 +133,11 @@ namespace FlaxEditor.Surface.Elements
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cached color for <see cref="CurrentType"/>.
|
||||
/// </summary>
|
||||
public Color CurrentTypeColor => _currentTypeColor;
|
||||
|
||||
/// <summary>
|
||||
/// The collection of the attributes used by the box. Assigned externally. Can be used to control the default value editing for the <see cref="InputBox"/> or to provide more metadata for the surface UI.
|
||||
/// </summary>
|
||||
@@ -494,44 +499,6 @@ namespace FlaxEditor.Surface.Elements
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the box GUI using <see cref="Render2D"/>.
|
||||
/// </summary>
|
||||
protected void DrawBox()
|
||||
{
|
||||
var rect = new Rectangle(Float2.Zero, Size);
|
||||
|
||||
// Size culling
|
||||
const float minBoxSize = 5.0f;
|
||||
if (rect.Size.LengthSquared < minBoxSize * minBoxSize)
|
||||
return;
|
||||
|
||||
// Debugging boxes size
|
||||
//Render2D.DrawRectangle(rect, Color.Orange); return;
|
||||
|
||||
// Draw icon
|
||||
bool hasConnections = HasAnyConnection;
|
||||
float alpha = Enabled ? 1.0f : 0.6f;
|
||||
Color color = _currentTypeColor * alpha;
|
||||
var style = Surface.Style;
|
||||
SpriteHandle icon;
|
||||
if (_currentType.Type == typeof(void))
|
||||
icon = hasConnections ? style.Icons.ArrowClose : style.Icons.ArrowOpen;
|
||||
else
|
||||
icon = hasConnections ? style.Icons.BoxClose : style.Icons.BoxOpen;
|
||||
color *= ConnectionsHighlightIntensity + 1;
|
||||
Render2D.DrawSprite(icon, rect, color);
|
||||
|
||||
// Draw selection hint
|
||||
if (_isSelected)
|
||||
{
|
||||
float outlineAlpha = Mathf.Sin(Time.TimeSinceStartup * 4.0f) * 0.5f + 0.5f;
|
||||
float outlineWidth = Mathf.Lerp(1.5f, 4.0f, outlineAlpha);
|
||||
var outlineRect = new Rectangle(rect.X - outlineWidth, rect.Y - outlineWidth, rect.Width + outlineWidth * 2, rect.Height + outlineWidth * 2);
|
||||
Render2D.DrawSprite(icon, outlineRect, FlaxEngine.GUI.Style.Current.BorderSelected.RGBMultiplied(1.0f + outlineAlpha * 0.4f));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseEnter(Float2 location)
|
||||
{
|
||||
@@ -658,12 +625,17 @@ namespace FlaxEditor.Surface.Elements
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connections origin offset.
|
||||
/// </summary>
|
||||
public Float2 ConnectionOffset;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Float2 ConnectionOrigin
|
||||
{
|
||||
get
|
||||
{
|
||||
var center = Center;
|
||||
var center = Center + ConnectionOffset;
|
||||
return Parent.PointToParent(ref center);
|
||||
}
|
||||
}
|
||||
@@ -752,7 +724,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
/// <inheritdoc />
|
||||
public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color)
|
||||
{
|
||||
OutputBox.DrawConnection(ref startPos, ref endPos, ref color, 2);
|
||||
OutputBox.DrawConnection(Surface.Style, ref startPos, ref endPos, ref color, 2);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1438,7 +1438,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
base.Draw();
|
||||
|
||||
// Box
|
||||
DrawBox();
|
||||
Surface.Style.DrawBox(this);
|
||||
|
||||
// Draw text
|
||||
var style = Style.Current;
|
||||
|
||||
@@ -27,12 +27,19 @@ namespace FlaxEditor.Surface.Elements
|
||||
/// <summary>
|
||||
/// Draws the connection between two boxes.
|
||||
/// </summary>
|
||||
/// <param name="style">The Visject surface style.</param>
|
||||
/// <param name="start">The start location.</param>
|
||||
/// <param name="end">The end location.</param>
|
||||
/// <param name="color">The connection color.</param>
|
||||
/// <param name="thickness">The connection thickness.</param>
|
||||
public static void DrawConnection(ref Float2 start, ref Float2 end, ref Color color, float thickness = 1)
|
||||
public static void DrawConnection(SurfaceStyle style, ref Float2 start, ref Float2 end, ref Color color, float thickness = 1)
|
||||
{
|
||||
if (style.DrawConnection != null)
|
||||
{
|
||||
style.DrawConnection(start, end, color, thickness);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate control points
|
||||
CalculateBezierControlPoints(start, end, out var control1, out var control2);
|
||||
|
||||
@@ -71,8 +78,8 @@ namespace FlaxEditor.Surface.Elements
|
||||
/// <param name="mousePosition">The mouse position</param>
|
||||
public bool IntersectsConnection(Box targetBox, ref Float2 mousePosition)
|
||||
{
|
||||
var startPos = Parent.PointToParent(Center);
|
||||
var endPos = targetBox.Parent.PointToParent(targetBox.Center);
|
||||
var startPos = ConnectionOrigin;
|
||||
var endPos = targetBox.ConnectionOrigin;
|
||||
return IntersectsConnection(ref startPos, ref endPos, ref mousePosition, MouseOverConnectionDistance);
|
||||
}
|
||||
|
||||
@@ -135,14 +142,15 @@ namespace FlaxEditor.Surface.Elements
|
||||
/// </summary>
|
||||
public void DrawConnections(ref Float2 mousePosition)
|
||||
{
|
||||
float mouseOverDistance = MouseOverConnectionDistance;
|
||||
// Draw all the connections
|
||||
var startPos = Parent.PointToParent(Center);
|
||||
var style = Surface.Style;
|
||||
var mouseOverDistance = MouseOverConnectionDistance;
|
||||
var startPos = ConnectionOrigin;
|
||||
var startHighlight = ConnectionsHighlightIntensity;
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
Box targetBox = Connections[i];
|
||||
var endPos = targetBox.Parent.PointToParent(targetBox.Center);
|
||||
var endPos = targetBox.ConnectionOrigin;
|
||||
var highlight = 1 + Mathf.Max(startHighlight, targetBox.ConnectionsHighlightIntensity);
|
||||
var color = _currentTypeColor * highlight;
|
||||
|
||||
@@ -152,7 +160,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
highlight += 0.5f;
|
||||
}
|
||||
|
||||
DrawConnection(ref startPos, ref endPos, ref color, highlight);
|
||||
DrawConnection(style, ref startPos, ref endPos, ref color, highlight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,9 +170,9 @@ namespace FlaxEditor.Surface.Elements
|
||||
public void DrawSelectedConnection(Box targetBox)
|
||||
{
|
||||
// Draw all the connections
|
||||
var startPos = Parent.PointToParent(Center);
|
||||
var endPos = targetBox.Parent.PointToParent(targetBox.Center);
|
||||
DrawConnection(ref startPos, ref endPos, ref _currentTypeColor, 2.5f);
|
||||
var startPos = ConnectionOrigin;
|
||||
var endPos = targetBox.ConnectionOrigin;
|
||||
DrawConnection(Surface.Style, ref startPos, ref endPos, ref _currentTypeColor, 2.5f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -176,7 +184,7 @@ namespace FlaxEditor.Surface.Elements
|
||||
base.Draw();
|
||||
|
||||
// Box
|
||||
DrawBox();
|
||||
Surface.Style.DrawBox(this);
|
||||
|
||||
// Draw text
|
||||
var style = Style.Current;
|
||||
|
||||
@@ -36,13 +36,13 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
if (nodeArchetype.Title == "Function Input" ||
|
||||
nodeArchetype.Title == "Function Output")
|
||||
return true;
|
||||
|
||||
return base.CanUseNodeType(nodeArchetype);
|
||||
return base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,9 +33,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
return (nodeArchetype.Flags & NodeFlags.MaterialGraph) != 0 && base.CanUseNodeType(nodeArchetype);
|
||||
return (nodeArchetype.Flags & NodeFlags.MaterialGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Checks if the given type is compatible with the given node archetype. Used for custom nodes
|
||||
/// </summary>
|
||||
public delegate bool IsCompatible(NodeArchetype nodeArch, ScriptType portType, ConnectionsHint hint);
|
||||
public delegate bool IsCompatible(NodeArchetype nodeArch, ScriptType portType, ConnectionsHint hint, VisjectSurfaceContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Unique node type ID within a single group.
|
||||
|
||||
@@ -175,6 +175,13 @@ namespace FlaxEditor.Surface
|
||||
Color = new Color(110, 180, 81),
|
||||
Archetypes = Archetypes.Collections.Nodes
|
||||
},
|
||||
new GroupArchetype
|
||||
{
|
||||
GroupID = 19,
|
||||
Name = "Behavior Tree",
|
||||
Color = new Color(70, 220, 181),
|
||||
Archetypes = Archetypes.BehaviorTree.Nodes
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -68,9 +68,14 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
NoSpawnViaPaste = 512,
|
||||
|
||||
/// <summary>
|
||||
/// Node can be used in the Behavior Tree graphs.
|
||||
/// </summary>
|
||||
BehaviorTreeGraph = 1024,
|
||||
|
||||
/// <summary>
|
||||
/// Node can be used in the all visual graphs.
|
||||
/// </summary>
|
||||
AllGraphs = MaterialGraph | ParticleEmitterGraph | AnimGraph | VisualScriptGraph,
|
||||
AllGraphs = MaterialGraph | ParticleEmitterGraph | AnimGraph | VisualScriptGraph | BehaviorTreeGraph,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,13 +35,13 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
if (nodeArchetype.Title == "Function Input" ||
|
||||
nodeArchetype.Title == "Function Output")
|
||||
return true;
|
||||
|
||||
return base.CanUseNodeType(nodeArchetype);
|
||||
return base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -87,9 +87,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
return (nodeArchetype.Flags & NodeFlags.ParticleEmitterGraph) != 0 && base.CanUseNodeType(nodeArchetype);
|
||||
return (nodeArchetype.Flags & NodeFlags.ParticleEmitterGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -59,9 +59,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
// Read node data
|
||||
Title = TitleValue;
|
||||
@@ -70,9 +70,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned();
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Randomize color
|
||||
Color = ColorValue = Color.FromHSV(new Random().NextFloat(0, 360), 0.7f, 0.25f, 0.8f);
|
||||
|
||||
@@ -122,14 +122,14 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Called when control gets loaded and added to surface.
|
||||
/// </summary>
|
||||
public virtual void OnLoaded()
|
||||
public virtual void OnLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when surface gets loaded and nodes boxes are connected.
|
||||
/// </summary>
|
||||
public virtual void OnSurfaceLoaded()
|
||||
public virtual void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
UpdateRectangles();
|
||||
}
|
||||
@@ -137,14 +137,14 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Called after adding the control to the surface after user spawn (eg. add comment, add new node, etc.).
|
||||
/// </summary>
|
||||
public virtual void OnSpawned()
|
||||
public virtual void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on removing the control from the surface after user delete/cut operation (eg. delete comment, cut node, etc.).
|
||||
/// </summary>
|
||||
public virtual void OnDeleted()
|
||||
public virtual void OnDeleted(SurfaceNodeActions action)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// The node archetype.
|
||||
/// </summary>
|
||||
public readonly NodeArchetype Archetype;
|
||||
public NodeArchetype Archetype;
|
||||
|
||||
/// <summary>
|
||||
/// The group archetype.
|
||||
@@ -165,25 +165,22 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
if (Surface == null)
|
||||
return;
|
||||
Size = CalculateNodeSize(width, height);
|
||||
|
||||
// Update boxes on width change
|
||||
//if (!Mathf.NearEqual(prevSize.X, Size.X))
|
||||
for (int i = 0; i < Elements.Count; i++)
|
||||
{
|
||||
for (int i = 0; i < Elements.Count; i++)
|
||||
if (Elements[i] is OutputBox box)
|
||||
{
|
||||
if (Elements[i] is OutputBox box)
|
||||
{
|
||||
box.Location = box.Archetype.Position + new Float2(width, 0);
|
||||
}
|
||||
box.Location = box.Archetype.Position + new Float2(width, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Size = CalculateNodeSize(width, height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically resizes the node to match the title size and all the elements for best fit of the node dimensions.
|
||||
/// </summary>
|
||||
public void ResizeAuto()
|
||||
public virtual void ResizeAuto()
|
||||
{
|
||||
if (Surface == null)
|
||||
return;
|
||||
@@ -220,7 +217,7 @@ namespace FlaxEditor.Surface
|
||||
width = Mathf.Max(width, control.Right + 4 - Constants.NodeMarginX);
|
||||
height = Mathf.Max(height, control.Bottom + 4 - Constants.NodeMarginY - Constants.NodeHeaderSize);
|
||||
}
|
||||
else
|
||||
else if (!_headerRect.Intersects(control.Bounds))
|
||||
{
|
||||
width = Mathf.Max(width, control.Width + 4);
|
||||
height = Mathf.Max(height, control.Height + 4);
|
||||
@@ -393,6 +390,19 @@ namespace FlaxEditor.Surface
|
||||
UpdateBoxesTypes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Array of nodes that are sealed to this node - sealed nodes are duplicated/copied/pasted/removed in a batch. Null if unused.
|
||||
/// </summary>
|
||||
public virtual SurfaceNode[] SealedNodes => null;
|
||||
|
||||
/// <summary>
|
||||
/// Called after adding the control to the surface after paste.
|
||||
/// </summary>
|
||||
/// <param name="idsMapping">The nodes IDs mapping (original node ID to pasted node ID). Can be sued to update internal node's data after paste operation from the original data.</param>
|
||||
public virtual void OnPasted(System.Collections.Generic.Dictionary<uint, uint> idsMapping)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this node uses dependent boxes.
|
||||
/// </summary>
|
||||
@@ -882,9 +892,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
base.OnSurfaceLoaded(action);
|
||||
|
||||
UpdateBoxesTypes();
|
||||
|
||||
@@ -896,11 +906,11 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDeleted()
|
||||
public override void OnDeleted(SurfaceNodeActions action)
|
||||
{
|
||||
RemoveConnections();
|
||||
|
||||
base.OnDeleted();
|
||||
base.OnDeleted(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -926,7 +936,7 @@ namespace FlaxEditor.Surface
|
||||
OnValuesChanged();
|
||||
Surface?.MarkAsEdited(graphEdited);
|
||||
|
||||
if (Surface?.Undo != null)
|
||||
if (Surface != null)
|
||||
Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited));
|
||||
|
||||
_isDuringValuesEditing = false;
|
||||
@@ -953,7 +963,7 @@ namespace FlaxEditor.Surface
|
||||
OnValuesChanged();
|
||||
Surface.MarkAsEdited(graphEdited);
|
||||
|
||||
if (Surface?.Undo != null)
|
||||
if (Surface != null)
|
||||
Surface.AddBatchedUndoAction(new EditNodeValuesAction(this, before, graphEdited));
|
||||
|
||||
_isDuringValuesEditing = false;
|
||||
@@ -1020,9 +1030,9 @@ namespace FlaxEditor.Surface
|
||||
Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
|
||||
|
||||
// Close button
|
||||
if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0)
|
||||
if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit)
|
||||
{
|
||||
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
|
||||
// Footer
|
||||
|
||||
@@ -140,6 +140,16 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public Texture Background;
|
||||
|
||||
/// <summary>
|
||||
/// Boxes drawing callback.
|
||||
/// </summary>
|
||||
public Action<Elements.Box> DrawBox = DefaultDrawBox;
|
||||
|
||||
/// <summary>
|
||||
/// Custom box connection drawing callback (null by default).
|
||||
/// </summary>
|
||||
public Action<Float2, Float2, Color, float> DrawConnection = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color for the connection.
|
||||
/// </summary>
|
||||
@@ -204,6 +214,41 @@ namespace FlaxEditor.Surface
|
||||
color = Colors.Default;
|
||||
}
|
||||
|
||||
private static void DefaultDrawBox(Elements.Box box)
|
||||
{
|
||||
var rect = new Rectangle(Float2.Zero, box.Size);
|
||||
|
||||
// Size culling
|
||||
const float minBoxSize = 5.0f;
|
||||
if (rect.Size.LengthSquared < minBoxSize * minBoxSize)
|
||||
return;
|
||||
|
||||
// Debugging boxes size
|
||||
//Render2D.DrawRectangle(rect, Color.Orange); return;
|
||||
|
||||
// Draw icon
|
||||
bool hasConnections = box.HasAnyConnection;
|
||||
float alpha = box.Enabled ? 1.0f : 0.6f;
|
||||
Color color = box.CurrentTypeColor * alpha;
|
||||
var style = box.Surface.Style;
|
||||
SpriteHandle icon;
|
||||
if (box.CurrentType.Type == typeof(void))
|
||||
icon = hasConnections ? style.Icons.ArrowClose : style.Icons.ArrowOpen;
|
||||
else
|
||||
icon = hasConnections ? style.Icons.BoxClose : style.Icons.BoxOpen;
|
||||
color *= box.ConnectionsHighlightIntensity + 1;
|
||||
Render2D.DrawSprite(icon, rect, color);
|
||||
|
||||
// Draw selection hint
|
||||
if (box.IsSelected)
|
||||
{
|
||||
float outlineAlpha = Mathf.Sin(Time.TimeSinceStartup * 4.0f) * 0.5f + 0.5f;
|
||||
float outlineWidth = Mathf.Lerp(1.5f, 4.0f, outlineAlpha);
|
||||
var outlineRect = new Rectangle(rect.X - outlineWidth, rect.Y - outlineWidth, rect.Width + outlineWidth * 2, rect.Height + outlineWidth * 2);
|
||||
Render2D.DrawSprite(icon, outlineRect, FlaxEngine.GUI.Style.Current.BorderSelected.RGBMultiplied(1.0f + outlineAlpha * 0.4f));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function used to create style for the given surface type. Can be overriden to provide some customization via user plugin.
|
||||
/// </summary>
|
||||
|
||||
@@ -67,8 +67,8 @@ namespace FlaxEditor.Surface.Undo
|
||||
else if (_nodeValues != null && _nodeValues.Length != 0)
|
||||
throw new InvalidOperationException("Invalid node values.");
|
||||
node.Location = _nodeLocation;
|
||||
context.OnControlLoaded(node);
|
||||
node.OnSurfaceLoaded();
|
||||
context.OnControlLoaded(node, SurfaceNodeActions.Undo);
|
||||
node.OnSurfaceLoaded(SurfaceNodeActions.Undo);
|
||||
|
||||
context.MarkAsModified();
|
||||
}
|
||||
@@ -89,7 +89,7 @@ namespace FlaxEditor.Surface.Undo
|
||||
|
||||
// Remove node
|
||||
context.Nodes.Remove(node);
|
||||
context.OnControlDeleted(node);
|
||||
context.OnControlDeleted(node, SurfaceNodeActions.Undo);
|
||||
|
||||
context.MarkAsModified();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
//#define DEBUG_SEARCH_TIME
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.ContextMenu;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEditor.Surface.Undo;
|
||||
@@ -13,6 +17,157 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
public partial class VisjectSurface
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility for easy nodes archetypes generation for Visject Surface based on scripting types.
|
||||
/// </summary>
|
||||
internal class NodesCache
|
||||
{
|
||||
public delegate void IterateType(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version);
|
||||
|
||||
internal static readonly List<NodesCache> Caches = new List<NodesCache>(8);
|
||||
|
||||
private readonly object _locker = new object();
|
||||
private readonly IterateType _iterator;
|
||||
private int _version;
|
||||
private Task _task;
|
||||
private VisjectCM _taskContextMenu;
|
||||
private Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache;
|
||||
|
||||
public NodesCache(IterateType iterator)
|
||||
{
|
||||
_iterator = iterator;
|
||||
}
|
||||
|
||||
public void Wait()
|
||||
{
|
||||
if (_task != null)
|
||||
{
|
||||
Profiler.BeginEvent("Setup Context Menu");
|
||||
_task.Wait();
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Wait();
|
||||
|
||||
if (_cache != null && _cache.Count != 0)
|
||||
{
|
||||
OnCodeEditingTypesCleared();
|
||||
}
|
||||
lock (_locker)
|
||||
{
|
||||
Caches.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Get(VisjectCM contextMenu)
|
||||
{
|
||||
Profiler.BeginEvent("Setup Context Menu");
|
||||
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (_cache == null)
|
||||
{
|
||||
if (!Caches.Contains(this))
|
||||
Caches.Add(this);
|
||||
_cache = new Dictionary<KeyValuePair<string, ushort>, GroupArchetype>();
|
||||
}
|
||||
contextMenu.LockChildrenRecursive();
|
||||
|
||||
// Check if has cached groups
|
||||
if (_cache.Count != 0)
|
||||
{
|
||||
// Check if context menu doesn't have the recent cached groups
|
||||
if (!contextMenu.Groups.Any(g => g.Archetypes[0].Tag is int asInt && asInt == _version))
|
||||
{
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
foreach (var g in _cache.Values)
|
||||
contextMenu.AddGroup(g);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove any old groups from context menu
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
|
||||
// Register for scripting types reload
|
||||
Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared;
|
||||
|
||||
// Run caching on an async
|
||||
_task = Task.Run(OnActiveContextMenuShowAsync);
|
||||
_taskContextMenu = contextMenu;
|
||||
}
|
||||
|
||||
contextMenu.UnlockChildrenRecursive();
|
||||
}
|
||||
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
private void OnActiveContextMenuShowAsync()
|
||||
{
|
||||
Profiler.BeginEvent("Setup Context Menu (async)");
|
||||
#if DEBUG_SEARCH_TIME
|
||||
var searchStartTime = DateTime.Now;
|
||||
#endif
|
||||
|
||||
foreach (var scriptType in Editor.Instance.CodeEditing.All.Get())
|
||||
{
|
||||
if (!SurfaceUtils.IsValidVisualScriptType(scriptType))
|
||||
continue;
|
||||
|
||||
_iterator(scriptType, _cache, _version);
|
||||
}
|
||||
|
||||
// Add group to context menu (on a main thread)
|
||||
FlaxEngine.Scripting.InvokeOnUpdate(() =>
|
||||
{
|
||||
#if DEBUG_SEARCH_TIME
|
||||
var addStartTime = DateTime.Now;
|
||||
#endif
|
||||
lock (_locker)
|
||||
{
|
||||
_taskContextMenu.AddGroups(_cache.Values);
|
||||
_taskContextMenu = null;
|
||||
}
|
||||
#if DEBUG_SEARCH_TIME
|
||||
Editor.LogError($"Added items to VisjectCM in: {(DateTime.Now - addStartTime).TotalMilliseconds} ms");
|
||||
#endif
|
||||
});
|
||||
|
||||
#if DEBUG_SEARCH_TIME
|
||||
Editor.LogError($"Collected items in: {(DateTime.Now - searchStartTime).TotalMilliseconds} ms");
|
||||
#endif
|
||||
Profiler.EndEvent();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_task = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCodeEditingTypesCleared()
|
||||
{
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_cache.Clear();
|
||||
_version++;
|
||||
}
|
||||
|
||||
Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared;
|
||||
}
|
||||
}
|
||||
|
||||
private ContextMenuButton _cmCopyButton;
|
||||
private ContextMenuButton _cmDuplicateButton;
|
||||
private ContextMenuButton _cmFormatNodesConnectionButton;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEditor.Surface.Undo;
|
||||
using FlaxEngine;
|
||||
@@ -66,6 +65,25 @@ namespace FlaxEditor.Surface
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect sealed nodes to be copied as well
|
||||
foreach (var control in selection.ToArray())
|
||||
{
|
||||
if (control is SurfaceNode node)
|
||||
{
|
||||
var sealedNodes = node.SealedNodes;
|
||||
if (sealedNodes != null)
|
||||
{
|
||||
foreach (var sealedNode in sealedNodes)
|
||||
{
|
||||
if (sealedNode != null && !selection.Contains(sealedNode))
|
||||
{
|
||||
selection.Add(sealedNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dataModel = new DataModel();
|
||||
var dataModelNodes = new List<DataModelNode>(selection.Count);
|
||||
var dataModelComments = new List<DataModelComment>();
|
||||
@@ -254,7 +272,7 @@ namespace FlaxEditor.Surface
|
||||
throw new InvalidOperationException("Unknown node type.");
|
||||
|
||||
// Validate given node type
|
||||
if (!CanUseNodeType(nodeArchetype))
|
||||
if (!CanUseNodeType(groupArchetype, nodeArchetype))
|
||||
continue;
|
||||
|
||||
// Create
|
||||
@@ -355,7 +373,7 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
}
|
||||
|
||||
Context.OnControlLoaded(node);
|
||||
Context.OnControlLoaded(node, SurfaceNodeActions.Paste);
|
||||
}
|
||||
|
||||
// Setup connections
|
||||
@@ -395,11 +413,15 @@ namespace FlaxEditor.Surface
|
||||
// Post load
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
node.Value.OnSurfaceLoaded();
|
||||
node.Value.OnSurfaceLoaded(SurfaceNodeActions.Paste);
|
||||
}
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
node.Value.OnSpawned();
|
||||
node.Value.OnSpawned(SurfaceNodeActions.Paste);
|
||||
}
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
node.Value.OnPasted(idsMapping);
|
||||
}
|
||||
|
||||
// Add undo action
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Surface
|
||||
@@ -135,8 +136,25 @@ namespace FlaxEditor.Surface
|
||||
endPos = _lastInstigatorUnderMouse.ConnectionOrigin;
|
||||
}
|
||||
|
||||
Float2 actualStartPos = startPos;
|
||||
Float2 actualEndPos = endPos;
|
||||
|
||||
if (_connectionInstigator is Archetypes.Tools.RerouteNode)
|
||||
{
|
||||
if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true })
|
||||
{
|
||||
actualStartPos = endPos;
|
||||
actualEndPos = startPos;
|
||||
}
|
||||
}
|
||||
else if (_connectionInstigator is Box { IsOutput: false })
|
||||
{
|
||||
actualStartPos = endPos;
|
||||
actualEndPos = startPos;
|
||||
}
|
||||
|
||||
// Draw connection
|
||||
_connectionInstigator.DrawConnectingLine(ref startPos, ref endPos, ref lineColor);
|
||||
_connectionInstigator.DrawConnectingLine(ref actualStartPos, ref actualEndPos, ref lineColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -176,7 +194,7 @@ namespace FlaxEditor.Surface
|
||||
var bezierStartPoint = new Float2(upperRight.X + offsetX * 0.75f, (upperRight.Y + bottomRight.Y) * 0.5f);
|
||||
var bezierEndPoint = inputBracket.Box.ParentNode.PointToParent(_rootControl.Parent, inputBracket.Box.Center);
|
||||
|
||||
Elements.OutputBox.DrawConnection(ref bezierStartPoint, ref bezierEndPoint, ref fadedColor);
|
||||
Elements.OutputBox.DrawConnection(Style, ref bezierStartPoint, ref bezierEndPoint, ref fadedColor);
|
||||
|
||||
// Debug Area
|
||||
//Rectangle drawRect = Rectangle.FromPoints(upperLeft, bottomRight);
|
||||
|
||||
@@ -115,15 +115,23 @@ namespace FlaxEditor.Surface
|
||||
var p1 = _rootControl.PointFromParent(ref _leftMouseDownPos);
|
||||
var p2 = _rootControl.PointFromParent(ref _mousePos);
|
||||
var selectionRect = Rectangle.FromPoints(p1, p2);
|
||||
var selectionChanged = false;
|
||||
|
||||
// Find controls to select
|
||||
for (int i = 0; i < _rootControl.Children.Count; i++)
|
||||
{
|
||||
if (_rootControl.Children[i] is SurfaceControl control)
|
||||
{
|
||||
control.IsSelected = control.IsSelectionIntersecting(ref selectionRect);
|
||||
var select = control.IsSelectionIntersecting(ref selectionRect);
|
||||
if (select != control.IsSelected)
|
||||
{
|
||||
control.IsSelected = select;
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectionChanged)
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void OnSurfaceControlSpawned(SurfaceControl control)
|
||||
@@ -252,8 +260,11 @@ namespace FlaxEditor.Surface
|
||||
node.Location += delta;
|
||||
_leftMouseDownPos = location;
|
||||
_movingNodesDelta += delta;
|
||||
Cursor = CursorType.SizeAll;
|
||||
MarkAsEdited(false);
|
||||
if (_movingNodes.Count > 0)
|
||||
{
|
||||
Cursor = CursorType.SizeAll;
|
||||
MarkAsEdited(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Handled
|
||||
@@ -343,12 +354,12 @@ namespace FlaxEditor.Surface
|
||||
if (!handled)
|
||||
CustomMouseDoubleClick?.Invoke(ref location, button, ref handled);
|
||||
|
||||
if (!handled && CanEdit)
|
||||
// Insert reroute node
|
||||
if (!handled && CanEdit && CanUseNodeType(7, 29))
|
||||
{
|
||||
var mousePos = _rootControl.PointFromParent(ref _mousePos);
|
||||
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox) && GetControlUnderMouse() == null)
|
||||
{
|
||||
// Insert reroute node
|
||||
if (Undo != null)
|
||||
{
|
||||
bool undoEnabled = Undo.Enabled;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.Options;
|
||||
@@ -122,7 +123,7 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Occurs when selection gets changed.
|
||||
/// </summary>
|
||||
protected event Action SelectionChanged;
|
||||
public event Action SelectionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The surface owner.
|
||||
@@ -262,6 +263,9 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Gets the list of the selected nodes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Don't call it too often. It does memory allocation and iterates over the surface controls to find selected nodes in the graph.
|
||||
/// </remarks>
|
||||
public List<SurfaceNode> SelectedNodes
|
||||
{
|
||||
get
|
||||
@@ -279,6 +283,9 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Gets the list of the selected controls (comments and nodes).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Don't call it too often. It does memory allocation and iterates over the surface controls to find selected nodes in the graph.
|
||||
/// </remarks>
|
||||
public List<SurfaceControl> SelectedControls
|
||||
{
|
||||
get
|
||||
@@ -530,9 +537,31 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Determines whether the specified node archetype can be used in the surface.
|
||||
/// </summary>
|
||||
/// <param name="groupID">The nodes group archetype identifier.</param>
|
||||
/// <param name="typeID">The node archetype identifier.</param>
|
||||
/// <returns>True if can use this node archetype, otherwise false.</returns>
|
||||
public bool CanUseNodeType(ushort groupID, ushort typeID)
|
||||
{
|
||||
var result = false;
|
||||
var nodeArchetypes = NodeArchetypes ?? NodeFactory.DefaultGroups;
|
||||
if (NodeFactory.GetArchetype(nodeArchetypes, groupID, typeID, out var groupArchetype, out var nodeArchetype))
|
||||
{
|
||||
var flags = nodeArchetype.Flags;
|
||||
nodeArchetype.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
nodeArchetype.Flags &= ~NodeFlags.NoSpawnViaPaste;
|
||||
result = CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
nodeArchetype.Flags = flags;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified node archetype can be used in the surface.
|
||||
/// </summary>
|
||||
/// <param name="groupArchetype">The nodes group archetype.</param>
|
||||
/// <param name="nodeArchetype">The node archetype.</param>
|
||||
/// <returns>True if can use this node archetype, otherwise false.</returns>
|
||||
public virtual bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public virtual bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
return (nodeArchetype.Flags & NodeFlags.NoSpawnViaPaste) == 0;
|
||||
}
|
||||
@@ -582,12 +611,17 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public void SelectAll()
|
||||
{
|
||||
bool selectionChanged = false;
|
||||
for (int i = 0; i < _rootControl.Children.Count; i++)
|
||||
{
|
||||
if (_rootControl.Children[i] is SurfaceControl control)
|
||||
if (_rootControl.Children[i] is SurfaceControl control && !control.IsSelected)
|
||||
{
|
||||
control.IsSelected = true;
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
SelectionChanged?.Invoke();
|
||||
if (selectionChanged)
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -595,12 +629,17 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public void ClearSelection()
|
||||
{
|
||||
bool selectionChanged = false;
|
||||
for (int i = 0; i < _rootControl.Children.Count; i++)
|
||||
{
|
||||
if (_rootControl.Children[i] is SurfaceControl control)
|
||||
if (_rootControl.Children[i] is SurfaceControl control && control.IsSelected)
|
||||
{
|
||||
control.IsSelected = false;
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
SelectionChanged?.Invoke();
|
||||
if (selectionChanged)
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -609,6 +648,8 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="control">The control.</param>
|
||||
public void AddToSelection(SurfaceControl control)
|
||||
{
|
||||
if (control.IsSelected)
|
||||
return;
|
||||
control.IsSelected = true;
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
@@ -619,9 +660,22 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="control">The control.</param>
|
||||
public void Select(SurfaceControl control)
|
||||
{
|
||||
ClearSelection();
|
||||
control.IsSelected = true;
|
||||
SelectionChanged?.Invoke();
|
||||
bool selectionChanged = false;
|
||||
for (int i = 0; i < _rootControl.Children.Count; i++)
|
||||
{
|
||||
if (_rootControl.Children[i] is SurfaceControl c && c != control && c.IsSelected)
|
||||
{
|
||||
c.IsSelected = false;
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
if (!control.IsSelected)
|
||||
{
|
||||
control.IsSelected = true;
|
||||
selectionChanged = true;
|
||||
}
|
||||
if (selectionChanged)
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -630,11 +684,13 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="controls">The controls.</param>
|
||||
public void Select(IEnumerable<SurfaceControl> controls)
|
||||
{
|
||||
var newSelection = controls.ToList();
|
||||
var prevSelection = SelectedControls;
|
||||
if (Utils.ArraysEqual(newSelection, prevSelection))
|
||||
return;
|
||||
ClearSelection();
|
||||
foreach (var control in controls)
|
||||
{
|
||||
foreach (var control in newSelection)
|
||||
control.IsSelected = true;
|
||||
}
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
@@ -644,6 +700,8 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="control">The control.</param>
|
||||
public void Deselect(SurfaceControl control)
|
||||
{
|
||||
if (!control.IsSelected)
|
||||
return;
|
||||
control.IsSelected = false;
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
@@ -682,11 +740,18 @@ namespace FlaxEditor.Surface
|
||||
/// <param name="withUndo">True if use undo/redo action for node removing.</param>
|
||||
public void Delete(IEnumerable<SurfaceControl> controls, bool withUndo = true)
|
||||
{
|
||||
if (!CanEdit || controls == null || !controls.Any())
|
||||
return;
|
||||
|
||||
var selectionChanged = false;
|
||||
List<SurfaceNode> nodes = null;
|
||||
foreach (var control in controls)
|
||||
{
|
||||
selectionChanged |= control.IsSelected;
|
||||
if (control.IsSelected)
|
||||
{
|
||||
selectionChanged = true;
|
||||
control.IsSelected = false;
|
||||
}
|
||||
|
||||
if (control is SurfaceNode node)
|
||||
{
|
||||
@@ -694,19 +759,34 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
if (nodes == null)
|
||||
nodes = new List<SurfaceNode>();
|
||||
nodes.Add(node);
|
||||
var sealedNodes = node.SealedNodes;
|
||||
if (sealedNodes != null)
|
||||
{
|
||||
foreach (var sealedNode in sealedNodes)
|
||||
{
|
||||
if (sealedNode != null)
|
||||
{
|
||||
if (sealedNode.IsSelected)
|
||||
{
|
||||
selectionChanged = true;
|
||||
sealedNode.IsSelected = false;
|
||||
}
|
||||
if (!nodes.Contains(sealedNode))
|
||||
nodes.Add(sealedNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!nodes.Contains(node))
|
||||
nodes.Add(node);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.OnControlDeleted(control);
|
||||
Context.OnControlDeleted(control, SurfaceNodeActions.User);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectionChanged)
|
||||
{
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
if (nodes != null)
|
||||
{
|
||||
@@ -717,7 +797,7 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
node.RemoveConnections();
|
||||
Nodes.Remove(node);
|
||||
Context.OnControlDeleted(node);
|
||||
Context.OnControlDeleted(node, SurfaceNodeActions.User);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -757,54 +837,7 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
if (!CanEdit)
|
||||
return;
|
||||
|
||||
var node = control as SurfaceNode;
|
||||
if (node == null)
|
||||
{
|
||||
Context.OnControlDeleted(control);
|
||||
MarkAsEdited();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((node.Archetype.Flags & NodeFlags.NoRemove) != 0)
|
||||
return;
|
||||
|
||||
if (control.IsSelected)
|
||||
{
|
||||
control.IsSelected = false;
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
if (Undo == null || !withUndo)
|
||||
{
|
||||
// Remove node
|
||||
node.RemoveConnections();
|
||||
Nodes.Remove(node);
|
||||
Context.OnControlDeleted(node);
|
||||
}
|
||||
else
|
||||
{
|
||||
var actions = new List<IUndoAction>();
|
||||
|
||||
// Break connections for node
|
||||
{
|
||||
var action = new EditNodeConnections(Context, node);
|
||||
node.RemoveConnections();
|
||||
action.End();
|
||||
actions.Add(action);
|
||||
}
|
||||
|
||||
// Remove node
|
||||
{
|
||||
var action = new AddRemoveNodeAction(node, false);
|
||||
action.Do();
|
||||
actions.Add(action);
|
||||
}
|
||||
|
||||
Undo.AddAction(new MultiUndoAction(actions, "Remove node"));
|
||||
}
|
||||
|
||||
MarkAsEdited();
|
||||
Delete(new[] { control }, withUndo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -814,72 +847,7 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
if (!CanEdit)
|
||||
return;
|
||||
bool edited = false;
|
||||
|
||||
List<SurfaceNode> nodes = null;
|
||||
for (int i = 0; i < _rootControl.Children.Count; i++)
|
||||
{
|
||||
if (_rootControl.Children[i] is SurfaceNode node)
|
||||
{
|
||||
if (node.IsSelected && (node.Archetype.Flags & NodeFlags.NoRemove) == 0)
|
||||
{
|
||||
if (nodes == null)
|
||||
nodes = new List<SurfaceNode>();
|
||||
nodes.Add(node);
|
||||
edited = true;
|
||||
}
|
||||
}
|
||||
else if (_rootControl.Children[i] is SurfaceControl control && control.IsSelected)
|
||||
{
|
||||
i--;
|
||||
Context.OnControlDeleted(control);
|
||||
edited = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (nodes != null)
|
||||
{
|
||||
if (Undo == null)
|
||||
{
|
||||
// Remove all nodes
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
node.RemoveConnections();
|
||||
Nodes.Remove(node);
|
||||
Context.OnControlDeleted(node);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var actions = new List<IUndoAction>();
|
||||
|
||||
// Break connections for all nodes
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var action = new EditNodeConnections(Context, node);
|
||||
node.RemoveConnections();
|
||||
action.End();
|
||||
actions.Add(action);
|
||||
}
|
||||
|
||||
// Remove all nodes
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var action = new AddRemoveNodeAction(node, false);
|
||||
action.Do();
|
||||
actions.Add(action);
|
||||
}
|
||||
|
||||
Undo.AddAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Remove node" : "Remove nodes"));
|
||||
}
|
||||
}
|
||||
|
||||
if (edited)
|
||||
{
|
||||
MarkAsEdited();
|
||||
}
|
||||
|
||||
SelectionChanged?.Invoke();
|
||||
Delete(SelectedControls, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,6 +13,32 @@ using Utils = FlaxEditor.Utilities.Utils;
|
||||
|
||||
namespace FlaxEditor.Surface
|
||||
{
|
||||
/// <summary>
|
||||
/// Types of surface actions.
|
||||
/// </summary>
|
||||
public enum SurfaceNodeActions
|
||||
{
|
||||
/// <summary>
|
||||
/// Node has been created by surface load.
|
||||
/// </summary>
|
||||
Load,
|
||||
|
||||
/// <summary>
|
||||
/// Node has been created/deleted by user action.
|
||||
/// </summary>
|
||||
User,
|
||||
|
||||
/// <summary>
|
||||
/// Node has been created/deleted via undo.
|
||||
/// </summary>
|
||||
Undo,
|
||||
|
||||
/// <summary>
|
||||
/// Node has been pasted.
|
||||
/// </summary>
|
||||
Paste,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The missing node. Cached the node group, type and stored values information.
|
||||
/// </summary>
|
||||
@@ -135,7 +161,7 @@ namespace FlaxEditor.Surface
|
||||
if (comment == null)
|
||||
throw new InvalidOperationException("Failed to create comment.");
|
||||
|
||||
OnControlLoaded(comment);
|
||||
OnControlLoaded(comment, SurfaceNodeActions.Load);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,7 +170,7 @@ namespace FlaxEditor.Surface
|
||||
for (int i = 0; i < RootControl.Children.Count; i++)
|
||||
{
|
||||
if (RootControl.Children[i] is SurfaceControl control)
|
||||
control.OnSurfaceLoaded();
|
||||
control.OnSurfaceLoaded(SurfaceNodeActions.Load);
|
||||
}
|
||||
|
||||
RootControl.UnlockChildrenRecursive();
|
||||
@@ -650,7 +676,7 @@ namespace FlaxEditor.Surface
|
||||
// Meta
|
||||
node.Meta.Load(stream);
|
||||
|
||||
OnControlLoaded(node);
|
||||
OnControlLoaded(node, SurfaceNodeActions.Load);
|
||||
}
|
||||
}
|
||||
else if (version == 7000)
|
||||
@@ -813,7 +839,7 @@ namespace FlaxEditor.Surface
|
||||
// Meta
|
||||
node.Meta.Load(stream);
|
||||
|
||||
OnControlLoaded(node);
|
||||
OnControlLoaded(node, SurfaceNodeActions.Load);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -856,9 +882,10 @@ namespace FlaxEditor.Surface
|
||||
/// Called when control gets added to the surface as spawn operation (eg. add new comment or add new node).
|
||||
/// </summary>
|
||||
/// <param name="control">The control.</param>
|
||||
public virtual void OnControlSpawned(SurfaceControl control)
|
||||
/// <param name="action">The action node.</param>
|
||||
public virtual void OnControlSpawned(SurfaceControl control, SurfaceNodeActions action)
|
||||
{
|
||||
control.OnSpawned();
|
||||
control.OnSpawned(action);
|
||||
ControlSpawned?.Invoke(control);
|
||||
if (Surface != null && control is SurfaceNode node)
|
||||
Surface.OnNodeSpawned(node);
|
||||
@@ -868,10 +895,11 @@ namespace FlaxEditor.Surface
|
||||
/// Called when control gets removed from the surface as delete/cut operation (eg. remove comment or cut node).
|
||||
/// </summary>
|
||||
/// <param name="control">The control.</param>
|
||||
public virtual void OnControlDeleted(SurfaceControl control)
|
||||
/// <param name="action">The action node.</param>
|
||||
public virtual void OnControlDeleted(SurfaceControl control, SurfaceNodeActions action)
|
||||
{
|
||||
ControlDeleted?.Invoke(control);
|
||||
control.OnDeleted();
|
||||
control.OnDeleted(action);
|
||||
if (control is SurfaceNode node)
|
||||
Surface.OnNodeDeleted(node);
|
||||
}
|
||||
@@ -880,16 +908,17 @@ namespace FlaxEditor.Surface
|
||||
/// Called when control gets loaded and should be added to the surface. Handles surface nodes initialization.
|
||||
/// </summary>
|
||||
/// <param name="control">The control.</param>
|
||||
public virtual void OnControlLoaded(SurfaceControl control)
|
||||
/// <param name="action">The action node.</param>
|
||||
public virtual void OnControlLoaded(SurfaceControl control, SurfaceNodeActions action)
|
||||
{
|
||||
if (control is SurfaceNode node)
|
||||
{
|
||||
// Initialize node
|
||||
OnNodeLoaded(node);
|
||||
OnNodeLoaded(node, action);
|
||||
}
|
||||
|
||||
// Link control
|
||||
control.OnLoaded();
|
||||
control.OnLoaded(action);
|
||||
control.Parent = RootControl;
|
||||
|
||||
if (control is SurfaceComment)
|
||||
@@ -903,7 +932,8 @@ namespace FlaxEditor.Surface
|
||||
/// Called when node gets loaded and should be added to the surface. Creates node elements from the archetype.
|
||||
/// </summary>
|
||||
/// <param name="node">The node.</param>
|
||||
public virtual void OnNodeLoaded(SurfaceNode node)
|
||||
/// <param name="action">The action node.</param>
|
||||
public virtual void OnNodeLoaded(SurfaceNode node, SurfaceNodeActions action)
|
||||
{
|
||||
// Create child elements of the node based on it's archetype
|
||||
int elementsCount = node.Archetype.Elements?.Length ?? 0;
|
||||
|
||||
@@ -315,9 +315,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
// Initialize
|
||||
OnControlLoaded(comment);
|
||||
comment.OnSurfaceLoaded();
|
||||
OnControlSpawned(comment);
|
||||
OnControlLoaded(comment, SurfaceNodeActions.User);
|
||||
comment.OnSurfaceLoaded(SurfaceNodeActions.User);
|
||||
OnControlSpawned(comment, SurfaceNodeActions.User);
|
||||
|
||||
MarkAsModified();
|
||||
|
||||
@@ -361,7 +361,7 @@ namespace FlaxEditor.Surface
|
||||
var flags = nodeArchetype.Flags;
|
||||
nodeArchetype.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
nodeArchetype.Flags &= ~NodeFlags.NoSpawnViaPaste;
|
||||
if (_surface != null && !_surface.CanUseNodeType(nodeArchetype))
|
||||
if (_surface != null && !_surface.CanUseNodeType(groupArchetype, nodeArchetype))
|
||||
{
|
||||
nodeArchetype.Flags = flags;
|
||||
Editor.LogWarning("Cannot spawn given node type. Title: " + nodeArchetype.Title);
|
||||
@@ -389,14 +389,14 @@ namespace FlaxEditor.Surface
|
||||
throw new InvalidOperationException("Invalid node custom values.");
|
||||
}
|
||||
node.Location = location;
|
||||
OnControlLoaded(node);
|
||||
OnControlLoaded(node, SurfaceNodeActions.User);
|
||||
beforeSpawned?.Invoke(node);
|
||||
node.OnSurfaceLoaded();
|
||||
OnControlSpawned(node);
|
||||
node.OnSurfaceLoaded(SurfaceNodeActions.User);
|
||||
OnControlSpawned(node, SurfaceNodeActions.User);
|
||||
|
||||
// Undo action
|
||||
if (Surface != null && Surface.Undo != null)
|
||||
Surface.Undo.AddAction(new AddRemoveNodeAction(node, true));
|
||||
if (Surface != null)
|
||||
Surface.AddBatchedUndoAction(new AddRemoveNodeAction(node, true));
|
||||
|
||||
MarkAsModified();
|
||||
|
||||
|
||||
@@ -4,15 +4,10 @@
|
||||
//#define DEBUG_FIELDS_SEARCHING
|
||||
//#define DEBUG_EVENTS_SEARCHING
|
||||
|
||||
#if DEBUG_INVOKE_METHODS_SEARCHING || DEBUG_FIELDS_SEARCHING || DEBUG_EVENTS_SEARCHING
|
||||
#define DEBUG_SEARCH_TIME
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.SceneGraph;
|
||||
@@ -41,396 +36,7 @@ namespace FlaxEditor.Surface
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
|
||||
internal static class NodesCache
|
||||
{
|
||||
private static readonly object _locker = new object();
|
||||
private static int _version;
|
||||
private static Task _task;
|
||||
private static VisjectCM _taskContextMenu;
|
||||
private static Dictionary<KeyValuePair<string, ushort>, GroupArchetype> _cache;
|
||||
|
||||
public static void Wait()
|
||||
{
|
||||
_task?.Wait();
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
Wait();
|
||||
|
||||
if (_cache != null && _cache.Count != 0)
|
||||
{
|
||||
OnCodeEditingTypesCleared();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Get(VisjectCM contextMenu)
|
||||
{
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (_cache == null)
|
||||
_cache = new Dictionary<KeyValuePair<string, ushort>, GroupArchetype>();
|
||||
contextMenu.LockChildrenRecursive();
|
||||
|
||||
// Check if has cached groups
|
||||
if (_cache.Count != 0)
|
||||
{
|
||||
// Check if context menu doesn't have the recent cached groups
|
||||
if (!contextMenu.Groups.Any(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int asInt && asInt == _version))
|
||||
{
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
foreach (var g in _cache.Values)
|
||||
contextMenu.AddGroup(g);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove any old groups from context menu
|
||||
var groups = contextMenu.Groups.Where(g => g.Archetypes.Count != 0 && g.Archetypes[0].Tag is int).ToArray();
|
||||
foreach (var g in groups)
|
||||
contextMenu.RemoveGroup(g);
|
||||
|
||||
// Register for scripting types reload
|
||||
Editor.Instance.CodeEditing.TypesCleared += OnCodeEditingTypesCleared;
|
||||
|
||||
// Run caching on an async
|
||||
_task = Task.Run(OnActiveContextMenuShowAsync);
|
||||
_taskContextMenu = contextMenu;
|
||||
}
|
||||
|
||||
contextMenu.UnlockChildrenRecursive();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnActiveContextMenuShowAsync()
|
||||
{
|
||||
Profiler.BeginEvent("Setup Visual Script Context Menu (async)");
|
||||
#if DEBUG_SEARCH_TIME
|
||||
var searchStartTime = DateTime.Now;
|
||||
var searchHitsCount = 0;
|
||||
#endif
|
||||
|
||||
foreach (var scriptType in Editor.Instance.CodeEditing.All.Get())
|
||||
{
|
||||
if (!SurfaceUtils.IsValidVisualScriptType(scriptType))
|
||||
continue;
|
||||
|
||||
// Skip Newtonsoft.Json stuff
|
||||
var scriptTypeTypeName = scriptType.TypeName;
|
||||
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
|
||||
continue;
|
||||
var scriptTypeName = scriptType.Name;
|
||||
|
||||
// Enum
|
||||
if (scriptType.IsEnum)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
|
||||
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = scriptTypeName;
|
||||
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(243, 156, 18),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Structure
|
||||
if (scriptType.IsValueType)
|
||||
{
|
||||
if (scriptType.IsVoid)
|
||||
continue;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(155, 89, 182),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create Pack node archetype
|
||||
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Pack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
|
||||
// Create Unpack node archetype
|
||||
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Unpack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
}
|
||||
|
||||
foreach (var member in scriptType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
|
||||
{
|
||||
if (member.IsGeneric)
|
||||
continue;
|
||||
|
||||
if (member.IsMethod)
|
||||
{
|
||||
// Skip methods not declared in this type
|
||||
if (member.Type is MethodInfo m && m.GetBaseDefinition().DeclaringType != m.DeclaringType)
|
||||
continue;
|
||||
var name = member.Name;
|
||||
if (name == "ToString")
|
||||
continue;
|
||||
|
||||
// Skip if searching by name doesn't return a match
|
||||
var members = scriptType.GetMembers(name, MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
if (!members.Contains(member))
|
||||
continue;
|
||||
|
||||
// Check if method is valid for Visual Script usage
|
||||
if (SurfaceUtils.IsValidVisualScriptInvokeMethod(member, out var parameters))
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Function.Nodes[3].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.DefaultValues[1] = name;
|
||||
node.DefaultValues[2] = parameters.Length;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = SurfaceUtils.GetMethodDisplayName((string)node.DefaultValues[1]);
|
||||
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
|
||||
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
|
||||
node.Tag = member;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(109, 160, 24),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
#if DEBUG_INVOKE_METHODS_SEARCHING
|
||||
Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature());
|
||||
searchHitsCount++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (member.IsField)
|
||||
{
|
||||
var name = member.Name;
|
||||
|
||||
// Skip if searching by name doesn't return a match
|
||||
var members = scriptType.GetMembers(name, MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
if (!members.Contains(member))
|
||||
continue;
|
||||
|
||||
// Check if field is valid for Visual Script usage
|
||||
if (SurfaceUtils.IsValidVisualScriptField(member))
|
||||
{
|
||||
if (member.HasGet)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Function.Nodes[6].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.DefaultValues[1] = name;
|
||||
node.DefaultValues[2] = member.ValueType.TypeName;
|
||||
node.DefaultValues[3] = member.IsStatic;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Get " + name;
|
||||
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
|
||||
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(109, 160, 24),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
#if DEBUG_FIELDS_SEARCHING
|
||||
Editor.LogWarning(scriptTypeTypeName + " -> Get " + member.GetSignature());
|
||||
searchHitsCount++;
|
||||
#endif
|
||||
}
|
||||
if (member.HasSet)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Function.Nodes[7].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.DefaultValues[1] = name;
|
||||
node.DefaultValues[2] = member.ValueType.TypeName;
|
||||
node.DefaultValues[3] = member.IsStatic;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Set " + name;
|
||||
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
|
||||
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(109, 160, 24),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
#if DEBUG_FIELDS_SEARCHING
|
||||
Editor.LogWarning(scriptTypeTypeName + " -> Set " + member.GetSignature());
|
||||
searchHitsCount++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (member.IsEvent)
|
||||
{
|
||||
var name = member.Name;
|
||||
|
||||
// Skip if searching by name doesn't return a match
|
||||
var members = scriptType.GetMembers(name, MemberTypes.Event, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
if (!members.Contains(member))
|
||||
continue;
|
||||
|
||||
// Check if field is valid for Visual Script usage
|
||||
if (SurfaceUtils.IsValidVisualScriptEvent(member))
|
||||
{
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
|
||||
if (!_cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(109, 160, 24),
|
||||
Tag = _version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
_cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add Bind event node
|
||||
var bindNode = (NodeArchetype)Archetypes.Function.Nodes[8].Clone();
|
||||
bindNode.DefaultValues[0] = scriptTypeTypeName;
|
||||
bindNode.DefaultValues[1] = name;
|
||||
bindNode.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
bindNode.Title = "Bind " + name;
|
||||
bindNode.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
|
||||
bindNode.SubTitle = string.Format(" (in {0})", scriptTypeName);
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(bindNode);
|
||||
|
||||
// Add Unbind event node
|
||||
var unbindNode = (NodeArchetype)Archetypes.Function.Nodes[9].Clone();
|
||||
unbindNode.DefaultValues[0] = scriptTypeTypeName;
|
||||
unbindNode.DefaultValues[1] = name;
|
||||
unbindNode.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
unbindNode.Title = "Unbind " + name;
|
||||
unbindNode.Description = bindNode.Description;
|
||||
unbindNode.SubTitle = bindNode.SubTitle;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(unbindNode);
|
||||
|
||||
#if DEBUG_EVENTS_SEARCHING
|
||||
Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature());
|
||||
searchHitsCount++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add group to context menu (on a main thread)
|
||||
FlaxEngine.Scripting.InvokeOnUpdate(() =>
|
||||
{
|
||||
#if DEBUG_SEARCH_TIME
|
||||
var addStartTime = DateTime.Now;
|
||||
#endif
|
||||
lock (_locker)
|
||||
{
|
||||
_taskContextMenu.AddGroups(_cache.Values);
|
||||
_taskContextMenu = null;
|
||||
}
|
||||
#if DEBUG_SEARCH_TIME
|
||||
Editor.LogError($"Added items to VisjectCM in: {(DateTime.Now - addStartTime).TotalMilliseconds} ms");
|
||||
#endif
|
||||
});
|
||||
|
||||
#if DEBUG_SEARCH_TIME
|
||||
Editor.LogError($"Collected {searchHitsCount} items in: {(DateTime.Now - searchStartTime).TotalMilliseconds} ms");
|
||||
#endif
|
||||
Profiler.EndEvent();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_task = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnCodeEditingTypesCleared()
|
||||
{
|
||||
Wait();
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_cache.Clear();
|
||||
_version++;
|
||||
}
|
||||
|
||||
Editor.Instance.CodeEditing.TypesCleared -= OnCodeEditingTypesCleared;
|
||||
}
|
||||
}
|
||||
|
||||
private static NodesCache _nodesCache = new NodesCache(IterateNodesCache);
|
||||
private DragActors _dragActors;
|
||||
|
||||
/// <summary>
|
||||
@@ -532,9 +138,9 @@ namespace FlaxEditor.Surface
|
||||
public override bool CanSetParameters => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUseNodeType(NodeArchetype nodeArchetype)
|
||||
public override bool CanUseNodeType(GroupArchetype groupArchetype, NodeArchetype nodeArchetype)
|
||||
{
|
||||
return (nodeArchetype.Flags & NodeFlags.VisualScriptGraph) != 0 && base.CanUseNodeType(nodeArchetype);
|
||||
return (nodeArchetype.Flags & NodeFlags.VisualScriptGraph) != 0 && base.CanUseNodeType(groupArchetype, nodeArchetype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -547,9 +153,8 @@ namespace FlaxEditor.Surface
|
||||
/// <inheritdoc />
|
||||
protected override void OnShowPrimaryMenu(VisjectCM activeCM, Float2 location, Box startBox)
|
||||
{
|
||||
Profiler.BeginEvent("Setup Visual Script Context Menu");
|
||||
|
||||
// Update nodes for method overrides
|
||||
Profiler.BeginEvent("Overrides");
|
||||
activeCM.RemoveGroup(_methodOverridesGroupArchetype);
|
||||
if (Script && !Script.WaitForLoaded(100))
|
||||
{
|
||||
@@ -595,11 +200,10 @@ namespace FlaxEditor.Surface
|
||||
|
||||
activeCM.AddGroup(_methodOverridesGroupArchetype, false);
|
||||
}
|
||||
Profiler.EndEvent();
|
||||
|
||||
// Update nodes for invoke methods (async)
|
||||
NodesCache.Get(activeCM);
|
||||
|
||||
Profiler.EndEvent();
|
||||
_nodesCache.Get(activeCM);
|
||||
|
||||
base.OnShowPrimaryMenu(activeCM, location, startBox);
|
||||
|
||||
@@ -608,7 +212,276 @@ namespace FlaxEditor.Surface
|
||||
|
||||
private void OnActiveContextMenuVisibleChanged(Control activeCM)
|
||||
{
|
||||
NodesCache.Wait();
|
||||
_nodesCache.Wait();
|
||||
}
|
||||
|
||||
private static void IterateNodesCache(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version)
|
||||
{
|
||||
// Skip Newtonsoft.Json stuff
|
||||
var scriptTypeTypeName = scriptType.TypeName;
|
||||
if (scriptTypeTypeName.StartsWith("Newtonsoft.Json."))
|
||||
return;
|
||||
var scriptTypeName = scriptType.Name;
|
||||
|
||||
// Enum
|
||||
if (scriptType.IsEnum)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Constants.Nodes[10].Clone();
|
||||
node.DefaultValues[0] = Activator.CreateInstance(scriptType.Type);
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = scriptTypeName;
|
||||
node.Description = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 2);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(243, 156, 18),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
// Structure
|
||||
if (scriptType.IsValueType)
|
||||
{
|
||||
if (scriptType.IsVoid)
|
||||
return;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 4);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(155, 89, 182),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
var tooltip = Editor.Instance.CodeDocs.GetTooltip(scriptType);
|
||||
|
||||
// Create Pack node archetype
|
||||
var node = (NodeArchetype)Archetypes.Packing.Nodes[6].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Pack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
|
||||
// Create Unpack node archetype
|
||||
node = (NodeArchetype)Archetypes.Packing.Nodes[13].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Unpack " + scriptTypeName;
|
||||
node.Description = tooltip;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
}
|
||||
|
||||
foreach (var member in scriptType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
|
||||
{
|
||||
if (member.IsGeneric)
|
||||
continue;
|
||||
|
||||
if (member.IsMethod)
|
||||
{
|
||||
// Skip methods not declared in this type
|
||||
if (member.Type is MethodInfo m && m.GetBaseDefinition().DeclaringType != m.DeclaringType)
|
||||
continue;
|
||||
var name = member.Name;
|
||||
if (name == "ToString")
|
||||
continue;
|
||||
|
||||
// Skip if searching by name doesn't return a match
|
||||
var members = scriptType.GetMembers(name, MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
if (!members.Contains(member))
|
||||
continue;
|
||||
|
||||
// Check if method is valid for Visual Script usage
|
||||
if (SurfaceUtils.IsValidVisualScriptInvokeMethod(member, out var parameters))
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Function.Nodes[3].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.DefaultValues[1] = name;
|
||||
node.DefaultValues[2] = parameters.Length;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = SurfaceUtils.GetMethodDisplayName((string)node.DefaultValues[1]);
|
||||
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
|
||||
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
|
||||
node.Tag = member;
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(109, 160, 24),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
#if DEBUG_INVOKE_METHODS_SEARCHING
|
||||
Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (member.IsField)
|
||||
{
|
||||
var name = member.Name;
|
||||
|
||||
// Skip if searching by name doesn't return a match
|
||||
var members = scriptType.GetMembers(name, MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
if (!members.Contains(member))
|
||||
continue;
|
||||
|
||||
// Check if field is valid for Visual Script usage
|
||||
if (SurfaceUtils.IsValidVisualScriptField(member))
|
||||
{
|
||||
if (member.HasGet)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Function.Nodes[6].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.DefaultValues[1] = name;
|
||||
node.DefaultValues[2] = member.ValueType.TypeName;
|
||||
node.DefaultValues[3] = member.IsStatic;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Get " + name;
|
||||
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
|
||||
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(109, 160, 24),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
#if DEBUG_FIELDS_SEARCHING
|
||||
Editor.LogWarning(scriptTypeTypeName + " -> Get " + member.GetSignature());
|
||||
#endif
|
||||
}
|
||||
if (member.HasSet)
|
||||
{
|
||||
// Create node archetype
|
||||
var node = (NodeArchetype)Archetypes.Function.Nodes[7].Clone();
|
||||
node.DefaultValues[0] = scriptTypeTypeName;
|
||||
node.DefaultValues[1] = name;
|
||||
node.DefaultValues[2] = member.ValueType.TypeName;
|
||||
node.DefaultValues[3] = member.IsStatic;
|
||||
node.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
node.Title = "Set " + name;
|
||||
node.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
|
||||
node.SubTitle = string.Format(" (in {0})", scriptTypeName);
|
||||
|
||||
// Create group archetype
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(109, 160, 24),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add node to the group
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(node);
|
||||
#if DEBUG_FIELDS_SEARCHING
|
||||
Editor.LogWarning(scriptTypeTypeName + " -> Set " + member.GetSignature());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (member.IsEvent)
|
||||
{
|
||||
var name = member.Name;
|
||||
|
||||
// Skip if searching by name doesn't return a match
|
||||
var members = scriptType.GetMembers(name, MemberTypes.Event, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
if (!members.Contains(member))
|
||||
continue;
|
||||
|
||||
// Check if field is valid for Visual Script usage
|
||||
if (SurfaceUtils.IsValidVisualScriptEvent(member))
|
||||
{
|
||||
var groupKey = new KeyValuePair<string, ushort>(scriptTypeName, 16);
|
||||
if (!cache.TryGetValue(groupKey, out var group))
|
||||
{
|
||||
group = new GroupArchetype
|
||||
{
|
||||
GroupID = groupKey.Value,
|
||||
Name = groupKey.Key,
|
||||
Color = new Color(109, 160, 24),
|
||||
Tag = version,
|
||||
Archetypes = new List<NodeArchetype>(),
|
||||
};
|
||||
cache.Add(groupKey, group);
|
||||
}
|
||||
|
||||
// Add Bind event node
|
||||
var bindNode = (NodeArchetype)Archetypes.Function.Nodes[8].Clone();
|
||||
bindNode.DefaultValues[0] = scriptTypeTypeName;
|
||||
bindNode.DefaultValues[1] = name;
|
||||
bindNode.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
bindNode.Title = "Bind " + name;
|
||||
bindNode.Description = SurfaceUtils.GetVisualScriptMemberInfoDescription(member);
|
||||
bindNode.SubTitle = string.Format(" (in {0})", scriptTypeName);
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(bindNode);
|
||||
|
||||
// Add Unbind event node
|
||||
var unbindNode = (NodeArchetype)Archetypes.Function.Nodes[9].Clone();
|
||||
unbindNode.DefaultValues[0] = scriptTypeTypeName;
|
||||
unbindNode.DefaultValues[1] = name;
|
||||
unbindNode.Flags &= ~NodeFlags.NoSpawnViaGUI;
|
||||
unbindNode.Title = "Unbind " + name;
|
||||
unbindNode.Description = bindNode.Description;
|
||||
unbindNode.SubTitle = bindNode.SubTitle;
|
||||
((IList<NodeArchetype>)group.Archetypes).Add(unbindNode);
|
||||
|
||||
#if DEBUG_EVENTS_SEARCHING
|
||||
Editor.LogWarning(scriptTypeTypeName + " -> " + member.GetSignature());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -686,7 +559,7 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
NodesCache.Wait();
|
||||
_nodesCache.Wait();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user