diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs
index 454c3a5d2..b0c3a36dd 100644
--- a/Source/Editor/Scripting/ScriptType.cs
+++ b/Source/Editor/Scripting/ScriptType.cs
@@ -1393,5 +1393,32 @@ namespace FlaxEditor.Scripting
return _custom.GetMembers(name, MemberTypes.Method, bindingAttr).FirstOrDefault();
return ScriptMemberInfo.Null;
}
+
+ ///
+ /// Basic check to see if a type could be casted to another type
+ ///
+ /// Source type
+ /// Target type
+ /// True if the type can be casted
+ public static bool CanCast(ScriptType from, ScriptType to)
+ {
+ if (from == to)
+ return true;
+ if (from == Null || to == Null)
+ return false;
+ return (from.Type != typeof(void) && from.Type != typeof(FlaxEngine.Object)) &&
+ (to.Type != typeof(void) && to.Type != typeof(FlaxEngine.Object)) &&
+ from.IsAssignableFrom(to);
+ }
+
+ ///
+ /// Basic check to see if this type could be casted to another type
+ ///
+ /// Target type
+ /// True if the type can be casted
+ public bool CanCastTo(ScriptType to)
+ {
+ return CanCast(this, to);
+ }
}
}
diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs
index cffb2ad6f..e9d73ae68 100644
--- a/Source/Editor/Surface/Archetypes/Function.cs
+++ b/Source/Editor/Surface/Archetypes/Function.cs
@@ -744,6 +744,16 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
+
+ internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
+ {
+ return false;
+ }
+
+ internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
+ {
+ return inputType.IsVoid;
+ }
}
private sealed class InvokeMethodNode : SurfaceNode
@@ -1151,6 +1161,54 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
+
+ internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
+ {
+ if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
+ return false;
+
+ if (!memberInfo.IsStatic)
+ {
+ if (VisjectSurface.FullCastCheck(memberInfo.DeclaringType, outputType, hint))
+ return true;
+ }
+
+ var parameters = memberInfo.GetParameters();
+ bool isPure = (parameters.Length == 0 && !memberInfo.ValueType.IsVoid);
+ if (outputType.IsVoid)
+ return !isPure;
+
+ foreach (var param in parameters)
+ {
+ if (param.IsOut)
+ continue;
+ if (VisjectSurface.FullCastCheck(param.Type, outputType, hint))
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
+ {
+ if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
+ return false;
+ if (VisjectSurface.FullCastCheck(memberInfo.ValueType, inputType, hint))
+ return true;
+
+ var parameters = memberInfo.GetParameters();
+ bool isPure = (parameters.Length == 0 && !memberInfo.ValueType.IsVoid);
+ if (inputType.IsVoid)
+ return !isPure;
+
+ foreach (var param in memberInfo.GetParameters())
+ {
+ if (!param.IsOut)
+ continue;
+ if (VisjectSurface.FullCastCheck(param.Type, inputType, hint))
+ return true;
+ }
+ return false;
+ }
}
private sealed class ReturnNode : SurfaceNode
@@ -1777,6 +1835,16 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
+
+ internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
+ {
+ return false;
+ }
+
+ internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
+ {
+ return inputType.IsVoid;
+ }
}
private abstract class FieldNodeBase : SurfaceNode
@@ -1913,6 +1981,64 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Get " + SurfaceUtils.GetMethodDisplayName((string)Values[1]);
UpdateSignature();
}
+
+ internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
+ {
+ var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
+ if (scriptType == ScriptType.Null)
+ return false;
+
+ var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
+ foreach (var member in members)
+ {
+ if (!SurfaceUtils.IsValidVisualScriptField(member))
+ continue;
+
+ if (member)
+ {
+ if (!member.IsStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
+ return true;
+ }
+ else
+ {
+ var isStatic = (bool)nodeArch.DefaultValues[3];
+ if (!isStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
+ return true;
+ }
+ break;
+ }
+
+ return false;
+ }
+
+ internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
+ {
+ var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
+ if (scriptType == ScriptType.Null)
+ return false;
+
+ var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
+ foreach (var member in members)
+ {
+ if (!SurfaceUtils.IsValidVisualScriptField(member))
+ continue;
+
+ if (member)
+ {
+ if (VisjectSurface.FullCastCheck(member.ValueType, inputType, hint))
+ return true;
+ }
+ else
+ {
+ var typeName = (string)nodeArch.DefaultValues[2];
+ if (VisjectSurface.FullCastCheck(TypeUtils.GetType(typeName), inputType, hint))
+ return true;
+ }
+ break;
+ }
+
+ return false;
+ }
}
private sealed class SetFieldNode : FieldNodeBase
@@ -1966,6 +2092,48 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Set " + SurfaceUtils.GetMethodDisplayName((string)Values[1]);
UpdateSignature();
}
+
+ internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
+ {
+ if (outputType.IsVoid)
+ return true;
+
+ var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
+ if (scriptType == ScriptType.Null)
+ return false;
+
+ var members = scriptType.GetMembers((string)nodeArch.DefaultValues[1], MemberTypes.Field, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
+ foreach (var member in members)
+ {
+ if (!SurfaceUtils.IsValidVisualScriptField(member))
+ continue;
+
+ if (member)
+ {
+ if (VisjectSurface.FullCastCheck(member.ValueType, outputType, hint))
+ return true;
+ if (!member.IsStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
+ return true;
+ }
+ else
+ {
+ var typeName = (string)nodeArch.DefaultValues[2];
+ if (VisjectSurface.FullCastCheck(TypeUtils.GetType(typeName), outputType, hint))
+ return true;
+ var isStatic = (bool)nodeArch.DefaultValues[3];
+ if (!isStatic && VisjectSurface.FullCastCheck(scriptType, outputType, hint))
+ return true;
+ }
+ break;
+ }
+
+ return false;
+ }
+
+ internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
+ {
+ return inputType.IsVoid;
+ }
}
private abstract class EventBaseNode : SurfaceNode, IFunctionsDependantNode
@@ -2184,6 +2352,43 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy();
}
+
+ internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
+ {
+ // Event based nodes always have a pulse input, so it's always compatible with void
+ if (outputType.IsVoid)
+ return true;
+
+ var eventName = (string)nodeArch.DefaultValues[1];
+ var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
+ var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
+ if (member && SurfaceUtils.IsValidVisualScriptEvent(member))
+ {
+ if (!member.IsStatic)
+ {
+ if (VisjectSurface.FullCastCheck(eventType, outputType, hint))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
+ {
+ // Event based nodes always have a pulse output, so it's always compatible with void
+ if (inputType.IsVoid)
+ return true;
+
+ var eventName = (string)nodeArch.DefaultValues[1];
+ var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
+ var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
+ if (member && SurfaceUtils.IsValidVisualScriptEvent(member))
+ {
+ if (VisjectSurface.FullCastCheck(member.ValueType, inputType, hint))
+ return true;
+ }
+ return false;
+ }
}
private sealed class BindEventNode : EventBaseNode
@@ -2265,6 +2470,8 @@ namespace FlaxEditor.Surface.Archetypes
Title = string.Empty,
Description = "Overrides the base class method with custom implementation",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste,
+ IsInputCompatible = MethodOverrideNode.IsInputCompatible,
+ IsOutputCompatible = MethodOverrideNode.IsOutputCompatible,
Size = new Float2(240, 60),
DefaultValues = new object[]
{
@@ -2277,6 +2484,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 4,
Create = (id, context, arch, groupArch) => new InvokeMethodNode(id, context, arch, groupArch),
+ IsInputCompatible = InvokeMethodNode.IsInputCompatible,
+ IsOutputCompatible = InvokeMethodNode.IsOutputCompatible,
Title = string.Empty,
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(240, 60),
@@ -2317,6 +2526,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 6,
Create = (id, context, arch, groupArch) => new VisualScriptFunctionNode(id, context, arch, groupArch),
+ IsInputCompatible = VisualScriptFunctionNode.IsInputCompatible,
+ IsOutputCompatible = VisualScriptFunctionNode.IsOutputCompatible,
Title = "New Function",
Description = "Adds a new function to the script",
Flags = NodeFlags.VisualScriptGraph,
@@ -2330,6 +2541,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 7,
Create = (id, context, arch, groupArch) => new GetFieldNode(id, context, arch, groupArch),
+ IsInputCompatible = GetFieldNode.IsInputCompatible,
+ IsOutputCompatible = GetFieldNode.IsOutputCompatible,
Title = string.Empty,
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(240, 60),
@@ -2345,6 +2558,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 8,
Create = (id, context, arch, groupArch) => new SetFieldNode(id, context, arch, groupArch),
+ IsInputCompatible = SetFieldNode.IsInputCompatible,
+ IsOutputCompatible = SetFieldNode.IsOutputCompatible,
Title = string.Empty,
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(240, 60),
@@ -2361,6 +2576,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 9,
Create = (id, context, arch, groupArch) => new BindEventNode(id, context, arch, groupArch),
+ IsInputCompatible = EventBaseNode.IsInputCompatible,
+ IsOutputCompatible = EventBaseNode.IsOutputCompatible,
Title = string.Empty,
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(260, 60),
@@ -2383,6 +2600,8 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 10,
Create = (id, context, arch, groupArch) => new UnbindEventNode(id, context, arch, groupArch),
+ IsInputCompatible = EventBaseNode.IsInputCompatible,
+ IsOutputCompatible = EventBaseNode.IsOutputCompatible,
Title = string.Empty,
Flags = NodeFlags.VisualScriptGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(260, 60),
diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs
index 5a5e9cd5b..e61b8e0df 100644
--- a/Source/Editor/Surface/Archetypes/Packing.cs
+++ b/Source/Editor/Surface/Archetypes/Packing.cs
@@ -207,6 +207,26 @@ namespace FlaxEditor.Surface.Archetypes
AddElement(box);
}
}
+
+ protected static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
+ {
+ // Event based nodes always have a pulse input, so it's always compatible with void
+ if (outputType.IsVoid)
+ return true;
+
+ var eventName = (string)nodeArch.DefaultValues[1];
+ var eventType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
+ var member = eventType.GetMember(eventName, MemberTypes.Event, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
+ if (member && SurfaceUtils.IsValidVisualScriptEvent(member))
+ {
+ if (!member.IsStatic)
+ {
+ if (VisjectSurface.FullCastCheck(eventType, outputType, hint))
+ return true;
+ }
+ }
+ return false;
+ }
}
private sealed class PackStructureNode : StructureNode
@@ -216,6 +236,35 @@ namespace FlaxEditor.Surface.Archetypes
: base(id, context, nodeArch, groupArch, false)
{
}
+
+ internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
+ {
+ var typeName = (string)nodeArch.DefaultValues[0];
+ var type = TypeUtils.GetType(typeName);
+ if (type)
+ {
+ var fields = type.GetMembers(BindingFlags.Public | BindingFlags.Instance).Where(x => x.IsField).ToArray();
+ var fieldsLength = fields.Length;
+ for (var i = 0; i < fieldsLength; i++)
+ {
+ if (VisjectSurface.FullCastCheck(fields[i].ValueType, outputType, hint))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
+ {
+ var typeName = (string)nodeArch.DefaultValues[0];
+ var type = TypeUtils.GetType(typeName);
+ if (type)
+ {
+ if (VisjectSurface.FullCastCheck(type, inputType, hint))
+ return true;
+ }
+ return false;
+ }
}
private sealed class UnpackStructureNode : StructureNode
@@ -225,6 +274,35 @@ namespace FlaxEditor.Surface.Archetypes
: base(id, context, nodeArch, groupArch, true)
{
}
+
+ internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint)
+ {
+ var typeName = (string)nodeArch.DefaultValues[0];
+ var type = TypeUtils.GetType(typeName);
+ if (type)
+ {
+ if (VisjectSurface.FullCastCheck(type, outputType, hint))
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint)
+ {
+ var typeName = (string)nodeArch.DefaultValues[0];
+ var type = TypeUtils.GetType(typeName);
+ if (type)
+ {
+ var fields = type.GetMembers(BindingFlags.Public | BindingFlags.Instance).Where(x => x.IsField).ToArray();
+ var fieldsLength = fields.Length;
+ for (var i = 0; i < fieldsLength; i++)
+ {
+ if (VisjectSurface.FullCastCheck(fields[i].ValueType, inputType, hint))
+ return true;
+ }
+ }
+ return false;
+ }
}
///
@@ -351,6 +429,8 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 26,
Title = "Pack Structure",
Create = (id, context, arch, groupArch) => new PackStructureNode(id, context, arch, groupArch),
+ IsInputCompatible = PackStructureNode.IsInputCompatible,
+ IsOutputCompatible = PackStructureNode.IsOutputCompatible,
Description = "Makes the structure data to from the components.",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(180, 20),
@@ -461,6 +541,8 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 36,
Title = "Unpack Structure",
Create = (id, context, arch, groupArch) => new UnpackStructureNode(id, context, arch, groupArch),
+ IsInputCompatible = UnpackStructureNode.IsInputCompatible,
+ IsOutputCompatible = UnpackStructureNode.IsOutputCompatible,
Description = "Breaks the structure data to allow extracting components from it.",
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(180, 20),
diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs
index bae62556e..342429fc8 100644
--- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs
+++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs
@@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Linq;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
-using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -40,6 +39,8 @@ namespace FlaxEditor.Surface.ContextMenu
public delegate List ParameterGetterDelegate();
private readonly List _groups = new List(16);
+ private CheckBox _contextSensitiveToggle;
+ private bool _contextSensitiveSearchEnabled = true;
private readonly TextBox _searchBox;
private bool _waitingForInput;
private VisjectCMGroup _surfaceParametersGroup;
@@ -127,7 +128,7 @@ namespace FlaxEditor.Surface.ContextMenu
_parameterSetNodeArchetype = info.ParameterSetNodeArchetype ?? Archetypes.Parameters.Nodes[3];
// Context menu dimensions
- Size = new Float2(320, 248);
+ Size = new Float2(300, 400);
var headerPanel = new Panel(ScrollBars.None)
{
@@ -139,17 +140,41 @@ namespace FlaxEditor.Surface.ContextMenu
};
// Title bar
+ var titleFontReference = new FontReference(Style.Current.FontLarge.Asset, 10);
var titleLabel = new Label
{
- Width = Width - 8,
+ Width = Width * 0.5f - 8f,
Height = 20,
X = 4,
Parent = headerPanel,
Text = "Select Node",
- HorizontalAlignment = TextAlignment.Center,
- Font = new FontReference(Style.Current.FontLarge.Asset, 10),
+ HorizontalAlignment = TextAlignment.Near,
+ Font = titleFontReference,
};
+ // Context sensitive toggle
+ var contextSensitiveLabel = new Label
+ {
+ Width = Width * 0.5f - 28,
+ Height = 20,
+ X = Width * 0.5f,
+ Parent = headerPanel,
+ Text = "Context Sensitive",
+ TooltipText = "Should the nodes be filtered to only show those that can be connected in the current context?",
+ HorizontalAlignment = TextAlignment.Far,
+ Font = titleFontReference,
+ };
+
+ _contextSensitiveToggle = new CheckBox
+ {
+ Width = 20,
+ Height = 20,
+ X = Width - 24,
+ Parent = headerPanel,
+ Checked = _contextSensitiveSearchEnabled,
+ };
+ _contextSensitiveToggle.StateChanged += OnContextSensitiveToggleStateChanged;
+
// Search box
_searchBox = new SearchBox(false, 2, 22)
{
@@ -288,6 +313,10 @@ namespace FlaxEditor.Surface.ContextMenu
OnSearchFilterChanged();
}
}
+ else if (_contextSensitiveSearchEnabled)
+ {
+ group.EvaluateVisibilityWithBox(_selectedBox);
+ }
Profiler.EndEvent();
}
@@ -321,6 +350,8 @@ namespace FlaxEditor.Surface.ContextMenu
Parent = group
};
}
+ if (_contextSensitiveSearchEnabled)
+ group.EvaluateVisibilityWithBox(_selectedBox);
group.SortChildren();
group.Parent = _groupsPanel;
_groups.Add(group);
@@ -418,8 +449,26 @@ namespace FlaxEditor.Surface.ContextMenu
return;
Profiler.BeginEvent("VisjectCM.OnSearchFilterChanged");
-
- if (string.IsNullOrEmpty(_searchBox.Text))
+ UpdateFilters();
+ _searchBox.Focus();
+ Profiler.EndEvent();
+ }
+
+ private void OnContextSensitiveToggleStateChanged(CheckBox checkBox)
+ {
+ // Skip events during setup or init stuff
+ if (IsLayoutLocked)
+ return;
+
+ Profiler.BeginEvent("VisjectCM.OnContextSensitiveToggleStateChanged");
+ _contextSensitiveSearchEnabled = checkBox.Checked;
+ UpdateFilters();
+ Profiler.EndEvent();
+ }
+
+ private void UpdateFilters()
+ {
+ if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBox == null)
{
ResetView();
Profiler.EndEvent();
@@ -430,7 +479,7 @@ namespace FlaxEditor.Surface.ContextMenu
LockChildrenRecursive();
for (int i = 0; i < _groups.Count; i++)
{
- _groups[i].UpdateFilter(_searchBox.Text);
+ _groups[i].UpdateFilter(_searchBox.Text, _contextSensitiveSearchEnabled ? _selectedBox : null);
_groups[i].UpdateItemSort(_selectedBox);
}
SortGroups();
@@ -443,9 +492,6 @@ namespace FlaxEditor.Surface.ContextMenu
PerformLayout();
if (SelectedItem != null)
_panel1.ScrollViewTo(SelectedItem);
- _searchBox.Focus();
- Profiler.EndEvent();
-
Profiler.EndEvent();
}
@@ -503,7 +549,11 @@ namespace FlaxEditor.Surface.ContextMenu
_searchBox.Clear();
SelectedItem = null;
for (int i = 0; i < _groups.Count; i++)
+ {
_groups[i].ResetView();
+ if (_contextSensitiveSearchEnabled)
+ _groups[i].EvaluateVisibilityWithBox(_selectedBox);
+ }
UnlockChildrenRecursive();
SortGroups();
@@ -759,5 +809,12 @@ namespace FlaxEditor.Surface.ContextMenu
{
return GetPreviousSiblings(item).OfType();
}
+
+ ///
+ public override void OnDestroy()
+ {
+ _contextSensitiveToggle.StateChanged -= OnContextSensitiveToggleStateChanged;
+ base.OnDestroy();
+ }
}
}
diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs
index 446840f2c..fd825cdff 100644
--- a/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs
+++ b/Source/Editor/Surface/ContextMenu/VisjectCMGroup.cs
@@ -66,7 +66,7 @@ namespace FlaxEditor.Surface.ContextMenu
{
if (_children[i] is VisjectCMItem item)
{
- item.UpdateFilter(null);
+ item.UpdateFilter(null, null);
item.UpdateScore(null);
}
}
@@ -84,23 +84,41 @@ namespace FlaxEditor.Surface.ContextMenu
/// Updates the filter.
///
/// The filter text.
- public void UpdateFilter(string filterText)
+ public void UpdateFilter(string filterText, Box selectedBox)
{
Profiler.BeginEvent("VisjectCMGroup.UpdateFilter");
// Update items
bool isAnyVisible = false;
+ bool groupHeaderMatches = QueryFilterHelper.Match(filterText, HeaderText);
for (int i = 0; i < _children.Count; i++)
{
if (_children[i] is VisjectCMItem item)
{
- item.UpdateFilter(filterText);
+ item.UpdateFilter(filterText, selectedBox, groupHeaderMatches);
isAnyVisible |= item.Visible;
}
}
- // Update header title
- if (QueryFilterHelper.Match(filterText, HeaderText))
+ // Update itself
+ if (isAnyVisible)
+ {
+ if (!string.IsNullOrEmpty(filterText))
+ Open(false);
+ Visible = true;
+ }
+ else
+ {
+ // Hide group if none of the items matched the filter
+ Visible = false;
+ }
+
+ Profiler.EndEvent();
+ }
+
+ public void EvaluateVisibilityWithBox(Box selectedBox)
+ {
+ if (selectedBox == null)
{
for (int i = 0; i < _children.Count; i++)
{
@@ -109,14 +127,25 @@ namespace FlaxEditor.Surface.ContextMenu
item.Visible = true;
}
}
- isAnyVisible = true;
+ Visible = true;
+ return;
+ }
+
+ Profiler.BeginEvent("VisjectCMGroup.EvaluateVisibilityWithBox");
+
+ bool isAnyVisible = false;
+ for (int i = 0; i < _children.Count; i++)
+ {
+ if (_children[i] is VisjectCMItem item)
+ {
+ item.Visible = item.CanConnectTo(selectedBox);
+ isAnyVisible |= item.Visible;
+ }
}
// Update itself
if (isAnyVisible)
{
- if (!string.IsNullOrEmpty(filterText))
- Open(false);
Visible = true;
}
else
diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs
index b68d529a1..b512f6946 100644
--- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs
+++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs
@@ -3,10 +3,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface.ContextMenu
{
@@ -56,7 +58,7 @@ namespace FlaxEditor.Surface.ContextMenu
/// The group archetype.
/// The archetype.
public VisjectCMItem(VisjectCMGroup group, GroupArchetype groupArchetype, NodeArchetype archetype)
- : base(0, 0, 120, 12)
+ : base(0, 0, 120, 14)
{
Group = group;
_groupArchetype = groupArchetype;
@@ -77,7 +79,7 @@ namespace FlaxEditor.Surface.ContextMenu
if (!Visible)
return;
- if (selectedBox != null && CanConnectTo(selectedBox, NodeArchetype))
+ if (selectedBox != null && CanConnectTo(selectedBox))
SortScore += 1;
if (Data != null)
SortScore += 1;
@@ -92,35 +94,91 @@ namespace FlaxEditor.Surface.ContextMenu
textRect = new Rectangle(22, 0, Width - 24, Height);
}
- private bool CanConnectTo(Box startBox, NodeArchetype nodeArchetype)
+ ///
+ /// Checks if this context menu item can be connected to a given box, before a node is actually spawned.
+ ///
+ /// The connected box
+ /// True if the connected box is compatible with this item
+ public bool CanConnectTo(Box startBox)
{
+ // Is compatible if box is null for reset reasons
if (startBox == null)
- return false;
- if (!startBox.IsOutput)
- return false; // For now, I'm only handing the output box case
+ return true;
- if (nodeArchetype.Elements != null)
+ if (_archetype == null)
+ return false;
+
+ bool isCompatible = false;
+ if (startBox.IsOutput && _archetype.IsInputCompatible != null)
{
- for (int i = 0; i < nodeArchetype.Elements.Length; i++)
- {
- if (nodeArchetype.Elements[i].Type == NodeElementType.Input &&
- startBox.CanUseType(nodeArchetype.Elements[i].ConnectionsType))
- {
- return true;
- }
- }
+ isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints);
}
- return false;
+ else if (!startBox.IsOutput && _archetype.IsOutputCompatible != null)
+ {
+ isCompatible |= _archetype.IsOutputCompatible.Invoke(_archetype, startBox.CurrentType, startBox.ParentNode.Archetype.ConnectionsHints);
+ }
+ else if (_archetype.Elements != null)
+ {
+ // Check compatibility based on the defined elements in the archetype. This handles all the default groups and items
+ isCompatible = CheckElementsCompatibility(startBox);
+ }
+
+ return isCompatible;
+ }
+
+ private bool CheckElementsCompatibility(Box startBox)
+ {
+ bool isCompatible = false;
+ foreach (NodeElementArchetype element in _archetype.Elements)
+ {
+ // Ignore all elements that aren't inputs or outputs (e.g. input fields)
+ if (element.Type != NodeElementType.Output && element.Type != NodeElementType.Input)
+ continue;
+
+ // Ignore elements with the same direction as the box
+ if ((startBox.IsOutput && element.Type == NodeElementType.Output) || (!startBox.IsOutput && element.Type == NodeElementType.Input))
+ continue;
+
+ ScriptType fromType;
+ ScriptType toType;
+ ConnectionsHint hint;
+ if (startBox.IsOutput)
+ {
+ fromType = element.ConnectionsType;
+ toType = startBox.CurrentType;
+ hint = _archetype.ConnectionsHints;
+ }
+ else
+ {
+ fromType = startBox.CurrentType;
+ toType = element.ConnectionsType;
+ hint = startBox.ParentNode.Archetype.ConnectionsHints;
+ }
+
+ isCompatible |= VisjectSurface.FullCastCheck(fromType, toType, hint);
+ }
+
+ return isCompatible;
}
///
/// Updates the filter.
///
/// The filter text.
- public void UpdateFilter(string filterText)
+ public void UpdateFilter(string filterText, Box selectedBox, bool groupHeaderMatches = false)
{
+ if (selectedBox != null)
+ {
+ Visible = CanConnectTo(selectedBox);
+ if (!Visible)
+ {
+ _highlights?.Clear();
+ return;
+ }
+ }
+
_isStartsWithMatch = _isFullMatch = false;
- if (filterText == null)
+ if (string.IsNullOrEmpty(filterText))
{
// Clear filter
_highlights?.Clear();
@@ -184,7 +242,7 @@ namespace FlaxEditor.Surface.ContextMenu
Data = data;
}
- else
+ else if (!groupHeaderMatches)
{
// Hide
_highlights?.Clear();
diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs
index e771fa71c..a03c8f701 100644
--- a/Source/Editor/Surface/Elements/Box.cs
+++ b/Source/Editor/Surface/Elements/Box.cs
@@ -91,7 +91,7 @@ namespace FlaxEditor.Surface.Elements
_currentType = value;
// Check if will need to update box connections due to type change
- if ((Surface == null || Surface._isUpdatingBoxTypes == 0) && HasAnyConnection && !CanCast(prev, _currentType))
+ if ((Surface == null || Surface._isUpdatingBoxTypes == 0) && HasAnyConnection && !prev.CanCastTo(_currentType))
{
// Remove all invalid connections and update those which still can be valid
var connections = Connections.ToArray();
@@ -231,58 +231,8 @@ namespace FlaxEditor.Surface.Elements
}
// Check using connection hints
- var connectionsHints = ParentNode.Archetype.ConnectionsHints;
- if (Archetype.ConnectionsType == ScriptType.Null && connectionsHints != ConnectionsHint.None)
- {
- if ((connectionsHints & ConnectionsHint.Anything) == ConnectionsHint.Anything)
- return true;
- if ((connectionsHints & ConnectionsHint.Value) == ConnectionsHint.Value && type.Type != typeof(void))
- return true;
- if ((connectionsHints & ConnectionsHint.Enum) == ConnectionsHint.Enum && type.IsEnum)
- return true;
- if ((connectionsHints & ConnectionsHint.Array) == ConnectionsHint.Array && type.IsArray)
- return true;
- if ((connectionsHints & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && type.IsDictionary)
- return true;
- if ((connectionsHints & ConnectionsHint.Vector) == ConnectionsHint.Vector)
- {
- var t = type.Type;
- if (t == typeof(Vector2) ||
- t == typeof(Vector3) ||
- t == typeof(Vector4) ||
- t == typeof(Float2) ||
- t == typeof(Float3) ||
- t == typeof(Float4) ||
- t == typeof(Double2) ||
- t == typeof(Double3) ||
- t == typeof(Double4) ||
- t == typeof(Int2) ||
- t == typeof(Int3) ||
- t == typeof(Int4) ||
- t == typeof(Color))
- {
- return true;
- }
- }
- if ((connectionsHints & ConnectionsHint.Scalar) == ConnectionsHint.Scalar)
- {
- var t = type.Type;
- if (t == typeof(bool) ||
- t == typeof(char) ||
- t == typeof(byte) ||
- t == typeof(short) ||
- t == typeof(ushort) ||
- t == typeof(int) ||
- t == typeof(uint) ||
- t == typeof(long) ||
- t == typeof(ulong) ||
- t == typeof(float) ||
- t == typeof(double))
- {
- return true;
- }
- }
- }
+ if (VisjectSurface.IsTypeCompatible(Archetype.ConnectionsType, type, ParentNode.Archetype.ConnectionsHints))
+ return true;
// Check independent and if there is box with bigger potential because it may block current one from changing type
var parentArch = ParentNode.Archetype;
@@ -296,7 +246,7 @@ namespace FlaxEditor.Surface.Elements
var b = ParentNode.GetBox(boxes[i]);
// Check if its the same and tested type matches the default value type
- if (b == this && CanCast(parentArch.DefaultType, type))
+ if (b == this && parentArch.DefaultType.CanCastTo(type))
{
// Can
return true;
@@ -718,17 +668,6 @@ namespace FlaxEditor.Surface.Elements
}
}
- private static bool CanCast(ScriptType oB, ScriptType iB)
- {
- if (oB == iB)
- return true;
- if (oB == ScriptType.Null || iB == ScriptType.Null)
- return false;
- return (oB.Type != typeof(void) && oB.Type != typeof(FlaxEngine.Object)) &&
- (iB.Type != typeof(void) && iB.Type != typeof(FlaxEngine.Object)) &&
- oB.IsAssignableFrom(iB);
- }
-
///
public bool AreConnected(IConnectionInstigator other)
{
@@ -787,7 +726,7 @@ namespace FlaxEditor.Surface.Elements
{
if (!iB.CanUseType(oB.CurrentType))
{
- if (!CanCast(oB.CurrentType, iB.CurrentType))
+ if (!oB.CurrentType.CanCastTo(iB.CurrentType))
{
// Cannot
return false;
@@ -798,7 +737,7 @@ namespace FlaxEditor.Surface.Elements
{
if (!oB.CanUseType(iB.CurrentType))
{
- if (!CanCast(oB.CurrentType, iB.CurrentType))
+ if (!oB.CurrentType.CanCastTo(iB.CurrentType))
{
// Cannot
return false;
@@ -871,7 +810,7 @@ namespace FlaxEditor.Surface.Elements
bool useCaster = false;
if (!iB.CanUseType(oB.CurrentType))
{
- if (CanCast(oB.CurrentType, iB.CurrentType))
+ if (oB.CurrentType.CanCastTo(iB.CurrentType))
useCaster = true;
else
return;
diff --git a/Source/Editor/Surface/Elements/OutputBox.cs b/Source/Editor/Surface/Elements/OutputBox.cs
index cf78b559c..8ef64ec77 100644
--- a/Source/Editor/Surface/Elements/OutputBox.cs
+++ b/Source/Editor/Surface/Elements/OutputBox.cs
@@ -35,7 +35,7 @@ namespace FlaxEditor.Surface.Elements
{
// Calculate control points
CalculateBezierControlPoints(start, end, out var control1, out var control2);
-
+
// Draw line
Render2D.DrawBezier(start, control1, control2, end, color, thickness);
@@ -54,16 +54,16 @@ namespace FlaxEditor.Surface.Elements
const float maxControlLength = 150f;
var dst = (end - start).Length;
var yDst = Mathf.Abs(start.Y - end.Y);
-
+
// Calculate control points
var minControlDst = dst * 0.5f;
var maxControlDst = Mathf.Max(Mathf.Min(maxControlLength, dst), minControlLength);
var controlDst = Mathf.Lerp(minControlDst, maxControlDst, Mathf.Clamp(yDst / minControlLength, 0f, 1f));
-
+
control1 = new Float2(start.X + controlDst, start.Y);
control2 = new Float2(end.X - controlDst, end.Y);
}
-
+
///
/// Checks if a point intersects a connection
///
@@ -86,11 +86,13 @@ namespace FlaxEditor.Surface.Elements
public static bool IntersectsConnection(ref Float2 start, ref Float2 end, ref Float2 point, float distance)
{
// Pretty much a point in rectangle check
- if ((point.X - start.X) * (end.X - point.X) < 0) return false;
+ if ((point.X - start.X) * (end.X - point.X) < 0)
+ return false;
float offset = Mathf.Sign(end.Y - start.Y) * distance;
- if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0) return false;
-
+ if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0)
+ return false;
+
float squaredDistance = distance;
CalculateBezierControlPoints(start, end, out var control1, out var control2);
diff --git a/Source/Editor/Surface/NodeArchetype.cs b/Source/Editor/Surface/NodeArchetype.cs
index 6dc923ce2..fe54268e2 100644
--- a/Source/Editor/Surface/NodeArchetype.cs
+++ b/Source/Editor/Surface/NodeArchetype.cs
@@ -89,6 +89,11 @@ namespace FlaxEditor.Surface
/// The created node object.
public delegate SurfaceNode CreateCustomNodeFunc(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch);
+ ///
+ /// Checks if the given type is compatible with the given node archetype. Used for custom nodes
+ ///
+ public delegate bool IsCompatible(NodeArchetype nodeArch, ScriptType portType, ConnectionsHint hint);
+
///
/// Unique node type ID within a single group.
///
@@ -99,6 +104,16 @@ namespace FlaxEditor.Surface
///
public CreateCustomNodeFunc Create;
+ ///
+ /// Function for asynchronously loaded nodes to check if input ports are compatible, for filtering.
+ ///
+ public IsCompatible IsInputCompatible;
+
+ ///
+ /// Function for asynchronously loaded nodes to check if output ports are compatible, for filtering.
+ ///
+ public IsCompatible IsOutputCompatible;
+
///
/// Default initial size of the node.
///
@@ -184,6 +199,8 @@ namespace FlaxEditor.Surface
{
TypeID = TypeID,
Create = Create,
+ IsInputCompatible = IsInputCompatible,
+ IsOutputCompatible = IsOutputCompatible,
Size = Size,
Flags = Flags,
Title = Title,
diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs
index 08b9c6d63..5f4c3ef07 100644
--- a/Source/Editor/Surface/SurfaceUtils.cs
+++ b/Source/Editor/Surface/SurfaceUtils.cs
@@ -406,8 +406,8 @@ namespace FlaxEditor.Surface
internal static bool IsValidVisualScriptType(ScriptType scriptType)
{
- if (!scriptType.IsPublic ||
- scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
+ if (!scriptType.IsPublic ||
+ scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
return false;
if (scriptType.IsGenericType)
diff --git a/Source/Editor/Surface/VisjectSurface.Connecting.cs b/Source/Editor/Surface/VisjectSurface.Connecting.cs
index 2fbff7cb8..dd34f9c7c 100644
--- a/Source/Editor/Surface/VisjectSurface.Connecting.cs
+++ b/Source/Editor/Surface/VisjectSurface.Connecting.cs
@@ -136,6 +136,88 @@ namespace FlaxEditor.Surface
return result;
}
+ ///
+ /// Checks if a type is compatible with another type and can be casted by using a connection hint
+ ///
+ /// Source type
+ /// Type to check compatibility with
+ /// Hint to check if casting is possible
+ /// True if the source type is compatible with the target type
+ public static bool IsTypeCompatible(ScriptType from, ScriptType to, ConnectionsHint hint)
+ {
+ if (from == ScriptType.Null && hint != ConnectionsHint.None)
+ {
+ if ((hint & ConnectionsHint.Anything) == ConnectionsHint.Anything)
+ return true;
+ if ((hint & ConnectionsHint.Value) == ConnectionsHint.Value && to.Type != typeof(void))
+ return true;
+ if ((hint & ConnectionsHint.Enum) == ConnectionsHint.Enum && to.IsEnum)
+ return true;
+ if ((hint & ConnectionsHint.Array) == ConnectionsHint.Array && to.IsArray)
+ return true;
+ if ((hint & ConnectionsHint.Dictionary) == ConnectionsHint.Dictionary && to.IsDictionary)
+ return true;
+ if ((hint & ConnectionsHint.Vector) == ConnectionsHint.Vector)
+ {
+ var t = to.Type;
+ if (t == typeof(Vector2) ||
+ t == typeof(Vector3) ||
+ t == typeof(Vector4) ||
+ t == typeof(Float2) ||
+ t == typeof(Float3) ||
+ t == typeof(Float4) ||
+ t == typeof(Double2) ||
+ t == typeof(Double3) ||
+ t == typeof(Double4) ||
+ t == typeof(Int2) ||
+ t == typeof(Int3) ||
+ t == typeof(Int4) ||
+ t == typeof(Color))
+ {
+ return true;
+ }
+ }
+ if ((hint & ConnectionsHint.Scalar) == ConnectionsHint.Scalar)
+ {
+ var t = to.Type;
+ if (t == typeof(bool) ||
+ t == typeof(char) ||
+ t == typeof(byte) ||
+ t == typeof(short) ||
+ t == typeof(ushort) ||
+ t == typeof(int) ||
+ t == typeof(uint) ||
+ t == typeof(long) ||
+ t == typeof(ulong) ||
+ t == typeof(float) ||
+ t == typeof(double))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Checks if a type is compatible with another type and can be casted by using a connection hint
+ ///
+ /// Source type
+ /// Target type
+ /// Connection hint
+ /// True if any method of casting or compatibility check succeeds
+ public static bool FullCastCheck(ScriptType from, ScriptType to, ConnectionsHint hint)
+ {
+ // Yes, from and to are switched on purpose
+ if (CanUseDirectCastStatic(to, from, false))
+ return true;
+ if (IsTypeCompatible(from, to, hint))
+ return true;
+ // Same here
+ return to.CanCastTo(from);
+ }
+
///
/// Checks if can use direct conversion from one type to another.
///