diff --git a/Content/Editor/Scripting/ScriptTemplate.cs b/Content/Editor/Scripting/ScriptTemplate.cs
index 2a150306d..663acf05f 100644
--- a/Content/Editor/Scripting/ScriptTemplate.cs
+++ b/Content/Editor/Scripting/ScriptTemplate.cs
@@ -2,35 +2,35 @@
using System.Collections.Generic;
using FlaxEngine;
-namespace %namespace%
+namespace %namespace%;
+
+///
+/// %class% Script.
+///
+public class %class% : Script
{
- ///
- /// %class% Script.
- ///
- public class %class% : Script
+ ///
+ public override void OnStart()
{
- ///
- public override void OnStart()
- {
- // Here you can add code that needs to be called when script is created, just before the first game update
- }
-
- ///
- public override void OnEnable()
- {
- // Here you can add code that needs to be called when script is enabled (eg. register for events)
- }
+ // Here you can add code that needs to be called when script is created, just before the first game update
+ }
+
+ ///
+ public override void OnEnable()
+ {
+ // Here you can add code that needs to be called when script is enabled (eg. register for events)
+ }
- ///
- public override void OnDisable()
- {
- // Here you can add code that needs to be called when script is disabled (eg. unregister from events)
- }
+ ///
+ public override void OnDisable()
+ {
+ // Here you can add code that needs to be called when script is disabled (eg. unregister from events)
+ }
- ///
- public override void OnUpdate()
- {
- // Here you can add code that needs to be called every frame
- }
+ ///
+ public override void OnUpdate()
+ {
+ // Here you can add code that needs to be called every frame
}
}
+
diff --git a/Source/Editor/Content/GUI/ContentView.DragDrop.cs b/Source/Editor/Content/GUI/ContentView.DragDrop.cs
index 348e2b443..ffce81e2d 100644
--- a/Source/Editor/Content/GUI/ContentView.DragDrop.cs
+++ b/Source/Editor/Content/GUI/ContentView.DragDrop.cs
@@ -68,7 +68,7 @@ namespace FlaxEditor.Content.GUI
_validDragOver = true;
result = DragDropEffect.Copy;
}
- else if (_dragActors.HasValidDrag)
+ else if (_dragActors != null && _dragActors.HasValidDrag)
{
_validDragOver = true;
result = DragDropEffect.Move;
@@ -94,7 +94,7 @@ namespace FlaxEditor.Content.GUI
result = DragDropEffect.Copy;
}
// Check if drop actor(s)
- else if (_dragActors.HasValidDrag)
+ else if (_dragActors != null && _dragActors.HasValidDrag)
{
// Import actors
var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder;
diff --git a/Source/Editor/Content/Import/ImportFilesDialog.cs b/Source/Editor/Content/Import/ImportFilesDialog.cs
index ab2c7be15..967583cf6 100644
--- a/Source/Editor/Content/Import/ImportFilesDialog.cs
+++ b/Source/Editor/Content/Import/ImportFilesDialog.cs
@@ -139,7 +139,7 @@ namespace FlaxEditor.Content.Import
var menu = new ContextMenu();
menu.AddButton("Rename", OnRenameClicked);
menu.AddButton("Don't import", OnDontImportClicked);
- menu.AddButton("Show in Explorer", OnShowInExplorerClicked);
+ menu.AddButton(Utilities.Constants.ShowInExplorer, OnShowInExplorerClicked);
menu.Tag = node;
menu.Show(node, location);
}
diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs
index 94db3a5b9..6bbdc0a51 100644
--- a/Source/Editor/Content/Items/ContentItem.cs
+++ b/Source/Editor/Content/Items/ContentItem.cs
@@ -486,7 +486,7 @@ namespace FlaxEditor.Content
else
Render2D.FillRectangle(rectangle, Color.Black);
}
-
+
///
/// Draws the item thumbnail.
///
@@ -684,7 +684,7 @@ namespace FlaxEditor.Content
var thumbnailSize = size.X;
thumbnailRect = new Rectangle(0, 0, thumbnailSize, thumbnailSize);
nameAlignment = TextAlignment.Center;
-
+
if (this is ContentFolder)
{
// Small shadow
@@ -692,7 +692,7 @@ namespace FlaxEditor.Content
var color = Color.Black.AlphaMultiplied(0.2f);
Render2D.FillRectangle(shadowRect, color);
Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f));
-
+
if (isSelected)
Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground);
else if (IsMouseOver)
@@ -706,14 +706,14 @@ namespace FlaxEditor.Content
var shadowRect = new Rectangle(2, 2, clientRect.Width + 1, clientRect.Height + 1);
var color = Color.Black.AlphaMultiplied(0.2f);
Render2D.FillRectangle(shadowRect, color);
-
+
Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f));
Render2D.FillRectangle(TextRectangle, style.LightBackground);
-
+
var accentHeight = 2 * view.ViewScale;
var barRect = new Rectangle(0, thumbnailRect.Height - accentHeight, clientRect.Width, accentHeight);
Render2D.FillRectangle(barRect, Color.DimGray);
-
+
DrawThumbnail(ref thumbnailRect, false);
if (isSelected)
{
@@ -733,18 +733,18 @@ namespace FlaxEditor.Content
var thumbnailSize = size.Y - 2 * DefaultMarginSize;
thumbnailRect = new Rectangle(DefaultMarginSize, DefaultMarginSize, thumbnailSize, thumbnailSize);
nameAlignment = TextAlignment.Near;
-
+
if (isSelected)
Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground);
else if (IsMouseOver)
Render2D.FillRectangle(clientRect, style.BackgroundHighlighted);
-
+
DrawThumbnail(ref thumbnailRect);
break;
}
default: throw new ArgumentOutOfRangeException();
}
-
+
// Draw short name
Render2D.PushClip(ref textRect);
Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, 0.95f);
diff --git a/Source/Editor/Content/Tree/MainContentTreeNode.cs b/Source/Editor/Content/Tree/MainContentTreeNode.cs
index 43b36986d..def873622 100644
--- a/Source/Editor/Content/Tree/MainContentTreeNode.cs
+++ b/Source/Editor/Content/Tree/MainContentTreeNode.cs
@@ -29,7 +29,7 @@ namespace FlaxEditor.Content
//_watcher.Changed += OnEvent;
_watcher.Created += OnEvent;
_watcher.Deleted += OnEvent;
- //_watcher.Renamed += OnEvent;
+ _watcher.Renamed += OnEvent;
}
private void OnEvent(object sender, FileSystemEventArgs e)
diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
index b66a96a9b..0008228f7 100644
--- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
@@ -17,7 +17,9 @@
#include "Editor/ProjectInfo.h"
#include "Editor/Cooker/GameCooker.h"
#include "Editor/Utilities/EditorUtilities.h"
-#include
+
+#include "pugixml/pugixml_extra.hpp"
+
using namespace pugi;
IMPLEMENT_SETTINGS_GETTER(MacPlatformSettings, MacPlatform);
@@ -170,17 +172,17 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
const String plistPath = data.DataOutputPath / TEXT("Info.plist");
{
xml_document doc;
- xml_node plist = doc.child_or_append(PUGIXML_TEXT("plist"));
+ xml_node_extra plist = xml_node_extra(doc).child_or_append(PUGIXML_TEXT("plist"));
plist.append_attribute(PUGIXML_TEXT("version")).set_value(PUGIXML_TEXT("1.0"));
- xml_node dict = plist.child_or_append(PUGIXML_TEXT("dict"));
+ xml_node_extra dict = plist.child_or_append(PUGIXML_TEXT("dict"));
#define ADD_ENTRY(key, value) \
- dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT(key)); \
- dict.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT(value))
+ dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT(key)); \
+ dict.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT(value))
#define ADD_ENTRY_STR(key, value) \
- dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT(key)); \
+ dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT(key)); \
{ std::u16string valueStr(value.GetText()); \
- dict.append_child(PUGIXML_TEXT("string")).set_child_value(pugi::string_t(valueStr.begin(), valueStr.end()).c_str()); }
+ dict.append_child_with_value(PUGIXML_TEXT("string"), pugi::string_t(valueStr.begin(), valueStr.end()).c_str()); }
ADD_ENTRY("CFBundleDevelopmentRegion", "English");
ADD_ENTRY("CFBundlePackageType", "APPL");
@@ -194,22 +196,22 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
ADD_ENTRY_STR("CFBundleVersion", projectVersion);
ADD_ENTRY_STR("NSHumanReadableCopyright", gameSettings->CopyrightNotice);
- dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("CFBundleSupportedPlatforms"));
- xml_node CFBundleSupportedPlatforms = dict.append_child(PUGIXML_TEXT("array"));
- CFBundleSupportedPlatforms.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("MacOSX"));
+ dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("CFBundleSupportedPlatforms"));
+ xml_node_extra CFBundleSupportedPlatforms = dict.append_child(PUGIXML_TEXT("array"));
+ CFBundleSupportedPlatforms.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT("MacOSX"));
- dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("LSMinimumSystemVersionByArchitecture"));
- xml_node LSMinimumSystemVersionByArchitecture = dict.append_child(PUGIXML_TEXT("dict"));
+ dict.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("LSMinimumSystemVersionByArchitecture"));
+ xml_node_extra LSMinimumSystemVersionByArchitecture = dict.append_child(PUGIXML_TEXT("dict"));
switch (_arch)
{
case ArchitectureType::x64:
- LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("x86_64"));
+ LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("x86_64"));
break;
case ArchitectureType::ARM64:
- LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("arm64"));
+ LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("key"), PUGIXML_TEXT("arm64"));
break;
}
- LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("10.15"));
+ LSMinimumSystemVersionByArchitecture.append_child_with_value(PUGIXML_TEXT("string"), PUGIXML_TEXT("10.15"));
#undef ADD_ENTRY
#undef ADD_ENTRY_STR
diff --git a/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
new file mode 100644
index 000000000..a6c4e6623
--- /dev/null
+++ b/Source/Editor/CustomEditors/Dedicated/MissingScriptEditor.cs
@@ -0,0 +1,26 @@
+using FlaxEditor.CustomEditors.Editors;
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.CustomEditors.Dedicated;
+
+///
+/// The missing script editor.
+///
+[CustomEditor(typeof(MissingScript)), DefaultEditor]
+public class MissingScriptEditor : GenericEditor
+{
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ if (layout.ContainerControl is not DropPanel dropPanel)
+ {
+ base.Initialize(layout);
+ return;
+ }
+
+ dropPanel.HeaderTextColor = Color.OrangeRed;
+
+ base.Initialize(layout);
+ }
+}
diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
index 03d58ef33..7b9b65c5c 100644
--- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
@@ -348,7 +348,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (!CanEditTangent())
return;
-
+
var index = _lastPointSelected.Index;
var currentTangentInPosition = _selectedSpline.GetSplineLocalTangent(index, true).Translation;
var currentTangentOutPosition = _selectedSpline.GetSplineLocalTangent(index, false).Translation;
diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
index 4aa02ac78..4c153e759 100644
--- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
@@ -88,7 +88,7 @@ namespace FlaxEditor.CustomEditors.Editors
LinkValues = Editor.Instance.Windows.PropertiesWin.ScaleLinked;
// Add button with the link icon
-
+
_linkButton = new Button
{
BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Link32),
diff --git a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
index f2398bbef..0369d679d 100644
--- a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
@@ -160,7 +160,6 @@ namespace FlaxEditor.CustomEditors.Editors
var option = _options[comboBox.SelectedIndex];
if (option.Type != null)
value = option.Creator(option.Type);
-
}
SetValue(value);
RebuildLayoutOnRefresh();
diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs
index 911397785..dbd5d124c 100644
--- a/Source/Editor/CustomEditors/Editors/TagEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs
@@ -634,7 +634,7 @@ namespace FlaxEditor.CustomEditors.Editors
var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText);
if (textSize.Y > button.Width)
button.Width = textSize.Y + 2;
-
+
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
button.Clicked += ShowPicker;
}
diff --git a/Source/Editor/CustomEditors/GUI/PropertiesList.cs b/Source/Editor/CustomEditors/GUI/PropertiesList.cs
index 2b2f0a3d3..93aacbd34 100644
--- a/Source/Editor/CustomEditors/GUI/PropertiesList.cs
+++ b/Source/Editor/CustomEditors/GUI/PropertiesList.cs
@@ -175,7 +175,7 @@ namespace FlaxEditor.CustomEditors.GUI
{
// Clear flag
_mouseOverSplitter = false;
-
+
if (_cursorChanged)
{
Cursor = CursorType.Default;
diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
index 7ecd197eb..628af18e1 100644
--- a/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
+++ b/Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
@@ -154,6 +154,7 @@ namespace FlaxEditor.GUI.ContextMenu
}
// Unlock and perform controls update
+ Location = Float2.Zero;
UnlockChildrenRecursive();
PerformLayout();
@@ -162,7 +163,6 @@ namespace FlaxEditor.GUI.ContextMenu
var dpiSize = Size * dpiScale;
var locationWS = parent.PointToWindow(location);
var locationSS = parentWin.PointToScreen(locationWS);
- Location = Float2.Zero;
var monitorBounds = Platform.GetMonitorBounds(locationSS);
var rightBottomLocationSS = locationSS + dpiSize;
bool isUp = false, isLeft = false;
diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs
index e6391d9b2..eec55c788 100644
--- a/Source/Editor/Modules/ContentDatabaseModule.cs
+++ b/Source/Editor/Modules/ContentDatabaseModule.cs
@@ -919,6 +919,11 @@ namespace FlaxEditor.Modules
// Check if node already has that element (skip during init when we want to walk project dir very fast)
if (_isDuringFastSetup || !parent.Folder.ContainsChild(path))
{
+#if PLATFORM_MAC
+ if (path.EndsWith(".DS_Store", StringComparison.Ordinal))
+ continue;
+#endif
+
// Create file item
ContentItem item;
if (path.EndsWith(".cs"))
@@ -960,6 +965,11 @@ namespace FlaxEditor.Modules
// Check if node already has that element (skip during init when we want to walk project dir very fast)
if (_isDuringFastSetup || !parent.Folder.ContainsChild(path))
{
+#if PLATFORM_MAC
+ if (path.EndsWith(".DS_Store", StringComparison.Ordinal))
+ continue;
+#endif
+
// Create file item
ContentItem item = null;
if (FlaxEngine.Content.GetAssetInfo(path, out var assetInfo))
@@ -1186,6 +1196,7 @@ namespace FlaxEditor.Modules
{
case WatcherChangeTypes.Created:
case WatcherChangeTypes.Deleted:
+ case WatcherChangeTypes.Renamed:
{
lock (_dirtyNodes)
{
diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs
index 299865dea..c882efb87 100644
--- a/Source/Editor/Modules/UIModule.cs
+++ b/Source/Editor/Modules/UIModule.cs
@@ -553,6 +553,13 @@ namespace FlaxEditor.Modules
cm.AddSeparator();
_menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes);
_menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search);
+ cm.AddSeparator();
+ cm.AddButton("Game Settings", () =>
+ {
+ var item = Editor.ContentDatabase.Find(GameSettings.GameSettingsAssetPath);
+ if(item != null)
+ Editor.ContentEditing.Open(item);
+ });
// Scene
MenuScene = MainMenu.AddButton("Scene");
diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs
index 63822a7ca..33124bab0 100644
--- a/Source/Editor/Options/GeneralOptions.cs
+++ b/Source/Editor/Options/GeneralOptions.cs
@@ -106,7 +106,7 @@ namespace FlaxEditor.Options
[DefaultValue(60.0f), Limit(0, 666)]
[EditorDisplay("General", "Editor FPS"), EditorOrder(110), Tooltip("Limit for the editor draw/update frames per second rate (FPS). Use higher values if you need more responsive interface or lower values to use less device power. Value 0 disables any limits.")]
public float EditorFPS { get; set; } = 60.0f;
-
+
///
/// Gets or sets The FPS of the editor when the editor window is not focused. Usually set to lower then the editor FPS.
///
@@ -203,7 +203,7 @@ namespace FlaxEditor.Options
[DefaultValue(5), Limit(1)]
[EditorDisplay("Auto Save", "Auto Save Frequency"), EditorOrder(801), Tooltip("The interval between auto saves (in minutes)")]
public int AutoSaveFrequency { get; set; } = 5;
-
+
///
/// Gets or sets a value indicating the time before the auto save that the popup shows (in seconds).
///
diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs
index 5fd8c31c7..95a273f19 100644
--- a/Source/Editor/Options/InterfaceOptions.cs
+++ b/Source/Editor/Options/InterfaceOptions.cs
@@ -166,7 +166,19 @@ namespace FlaxEditor.Options
/// Gets or sets the output log text font.
///
[EditorDisplay("Output Log", "Text Font"), EditorOrder(320), Tooltip("The output log text font.")]
- public FontReference OutputLogTextFont { get; set; } = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10);
+ public FontReference OutputLogTextFont
+ {
+ get => _outputLogFont;
+ set
+ {
+ if (value == null)
+ _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10);
+ else if (!value.Font)
+ _outputLogFont.Font = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont);
+ else
+ _outputLogFont = value;
+ }
+ }
///
/// Gets or sets the output log text color.
@@ -225,29 +237,82 @@ namespace FlaxEditor.Options
public int NumberOfGameClientsToLaunch = 1;
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont);
+ private FontReference _titleFont = new FontReference(DefaultFont, 18);
+ private FontReference _largeFont = new FontReference(DefaultFont, 14);
+ private FontReference _mediumFont = new FontReference(DefaultFont, 9);
+ private FontReference _smallFont = new FontReference(DefaultFont, 9);
+ private FontReference _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10);
///
/// Gets or sets the title font for editor UI.
///
[EditorDisplay("Fonts"), EditorOrder(600), Tooltip("The title font for editor UI.")]
- public FontReference TitleFont { get; set; } = new FontReference(DefaultFont, 18);
+ public FontReference TitleFont
+ {
+ get => _titleFont;
+ set
+ {
+ if (value == null)
+ _titleFont = new FontReference(DefaultFont, 18);
+ else if (!value.Font)
+ _titleFont.Font = DefaultFont;
+ else
+ _titleFont = value;
+ }
+ }
///
/// Gets or sets the large font for editor UI.
///
[EditorDisplay("Fonts"), EditorOrder(610), Tooltip("The large font for editor UI.")]
- public FontReference LargeFont { get; set; } = new FontReference(DefaultFont, 14);
+ public FontReference LargeFont
+ {
+ get => _largeFont;
+ set
+ {
+ if (value == null)
+ _largeFont = new FontReference(DefaultFont, 14);
+ else if (!value.Font)
+ _largeFont.Font = DefaultFont;
+ else
+ _largeFont = value;
+ }
+ }
///
/// Gets or sets the medium font for editor UI.
///
[EditorDisplay("Fonts"), EditorOrder(620), Tooltip("The medium font for editor UI.")]
- public FontReference MediumFont { get; set; } = new FontReference(DefaultFont, 9);
+ public FontReference MediumFont
+ {
+ get => _mediumFont;
+ set
+ {
+ if (value == null)
+ _mediumFont = new FontReference(DefaultFont, 9);
+ else if (!value.Font)
+ _mediumFont.Font = DefaultFont;
+ else
+ _mediumFont = value;
+ }
+ }
///
/// Gets or sets the small font for editor UI.
///
[EditorDisplay("Fonts"), EditorOrder(630), Tooltip("The small font for editor UI.")]
- public FontReference SmallFont { get; set; } = new FontReference(DefaultFont, 9);
+ public FontReference SmallFont
+ {
+ get => _smallFont;
+ set
+ {
+ if (value == null)
+ _smallFont = new FontReference(DefaultFont, 9);
+ else if (!value.Font)
+ _smallFont.Font = DefaultFont;
+ else
+ _smallFont = value;
+ }
+ }
}
}
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 54b561e4c..de02e9336 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 0abae2d0b..16f20809c 100644
--- a/Source/Editor/Surface/Archetypes/Packing.cs
+++ b/Source/Editor/Surface/Archetypes/Packing.cs
@@ -216,6 +216,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 +254,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 +409,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 +521,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 e7b71ddb0..8369d9c4d 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;
@@ -41,6 +40,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;
@@ -128,7 +129,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)
{
@@ -140,17 +141,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)
{
@@ -291,6 +316,10 @@ namespace FlaxEditor.Surface.ContextMenu
OnSearchFilterChanged();
}
}
+ else if (_contextSensitiveSearchEnabled)
+ {
+ group.EvaluateVisibilityWithBox(_selectedBox);
+ }
Profiler.EndEvent();
}
@@ -324,6 +353,8 @@ namespace FlaxEditor.Surface.ContextMenu
Parent = group
};
}
+ if (_contextSensitiveSearchEnabled)
+ group.EvaluateVisibilityWithBox(_selectedBox);
group.SortChildren();
if (ShowExpanded)
group.Open(false);
@@ -423,8 +454,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();
@@ -435,7 +484,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();
@@ -448,9 +497,6 @@ namespace FlaxEditor.Surface.ContextMenu
PerformLayout();
if (SelectedItem != null)
_panel1.ScrollViewTo(SelectedItem);
- _searchBox.Focus();
- Profiler.EndEvent();
-
Profiler.EndEvent();
}
@@ -508,7 +554,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();
@@ -764,5 +814,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..e52e10482 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,42 @@ namespace FlaxEditor.Surface.ContextMenu
/// Updates the filter.
///
/// The filter text.
- public void UpdateFilter(string filterText)
+ /// The optionally selected box to show hints for it.
+ 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();
+ }
+
+ internal void EvaluateVisibilityWithBox(Box selectedBox)
+ {
+ if (selectedBox == null)
{
for (int i = 0; i < _children.Count; i++)
{
@@ -109,14 +128,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..7fd583202 100644
--- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs
+++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Utilities;
using FlaxEngine;
@@ -56,7 +57,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 +78,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 +93,93 @@ 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)
+ /// The optionally selected box to show hints for it.
+ /// True if item's group header got a filter match and item should stay visible.
+ 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 +243,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 da081446d..53bba2d68 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();
@@ -236,58 +236,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;
@@ -301,7 +251,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;
@@ -690,17 +640,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)
{
@@ -759,7 +698,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;
@@ -770,7 +709,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;
@@ -843,7 +782,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 b6efa1aa3..497e2001a 100644
--- a/Source/Editor/Surface/Elements/OutputBox.cs
+++ b/Source/Editor/Surface/Elements/OutputBox.cs
@@ -42,7 +42,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);
@@ -61,16 +61,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
///
@@ -93,11 +93,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.
///
diff --git a/Source/Editor/Utilities/Constants.cs b/Source/Editor/Utilities/Constants.cs
index 176fed5f7..fb9f82cf5 100644
--- a/Source/Editor/Utilities/Constants.cs
+++ b/Source/Editor/Utilities/Constants.cs
@@ -14,5 +14,11 @@ namespace FlaxEditor.Utilities
public const string FacebookUrl = "https://facebook.com/FlaxEngine";
public const string YoutubeUrl = "https://youtube.com/c/FlaxEngine";
public const string TwitterUrl = "https://twitter.com/FlaxEngine";
+
+#if PLATFORM_MAC
+ public const string ShowInExplorer = "Show in Finder";
+#else
+ public const string ShowInExplorer = "Show in explorer";
+#endif
}
}
diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
index d360e5570..d8790172b 100644
--- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
+++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
@@ -321,6 +321,7 @@ namespace FlaxEditor.Windows.Assets
var tree = group.Tree();
tree.TreeControl.RightClick += OnTreeNodeRightClick;
+ tree.TreeControl.SelectedChanged += OnTreeSelectedChanged;
for (int i = 0; i < nodes.Length; i++)
{
if (nodes[i].ParentIndex == -1)
@@ -367,6 +368,12 @@ namespace FlaxEditor.Windows.Assets
menu.Show(node, location);
}
+ private void OnTreeSelectedChanged(List before, List after)
+ {
+ if (after.Count != 0)
+ ((SkeletonPropertiesProxy)Values[0]).Window._preview.ShowDebugDraw = true;
+ }
+
private void OnTreeNodeCopyName(ContextMenuButton b)
{
Clipboard.Text = (string)b.Tag;
diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
index 6923d634d..1dd26ecf5 100644
--- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
@@ -58,7 +58,7 @@ namespace FlaxEditor.Windows
if (item is ContentFolder contentFolder && contentFolder.Node is ProjectTreeNode)
{
- cm.AddButton("Show in explorer", () => FileSystem.ShowFileExplorer(CurrentViewFolder.Path));
+ cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(CurrentViewFolder.Path));
}
else if (isValidElement)
{
@@ -72,7 +72,7 @@ namespace FlaxEditor.Windows
Open(e);
});
- cm.AddButton("Show in explorer", () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(item.Path)));
+ cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(item.Path)));
if (item.HasDefaultThumbnail == false)
{
@@ -135,7 +135,7 @@ namespace FlaxEditor.Windows
}
else
{
- cm.AddButton("Show in explorer", () => FileSystem.ShowFileExplorer(CurrentViewFolder.Path));
+ cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(CurrentViewFolder.Path));
b = cm.AddButton("Paste", _view.Paste);
b.Enabled = _view.CanPaste();
diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs
index af2eee09f..b1183753e 100644
--- a/Source/Editor/Windows/ContentWindow.cs
+++ b/Source/Editor/Windows/ContentWindow.cs
@@ -988,7 +988,7 @@ namespace FlaxEditor.Windows
}
_view.ShowItems(items, _sortType, false, true);
}
- else
+ else if (target != null)
{
// Show folder contents
var items = target.Folder.Children;
diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index bbdf763fb..fdedbb3c2 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -463,6 +463,18 @@ namespace FlaxEditor.Windows
Cursor = CursorType.Default;
}
+ ///
+ public override void OnMouseLeave()
+ {
+ base.OnMouseLeave();
+
+ // Remove focus from game window when mouse moves out and the cursor is hidden during game
+ if ((IsFocused || ContainsFocus) && Parent != null && Editor.IsPlayMode && !Screen.CursorVisible)
+ {
+ Parent.Focus();
+ }
+ }
+
///
public override void OnShowContextMenu(ContextMenu menu)
{
diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs
index 1d188a32c..3665b7073 100644
--- a/Source/Editor/Windows/OutputLogWindow.cs
+++ b/Source/Editor/Windows/OutputLogWindow.cs
@@ -195,7 +195,7 @@ namespace FlaxEditor.Windows
_contextMenu.AddButton("Clear log", Clear);
_contextMenu.AddButton("Copy selection", _output.Copy);
_contextMenu.AddButton("Select All", _output.SelectAll);
- _contextMenu.AddButton("Show in explorer", () => FileSystem.ShowFileExplorer(Path.Combine(Globals.ProjectFolder, "Logs")));
+ _contextMenu.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(Path.Combine(Globals.ProjectFolder, "Logs")));
_contextMenu.AddButton("Scroll to bottom", () => { _vScroll.TargetValue = _vScroll.Maximum; }).Icon = Editor.Icons.ArrowDown12;
// Setup editor options
diff --git a/Source/Editor/Windows/Profiler/Assets.cs b/Source/Editor/Windows/Profiler/Assets.cs
index e2a65d496..3ccf6e2eb 100644
--- a/Source/Editor/Windows/Profiler/Assets.cs
+++ b/Source/Editor/Windows/Profiler/Assets.cs
@@ -274,7 +274,7 @@ namespace FlaxEditor.Windows.Profiler
ContextMenuButton b;
b = cm.AddButton("Open", () => Editor.Instance.ContentEditing.Open(assetItem));
cm.AddButton("Show in content window", () => Editor.Instance.Windows.ContentWin.Select(assetItem));
- cm.AddButton("Show in explorer", () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(assetItem.Path)));
+ cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(assetItem.Path)));
cm.AddButton("Select actors using this asset", () => Editor.Instance.SceneEditing.SelectActorsUsingAsset(assetItem.ID));
cm.AddButton("Show asset references graph", () => Editor.Instance.Windows.Open(new AssetReferencesGraphWindow(Editor.Instance, assetItem)));
cm.AddButton("Copy name", () => Clipboard.Text = assetItem.NamePath);
diff --git a/Source/Editor/Windows/Search/SearchItem.cs b/Source/Editor/Windows/Search/SearchItem.cs
index e8bdc4e56..506d30d0d 100644
--- a/Source/Editor/Windows/Search/SearchItem.cs
+++ b/Source/Editor/Windows/Search/SearchItem.cs
@@ -157,7 +157,7 @@ namespace FlaxEditor.Windows.Search
var cm = new FlaxEditor.GUI.ContextMenu.ContextMenu { Tag = assetItem };
b = cm.AddButton("Open", () => Editor.Instance.ContentFinding.Open(Item));
cm.AddSeparator();
- cm.AddButton("Show in explorer", () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(assetItem.Path)));
+ cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(assetItem.Path)));
cm.AddButton("Show in Content window", () => Editor.Instance.Windows.ContentWin.Select(assetItem, true));
b.Enabled = proxy != null && proxy.CanReimport(assetItem);
if (assetItem is BinaryAssetItem binaryAsset)
diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h
index 2d2ba4b3f..be188b2b9 100644
--- a/Source/Engine/Content/Asset.h
+++ b/Source/Engine/Content/Asset.h
@@ -227,7 +227,7 @@ protected:
bool onLoad(LoadAssetTask* task);
void onLoaded();
- void onLoaded_MainThread();
+ virtual void onLoaded_MainThread();
virtual void onUnload_MainThread();
#if USE_EDITOR
virtual void onRename(const StringView& newPath) = 0;
diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp
index 1fbc2abed..9977a28e1 100644
--- a/Source/Engine/Content/JsonAsset.cpp
+++ b/Source/Engine/Content/JsonAsset.cpp
@@ -12,6 +12,7 @@
#include "FlaxEngine.Gen.h"
#include "Cache/AssetsCache.h"
#include "Engine/Core/Log.h"
+#include "Engine/Core/Config/Settings.h"
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Content/Factories/JsonAssetFactory.h"
@@ -126,8 +127,7 @@ void FindIds(ISerializable::DeserializeStream& node, Array& output)
}
else if (node.IsString())
{
- const auto length = node.GetStringLength();
- if (length == 32)
+ if (node.GetStringLength() == 32)
{
// Try parse as Guid in format `N` (32 hex chars)
Guid id;
@@ -362,10 +362,25 @@ void JsonAsset::unload(bool isReloading)
#endif
Scripting::ScriptsUnload.Unbind(this);
DeleteInstance();
+ _isAfterReload |= isReloading;
JsonAssetBase::unload(isReloading);
}
+void JsonAsset::onLoaded_MainThread()
+{
+ JsonAssetBase::onLoaded_MainThread();
+
+ // Special case for Settings assets to flush them after edited and saved in Editor
+ const StringAsANSI<> dataTypeNameAnsi(DataTypeName.Get(), DataTypeName.Length());
+ const auto typeHandle = Scripting::FindScriptingType(StringAnsiView(dataTypeNameAnsi.Get(), DataTypeName.Length()));
+ if (Instance && typeHandle && typeHandle.IsSubclassOf(SettingsBase::TypeInitializer) && _isAfterReload)
+ {
+ _isAfterReload = false;
+ ((SettingsBase*)Instance)->Apply();
+ }
+}
+
bool JsonAsset::CreateInstance()
{
ScopeLock lock(Locker);
diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h
index c53649d33..a8e89cec0 100644
--- a/Source/Engine/Content/JsonAsset.h
+++ b/Source/Engine/Content/JsonAsset.h
@@ -118,6 +118,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API JsonAsset : public JsonAssetBase
DECLARE_ASSET_HEADER(JsonAsset);
private:
ScriptingType::Dtor _dtor;
+ bool _isAfterReload = false;
public:
///
@@ -149,6 +150,7 @@ protected:
// [JsonAssetBase]
LoadResult loadAsset() override;
void unload(bool isReloading) override;
+ void onLoaded_MainThread() override;
private:
bool CreateInstance();
diff --git a/Source/Engine/Core/Collections/ArrayExtensions.h b/Source/Engine/Core/Collections/ArrayExtensions.h
index 788b31edb..2ae6da9c8 100644
--- a/Source/Engine/Core/Collections/ArrayExtensions.h
+++ b/Source/Engine/Core/Collections/ArrayExtensions.h
@@ -4,7 +4,7 @@
#include "../Collections/Array.h"
#include "../Collections/Dictionary.h"
-#include
+#include "../Delegate.h"
class ArrayExtensions;
@@ -23,7 +23,6 @@ public:
///
/// Gets the common key.
///
- /// The key.
FORCE_INLINE const TKey& GetKey() const
{
return _key;
@@ -32,7 +31,6 @@ public:
///
/// Gets the common key.
///
- /// The key.
FORCE_INLINE TKey GetKey()
{
return _key;
@@ -52,7 +50,7 @@ public:
/// The prediction function. Should return true for the target element to find.
/// The index of the element or -1 if nothing found.
template
- static int32 IndexOf(const Array& obj, const std::function& predicate)
+ static int32 IndexOf(const Array& obj, const Function& predicate)
{
for (int32 i = 0; i < obj.Count(); i++)
{
@@ -71,7 +69,7 @@ public:
/// The prediction function.
/// True if any element in the collection matches the prediction, otherwise false.
template
- static bool Any(const Array& obj, const std::function& predicate)
+ static bool Any(const Array& obj, const Function& predicate)
{
for (int32 i = 0; i < obj.Count(); i++)
{
@@ -90,7 +88,7 @@ public:
/// The prediction function.
/// True if all elements in the collection matches the prediction, otherwise false.
template
- static int32 All(const Array& obj, const std::function& predicate)
+ static int32 All(const Array& obj, const Function& predicate)
{
for (int32 i = 0; i < obj.Count(); i++)
{
@@ -109,7 +107,7 @@ public:
/// A function to extract the key for each element.
/// The result collection with groups.
template
- static void GroupBy(const Array& obj, const std::function& keySelector, Array, AllocationType>& result)
+ static void GroupBy(const Array& obj, const Function& keySelector, Array, AllocationType>& result)
{
Dictionary> data(static_cast(obj.Count() * 3.0f));
for (int32 i = 0; i < obj.Count(); i++)
diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp
index ec56861ba..5b93ab588 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -105,6 +105,14 @@ int32 Engine::Main(const Char* cmdLine)
Globals::StartupFolder = Globals::BinariesFolder = Platform::GetMainDirectory();
#if USE_EDITOR
Globals::StartupFolder /= TEXT("../../../..");
+#if PLATFORM_MAC
+ if (Globals::BinariesFolder.EndsWith(TEXT(".app/Contents")))
+ {
+ // If running editor from application package on macOS
+ Globals::StartupFolder = Globals::BinariesFolder;
+ Globals::BinariesFolder /= TEXT("MacOS");
+ }
+#endif
#endif
StringUtils::PathRemoveRelativeParts(Globals::StartupFolder);
FileSystem::NormalizePath(Globals::BinariesFolder);
@@ -122,7 +130,6 @@ int32 Engine::Main(const Char* cmdLine)
}
EngineImpl::InitPaths();
-
EngineImpl::InitLog();
#if USE_EDITOR
@@ -542,7 +549,8 @@ void EngineImpl::InitLog()
LOG(Info, "Product: {0}, Company: {1}", Globals::ProductName, Globals::CompanyName);
LOG(Info, "Current culture: {0}", Platform::GetUserLocaleName());
LOG(Info, "Command line: {0}", CommandLine);
- LOG(Info, "Base directory: {0}", Globals::StartupFolder);
+ LOG(Info, "Base folder: {0}", Globals::StartupFolder);
+ LOG(Info, "Binaries folder: {0}", Globals::BinariesFolder);
LOG(Info, "Temporary folder: {0}", Globals::TemporaryFolder);
LOG(Info, "Project folder: {0}", Globals::ProjectFolder);
#if USE_EDITOR
diff --git a/Source/Engine/Engine/InputAxis.cs b/Source/Engine/Engine/InputAxis.cs
index f8ba7e72b..c70bada74 100644
--- a/Source/Engine/Engine/InputAxis.cs
+++ b/Source/Engine/Engine/InputAxis.cs
@@ -1,5 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+using System;
+
namespace FlaxEngine
{
///
@@ -23,11 +25,17 @@ namespace FlaxEngine
///
public float ValueRaw => Input.GetAxisRaw(Name);
+ ///
+ /// Occurs when axis is changed. Called before scripts update.
+ ///
+ public event Action ValueChanged;
+
///
/// Initializes a new instance of the class.
///
public InputAxis()
{
+ Input.AxisValueChanged += Handler;
}
///
@@ -36,7 +44,31 @@ namespace FlaxEngine
/// The axis name.
public InputAxis(string name)
{
+ Input.AxisValueChanged += Handler;
Name = name;
}
+
+ private void Handler(string name)
+ {
+ if (string.Equals(Name, name, StringComparison.OrdinalIgnoreCase))
+ ValueChanged?.Invoke();
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~InputAxis()
+ {
+ Input.AxisValueChanged -= Handler;
+ }
+
+ ///
+ /// Releases this object.
+ ///
+ public void Dispose()
+ {
+ Input.AxisValueChanged -= Handler;
+ GC.SuppressFinalize(this);
+ }
}
}
diff --git a/Source/Engine/Engine/InputEvent.cs b/Source/Engine/Engine/InputEvent.cs
index 176d21fd0..0267aed96 100644
--- a/Source/Engine/Engine/InputEvent.cs
+++ b/Source/Engine/Engine/InputEvent.cs
@@ -16,15 +16,36 @@ namespace FlaxEngine
public string Name;
///
- /// Returns true if the event has been triggered during the current frame (e.g. user pressed a key). Use to catch events without active waiting.
+ /// Returns true if the event has been triggered during the current frame (e.g. user pressed a key). Use to catch events without active waiting.
///
public bool Active => Input.GetAction(Name);
+ ///
+ /// Returns the event state. Use Use , , to catch events without active waiting.
+ ///
+ public InputActionState State => Input.GetActionState(Name);
+
///
/// Occurs when event is triggered (e.g. user pressed a key). Called before scripts update.
///
+ [System.Obsolete("Depreciated in 1.7, use Pressed Action.")]
public event Action Triggered;
+ ///
+ /// Occurs when event is pressed (e.g. user pressed a key). Called before scripts update.
+ ///
+ public event Action Pressed;
+
+ ///
+ /// Occurs when event is being pressing (e.g. user pressing a key). Called before scripts update.
+ ///
+ public event Action Pressing;
+
+ ///
+ /// Occurs when event is released (e.g. user releases a key). Called before scripts update.
+ ///
+ public event Action Released;
+
///
/// Initializes a new instance of the class.
///
@@ -51,10 +72,26 @@ namespace FlaxEngine
Input.ActionTriggered -= Handler;
}
- private void Handler(string name)
+ private void Handler(string name, InputActionState state)
{
- if (string.Equals(name, Name, StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(name, Name, StringComparison.OrdinalIgnoreCase))
+ return;
+ switch (state)
+ {
+ case InputActionState.None: break;
+ case InputActionState.Waiting: break;
+ case InputActionState.Pressing:
+ Pressing?.Invoke();
+ break;
+ case InputActionState.Press:
Triggered?.Invoke();
+ Pressed?.Invoke();
+ break;
+ case InputActionState.Release:
+ Released?.Invoke();
+ break;
+ default: break;
+ }
}
///
diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp
index 785010c44..7d530dfed 100644
--- a/Source/Engine/Foliage/FoliageType.cpp
+++ b/Source/Engine/Foliage/FoliageType.cpp
@@ -130,7 +130,7 @@ void FoliageType::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE(Model);
- const std::function IsValidMaterial = [](const ModelInstanceEntry& e) -> bool
+ const Function IsValidMaterial = [](const ModelInstanceEntry& e) -> bool
{
return e.Material;
};
diff --git a/Source/Engine/Graphics/Models/SkeletonMapping.h b/Source/Engine/Graphics/Models/SkeletonMapping.h
index ac2af245b..e3ec0c793 100644
--- a/Source/Engine/Graphics/Models/SkeletonMapping.h
+++ b/Source/Engine/Graphics/Models/SkeletonMapping.h
@@ -66,7 +66,7 @@ public:
const auto parentModelIndex = node.ParentIndex;
// Find matching node in skeleton (or map to best parent)
- const std::function f = [node](const T& x) -> bool
+ const Function f = [node](const T& x) -> bool
{
return x.Name == node.Name;
};
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp
index 64d8bfb67..28957d9f3 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp
@@ -459,9 +459,9 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
{
case VK_DESCRIPTOR_TYPE_SAMPLER:
{
- const VkSampler sampler = _samplerHandles[slot];
- ASSERT(sampler);
- needsWrite |= dsWriter.WriteSampler(descriptorIndex, sampler, index);
+ const VkSampler handle = _samplerHandles[slot];
+ ASSERT(handle);
+ needsWrite |= dsWriter.WriteSampler(descriptorIndex, handle, index);
break;
}
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
@@ -547,12 +547,18 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
}
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
{
- auto cb = handles[slot];
- ASSERT(cb);
- VkBuffer buffer;
- VkDeviceSize offset, range;
- uint32 dynamicOffset;
- cb->DescriptorAsDynamicUniformBuffer(this, buffer, offset, range, dynamicOffset);
+ auto handle = handles[slot];
+ VkBuffer buffer = VK_NULL_HANDLE;
+ VkDeviceSize offset = 0, range = 0;
+ uint32 dynamicOffset = 0;
+ if (handle)
+ handle->DescriptorAsDynamicUniformBuffer(this, buffer, offset, range, dynamicOffset);
+ else
+ {
+ const auto dummy = _device->HelperResources.GetDummyBuffer();
+ buffer = dummy->GetHandle();
+ range = dummy->GetSize();
+ }
needsWrite |= dsWriter.WriteDynamicUniformBuffer(descriptorIndex, buffer, offset, range, dynamicOffset, index);
break;
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp
index bdd92c762..0b857d394 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp
@@ -35,6 +35,10 @@ static const char* GValidationLayers[] =
static const char* GInstanceExtensions[] =
{
+#if PLATFORM_APPLE_FAMILY && defined(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)
+ VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
+ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
+#endif
#if VK_EXT_validation_cache
VK_EXT_VALIDATION_CACHE_EXTENSION_NAME,
#endif
@@ -46,6 +50,9 @@ static const char* GInstanceExtensions[] =
static const char* GDeviceExtensions[] =
{
+#if PLATFORM_APPLE_FAMILY && defined(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)
+ VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
+#endif
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
#if VK_KHR_maintenance1
VK_KHR_MAINTENANCE1_EXTENSION_NAME,
@@ -571,7 +578,7 @@ void GPUDeviceVulkan::ParseOptionalDeviceExtensions(const Array& de
const auto HasExtension = [&deviceExtensions](const char* name) -> bool
{
- const std::function CheckCallback = [&name](const char* const& extension) -> bool
+ const Function CheckCallback = [&name](const char* const& extension) -> bool
{
return StringUtils::Compare(extension, name) == 0;
};
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
index eb23c0871..b4a6112a9 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
@@ -431,7 +431,7 @@ void DeferredDeletionQueueVulkan::EnqueueGenericResource(Type type, uint64 handl
ScopeLock lock(_locker);
#if BUILD_DEBUG
- const std::function ContainsHandle = [handle](const Entry& e)
+ const Function ContainsHandle = [handle](const Entry& e)
{
return e.Handle == handle;
};
@@ -868,7 +868,7 @@ GPUBufferVulkan* HelperResourcesVulkan::GetDummyBuffer()
if (!_dummyBuffer)
{
_dummyBuffer = (GPUBufferVulkan*)_device->CreateBuffer(TEXT("DummyBuffer"));
- _dummyBuffer->Init(GPUBufferDescription::Buffer(sizeof(int32), GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess, PixelFormat::R32_SInt));
+ _dummyBuffer->Init(GPUBufferDescription::Buffer(sizeof(int32) * 256, GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess, PixelFormat::R32_SInt));
}
return _dummyBuffer;
@@ -1078,13 +1078,16 @@ GPUDevice* GPUDeviceVulkan::Create()
VkInstanceCreateInfo instInfo;
RenderToolsVulkan::ZeroStruct(instInfo, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO);
+#if PLATFORM_APPLE_FAMILY
+ instInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
+#endif
instInfo.pApplicationInfo = &appInfo;
GetInstanceLayersAndExtensions(InstanceExtensions, InstanceLayers, SupportsDebugUtilsExt);
const auto hasExtension = [](const Array& extensions, const char* name) -> bool
{
- const std::function callback = [&name](const char* const& extension) -> bool
+ const Function callback = [&name](const char* const& extension) -> bool
{
return extension && StringUtils::Compare(extension, name) == 0;
};
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs b/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs
index 252652978..9ffa68272 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs
+++ b/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs
@@ -51,7 +51,8 @@ public sealed class VulkanSdk : Sdk
var subDirs = Directory.GetDirectories(path);
if (subDirs.Length != 0)
{
- path = Path.Combine(subDirs[0], "macOS");
+ Flax.Build.Utilities.SortVersionDirectories(subDirs);
+ path = Path.Combine(subDirs.Last(), "macOS");
if (Directory.Exists(path))
vulkanSdk = path;
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h b/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h
index 3a1b6a615..4e2694aab 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h
@@ -41,4 +41,15 @@
#define VMA_NOT_NULL
#include
+#if PLATFORM_APPLE_FAMILY
+// Declare potentially missing extensions from newer SDKs
+#ifndef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME
+#define VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME "VK_KHR_portability_enumeration"
+#define VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR 0x00000001
+#endif
+#ifndef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME
+#define VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME "VK_KHR_portability_subset"
+#endif
+#endif
+
#endif
diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp
index 81411ed76..dbcdf66bd 100644
--- a/Source/Engine/Input/Input.cpp
+++ b/Source/Engine/Input/Input.cpp
@@ -97,7 +97,8 @@ Action Input::MouseLeave;
Delegate Input::TouchDown;
Delegate Input::TouchMove;
Delegate Input::TouchUp;
-Delegate Input::ActionTriggered;
+Delegate Input::ActionTriggered;
+Delegate Input::AxisValueChanged;
Array Input::ActionMappings;
Array Input::AxisMappings;
@@ -1017,14 +1018,22 @@ void InputService::Update()
Input::SetMousePosition(Screen::GetSize() * 0.5f);
}
- // Send events for the active actions (send events only in play mode)
+ // Send events for the active actions and axes (send events only in play mode)
if (!Time::GetGamePaused())
{
+ for (auto i = Axes.Begin(); i.IsNotEnd(); ++i)
+ {
+ if (Math::NotNearEqual(i->Value.Value, i->Value.PrevKeyValue))
+ {
+ Input::AxisValueChanged(i->Key);
+ }
+ }
+
for (auto i = Actions.Begin(); i.IsNotEnd(); ++i)
{
- if (i->Value.Active)
+ if (i->Value.State != InputActionState::Waiting)
{
- Input::ActionTriggered(i->Key);
+ Input::ActionTriggered(i->Key, i->Value.State);
}
}
}
diff --git a/Source/Engine/Input/Input.h b/Source/Engine/Input/Input.h
index 84d312fa3..c11026eff 100644
--- a/Source/Engine/Input/Input.h
+++ b/Source/Engine/Input/Input.h
@@ -293,7 +293,13 @@ public:
/// Event fired when virtual input action is triggered. Called before scripts update. See to edit configuration.
///
///
- API_EVENT() static Delegate ActionTriggered;
+ API_EVENT() static Delegate ActionTriggered;
+
+ ///
+ /// Event fired when virtual input axis is changed. Called before scripts update. See to edit configuration.
+ ///
+ ///
+ API_EVENT() static Delegate AxisValueChanged;
///
/// Gets the value of the virtual action identified by name. Use to get the current config.
diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp
index 2ff681628..459e1fb6b 100644
--- a/Source/Engine/Level/Actors/AnimatedModel.cpp
+++ b/Source/Engine/Level/Actors/AnimatedModel.cpp
@@ -697,7 +697,8 @@ void AnimatedModel::UpdateBounds()
}
else if (model && model->IsLoaded() && model->LODs.Count() != 0)
{
- BoundingBox box = model->LODs[0].GetBox(_transform, _deformation);
+ const BoundingBox modelBox = model->GetBox(_transform.GetWorld());
+ BoundingBox box = modelBox;
if (GraphInstance.NodesPose.Count() != 0)
{
// Per-bone bounds estimated from positions
@@ -705,11 +706,11 @@ void AnimatedModel::UpdateBounds()
const int32 bonesCount = skeleton.Bones.Count();
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
box.Merge(_transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones.Get()[boneIndex].NodeIndex].GetTranslation()));
+ _box = box;
}
- _box = box;
// Apply margin based on model dimensions
- const Vector3 modelBoxSize = model->GetBox().GetSize();
+ const Vector3 modelBoxSize = modelBox.GetSize();
const Vector3 center = _box.GetCenter();
const Vector3 sizeHalf = Vector3::Max(_box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f;
_box = BoundingBox(center - sizeHalf, center + sizeHalf);
diff --git a/Source/Engine/Level/Components/MissingScript.h b/Source/Engine/Level/Components/MissingScript.h
new file mode 100644
index 000000000..bfb73498f
--- /dev/null
+++ b/Source/Engine/Level/Components/MissingScript.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if USE_EDITOR
+
+#include "Engine/Core/Cache.h"
+#include "Engine/Scripting/Script.h"
+#include "Engine/Scripting/ScriptingObjectReference.h"
+#include "Engine/Serialization/JsonWriters.h"
+
+///
+/// Actor script component that represents missing script.
+///
+API_CLASS(Attributes="HideInEditor") class FLAXENGINE_API MissingScript : public Script
+{
+ API_AUTO_SERIALIZATION();
+ DECLARE_SCRIPTING_TYPE(MissingScript);
+
+private:
+ ScriptingObjectReference