diff --git a/Source/Editor/CustomEditorWindow.cs b/Source/Editor/CustomEditorWindow.cs
index 3df453ef8..4a94e0a34 100644
--- a/Source/Editor/CustomEditorWindow.cs
+++ b/Source/Editor/CustomEditorWindow.cs
@@ -1,8 +1,10 @@
// Copyright (c) Wojciech Figat. All rights reserved.
+using System;
using FlaxEditor.CustomEditors;
using FlaxEditor.GUI.Docking;
using FlaxEditor.Windows;
+using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor
@@ -89,8 +91,20 @@ namespace FlaxEditor
{
Editor.Instance.Windows.AddToRestore(this);
}
- Window.Close();
- Window.Dispose();
+ Window.Close(ClosingReason.ScriptsReload);
+ }
+
+ ///
+ /// Reattaches the window control to existing floating window.
+ ///
+ /// The window handle.
+ /// Initial window state.
+ /// The panel to dock to, if any.
+ /// Only used if is set. If true the window will be selected after docking it.
+ /// The splitter value to use if toDock is not null. If not specified, a default value will be used.
+ public void Restore(IntPtr windowHandle, DockState state = DockState.Float, DockPanel toDock = null, bool autoSelect = true, float? splitterValue = null)
+ {
+ _win.Restore(windowHandle, state, toDock, autoSelect, splitterValue);
}
///
diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
index b63866c9c..29328e318 100644
--- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
@@ -322,7 +322,11 @@ namespace FlaxEditor.CustomEditors.Editors
// Deselect
if (_value != null && button1Rect.Contains(ref location))
+ {
+ Focus();
Value = null;
+ return true;
+ }
// Picker dropdown menu
if (_supportsPickDropDown && (isSelected ? button2Rect : button1Rect).Contains(ref location))
@@ -372,6 +376,18 @@ namespace FlaxEditor.CustomEditors.Editors
if (_hasValidDragOver)
_hasValidDragOver = false;
}
+ if (button == MouseButton.Middle)
+ {
+ // Clear value
+ Focus();
+ Value = null;
+ return true;
+ }
+ if (button == MouseButton.Right && _supportsPickDropDown)
+ {
+ // Show picker
+ ShowDropDownMenu();
+ }
return base.OnMouseUp(location, button);
}
diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp
index 18bf43a3a..f3065c1a1 100644
--- a/Source/Editor/Editor.cpp
+++ b/Source/Editor/Editor.cpp
@@ -642,9 +642,11 @@ int32 Editor::LoadProduct()
}
}
+#if !FLAX_TESTS
// Update the last opened project path
if (lastProjectPath.Compare(Project->ProjectFolderPath) != 0)
File::WriteAllText(lastProjectSettingPath, Project->ProjectFolderPath, Encoding::UTF8);
+#endif
return 0;
}
diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs
index f9c322c32..7d0e6c20b 100644
--- a/Source/Editor/GUI/AssetPicker.cs
+++ b/Source/Editor/GUI/AssetPicker.cs
@@ -299,6 +299,7 @@ namespace FlaxEditor.GUI
}
else if (Button1Rect.Contains(location))
{
+ // Show picker
Focus();
OnSubmit();
}
@@ -318,6 +319,18 @@ namespace FlaxEditor.GUI
}
}
}
+ if (button == MouseButton.Middle && IconRect.Contains(ref location))
+ {
+ // Clear value
+ Focus();
+ Validator.SelectedItem = null;
+ }
+ if (button == MouseButton.Right && IconRect.Contains(ref location))
+ {
+ // Show picker
+ Focus();
+ OnSubmit();
+ }
// Handled
return true;
diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs
index 8cde79ce8..bf08695d8 100644
--- a/Source/Editor/GUI/Docking/DockWindow.cs
+++ b/Source/Editor/GUI/Docking/DockWindow.cs
@@ -1,7 +1,9 @@
// Copyright (c) Wojciech Figat. All rights reserved.
+using System;
using System.Xml;
using System.Globalization;
+using System.Linq;
using FlaxEngine;
using FlaxEngine.Assertions;
using FlaxEngine.GUI;
@@ -50,7 +52,7 @@ namespace FlaxEditor.GUI.Docking
///
/// Gets a value indicating whether this window is docked.
///
- public bool IsDocked => _dockedTo != null;
+ public bool IsDocked => _dockedTo != null && _dockedTo.TabsCount > 1;
///
/// Gets a value indicating whether this window is selected.
@@ -232,6 +234,46 @@ namespace FlaxEditor.GUI.Docking
}
}
+ ///
+ /// Reattaches the window control to existing floating window.
+ ///
+ /// The window handle.
+ /// Initial window state.
+ /// Panel to dock to it.
+ /// Only used if is set. If true the window will be selected after docking it.
+ /// Only used if is set. The splitter value to use. If not specified, a default value will be used.
+ public void Restore(IntPtr windowHandle, DockState state = DockState.Float, DockPanel toDock = null, bool autoSelect = true, float? splitterValue = null)
+ {
+ if (state != DockState.Float)
+ {
+ Show(state, toDock, autoSelect, splitterValue);
+ return;
+ }
+
+ Undock();
+
+ // Find the existing window and remove all controls from it
+ var window = Editor.GetWindows().First(x => x.NativePtr == windowHandle);
+ var windowGUI = window.GUI;
+ while (windowGUI.Children.Count > 0)
+ windowGUI.Children[^1].Dispose();
+ windowGUI.EndTrackingMouse();
+
+ // Create dock panel for the window
+ var dockPanel = new FloatWindowDockPanel(_masterPanel, windowGUI);
+ dockPanel.DockWindowInternal(DockState.DockFill, this);
+
+ // Perform layout
+ Visible = true;
+ windowGUI.UnlockChildrenRecursive();
+ windowGUI.PerformLayout();
+
+ OnShow();
+
+ // Perform layout again
+ windowGUI.PerformLayout();
+ }
+
///
/// Shows the window.
///
@@ -332,6 +374,12 @@ namespace FlaxEditor.GUI.Docking
}
else
{
+ if (reason == ClosingReason.ScriptsReload && _dockedTo is FloatWindowDockPanel floatPanel)
+ {
+ // Unlink the window to keep it alive during scripts reload
+ floatPanel.UnlinkWindow();
+ }
+
// Undock
Undock();
@@ -488,7 +536,7 @@ namespace FlaxEditor.GUI.Docking
base.Focus();
SelectTab(false);
- BringToFront();
+ _dockedTo?.RootWindow?.Focus();
}
///
diff --git a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
index b47d76bdd..f8eb26ded 100644
--- a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
+++ b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
@@ -49,6 +49,7 @@ namespace FlaxEditor.GUI.Docking
private MasterDockPanel _masterPanel;
private WindowRootControl _window;
+ private bool _showDecorations;
///
/// Gets the master panel.
@@ -65,6 +66,31 @@ namespace FlaxEditor.GUI.Docking
///
public bool IsDragging { get; internal set; }
+ ///
+ /// Shows client-side window decorations around this panel.
+ ///
+ public bool ShowDecorations
+ {
+ get => _showDecorations;
+ set
+ {
+ if (value == _showDecorations)
+ return;
+ _showDecorations = value;
+ if (value)
+ {
+ var decorations = Parent.AddChild(new FloatWindowDecorations(this));
+ decorations.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, false);
+ }
+ else
+ {
+ var decorations = Parent.GetChild();
+ if (decorations != null)
+ decorations.Dispose();
+ }
+ }
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -82,10 +108,16 @@ namespace FlaxEditor.GUI.Docking
_window.Window.Closing += OnClosing;
_window.Window.LeftButtonHit += OnLeftButtonHit;
- if (Utilities.Utils.UseCustomWindowDecorations())
+ ShowDecorations = Utilities.Utils.UseCustomWindowDecorations();
+ }
+
+ internal void UnlinkWindow()
+ {
+ if (_window != null)
{
- var decorations = Parent.AddChild(new FloatWindowDecorations(this));
- decorations.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, false);
+ _window.Window.Closing -= OnClosing;
+ _window.Window.LeftButtonHit -= OnLeftButtonHit;
+ _window = null;
}
}
@@ -94,13 +126,10 @@ namespace FlaxEditor.GUI.Docking
{
base.PerformLayoutBeforeChildren();
- var decorations = Parent.GetChild();
- if (decorations != null)
- {
- // Apply offset for the title bar
- foreach (var child in Children)
- child.Bounds = child.Bounds with { Y = decorations.Height, Height = Parent.Height - decorations.Height };
- }
+ // Apply offset for the title bar
+ var decorationsHeight = Parent.GetChild()?.Height ?? 0;
+ foreach (var child in Children)
+ child.Bounds = child.Bounds with { Y = decorationsHeight, Height = Parent.Height - decorationsHeight };
}
///
@@ -191,9 +220,7 @@ namespace FlaxEditor.GUI.Docking
}
// Unlink
- _window.Window.Closing -= OnClosing;
- _window.Window.LeftButtonHit = null;
- _window = null;
+ UnlinkWindow();
// Remove object
FlaxEngine.Assertions.Assert.IsTrue(TabsCount == 0 && ChildPanelsCount == 0);
@@ -244,6 +271,7 @@ namespace FlaxEditor.GUI.Docking
{
_masterPanel?.FloatingPanels.Remove(this);
+ UnlinkWindow();
base.OnDestroy();
}
}
diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs
index 34e35bcc0..f7278a478 100644
--- a/Source/Editor/Modules/ContentDatabaseModule.cs
+++ b/Source/Editor/Modules/ContentDatabaseModule.cs
@@ -748,21 +748,27 @@ namespace FlaxEditor.Modules
if (item.Path.Contains(".Build.cs", StringComparison.Ordinal) && item.ItemType == ContentItemType.Script)
Editor.Instance.CodeEditing.RemoveModule(item.Path);
- // Check if it's an asset
- if (item.IsAsset)
- {
- // Delete asset by using content pool
- FlaxEngine.Content.DeleteAsset(path);
- }
- else if (item is ScriptItem)
- {
- FlaxEngine.Content.DeleteScript(path);
- }
- else if (deletedByUser)
- {
- // Delete file
- if (File.Exists(path))
- File.Delete(path);
+ // Delete asset file only if it was explicitly deleted by the user.
+ // Some applications might modify the file by deleting the original and replacing
+ // it with a new file which sometimes is caught in the middle of these operations.
+ if (deletedByUser)
+ {
+ // Check if it's an asset
+ if (item.IsAsset)
+ {
+ // Delete asset by using content pool
+ FlaxEngine.Content.DeleteAsset(path);
+ }
+ else if (item is ScriptItem)
+ {
+ FlaxEngine.Content.DeleteScript(path);
+ }
+ else
+ {
+ // Delete file
+ if (File.Exists(path))
+ File.Delete(path);
+ }
}
// Unlink from the parent
@@ -826,23 +832,29 @@ namespace FlaxEditor.Modules
var startTime = Platform.TimeSeconds;
_rebuildFlag = false;
_rebuildInitFlag = false;
- _enableEvents = false;
- // Load all folders
- // TODO: we should create async task for gathering content and whole workspace contents if it takes too long
- // TODO: create progress bar in content window and after end we should enable events and update it
+ _enableEvents = false;
_isDuringFastSetup = true;
var startItems = _itemsCreated;
- foreach (var project in Projects)
+ try
{
- if (project.Content != null)
- LoadFolder(project.Content, true);
- if (project.Source != null)
- LoadFolder(project.Source, true);
+ // Load all folders
+ // TODO: we should create async task for gathering content and whole workspace contents if it takes too long
+ // TODO: create progress bar in content window and after end we should enable events and update it
+ foreach (var project in Projects)
+ {
+ if (project.Content != null)
+ LoadFolder(project.Content, true);
+ if (project.Source != null)
+ LoadFolder(project.Source, true);
+ }
}
- _isDuringFastSetup = false;
-
- _enableEvents = enableEvents;
+ finally
+ {
+ _isDuringFastSetup = false;
+ _enableEvents = enableEvents;
+ }
+
var endTime = Platform.TimeSeconds;
Editor.Log(string.Format("Project database created in {0} ms. Items count: {1}", (int)((endTime - startTime) * 1000.0), _itemsCreated - startItems));
Profiler.EndEvent();
@@ -1340,39 +1352,44 @@ namespace FlaxEditor.Modules
_enableEvents = false;
_isDuringFastSetup = true;
var startItems = _itemsCreated;
- foreach (var project in Projects)
+ try
{
- if (project.Content != null)
+ foreach (var project in Projects)
{
- //Dispose(project.Content.Folder);
- for (int i = 0; i < project.Content.Folder.Children.Count; i++)
+ if (project.Content != null)
{
- Dispose(project.Content.Folder.Children[i]);
- i--;
+ //Dispose(project.Content.Folder);
+ for (int i = 0; i < project.Content.Folder.Children.Count; i++)
+ {
+ Dispose(project.Content.Folder.Children[i]);
+ i--;
+ }
+ }
+ if (project.Source != null)
+ {
+ //Dispose(project.Source.Folder);
+ for (int i = 0; i < project.Source.Folder.Children.Count; i++)
+ {
+ Dispose(project.Source.Folder.Children[i]);
+ i--;
+ }
}
}
- if (project.Source != null)
+
+ List removeProxies = new List();
+ foreach (var proxy in Editor.Instance.ContentDatabase.Proxy)
{
- //Dispose(project.Source.Folder);
- for (int i = 0; i < project.Source.Folder.Children.Count; i++)
- {
- Dispose(project.Source.Folder.Children[i]);
- i--;
- }
+ if (proxy.GetType().IsCollectible)
+ removeProxies.Add(proxy);
}
+ foreach (var proxy in removeProxies)
+ RemoveProxy(proxy, false);
}
-
- List removeProxies = new List();
- foreach (var proxy in Editor.Instance.ContentDatabase.Proxy)
+ finally
{
- if (proxy.GetType().IsCollectible)
- removeProxies.Add(proxy);
+ _isDuringFastSetup = false;
+ _enableEvents = enabledEvents;
}
- foreach (var proxy in removeProxies)
- RemoveProxy(proxy, false);
-
- _isDuringFastSetup = false;
- _enableEvents = enabledEvents;
}
private void OnScriptsReloadEnd()
@@ -1383,18 +1400,22 @@ namespace FlaxEditor.Modules
///
public override void OnUpdate()
{
- // Update all dirty content tree nodes
- lock (_dirtyNodes)
+ // Check for updates only when the editor is focused to only check once for all changes done in background.
+ if (FlaxEngine.Engine.HasFocus)
{
- Profiler.BeginEvent("ContentDatabase.Refresh");
- foreach (var node in _dirtyNodes)
+ // Update all dirty content tree nodes
+ lock (_dirtyNodes)
{
- LoadFolder(node, true);
+ Profiler.BeginEvent("ContentDatabase.Refresh");
+ foreach (var node in _dirtyNodes)
+ {
+ LoadFolder(node, true);
+ }
+ if (_enableEvents && _dirtyNodes.Count != 0)
+ WorkspaceModified?.Invoke();
+ _dirtyNodes.Clear();
+ Profiler.EndEvent();
}
- if (_enableEvents && _dirtyNodes.Count != 0)
- WorkspaceModified?.Invoke();
- _dirtyNodes.Clear();
- Profiler.EndEvent();
}
// Lazy-rebuilds
diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
index 12cd89060..d29b6f89d 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
@@ -505,7 +505,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
base.OnUpdate();
// Automatic project files generation after workspace modifications
- if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty && !ScriptsBuilder.IsCompiling)
+ if (_autoGenerateScriptsProjectFiles && ScriptsBuilder.IsSourceWorkspaceDirty && !ScriptsBuilder.IsCompiling && Engine.HasFocus)
{
// Try to delay generation when a lot of files are added at once
if (ScriptsBuilder.IsSourceDirtyFor(TimeSpan.FromMilliseconds(150)))
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index b4f8089e2..b667e3879 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
@@ -33,11 +34,12 @@ namespace FlaxEditor.Modules
private float _projectIconScreenshotTimeout = -1;
private string _windowsLayoutPath;
- private struct WindowRestoreData
+ private class WindowRestoreData
{
public string AssemblyName;
public string TypeName;
+ public IntPtr WindowHandle;
public DockState DockState;
public DockPanel DockedTo;
public int DockedTabIndex;
@@ -45,20 +47,10 @@ namespace FlaxEditor.Modules
public bool SelectOnShow = false;
- public bool Maximize;
- public bool Minimize;
- public Float2 FloatSize;
- public Float2 FloatPosition;
-
public Guid AssetItemID;
-
- // Constructor, to allow for default values
- public WindowRestoreData()
- {
- }
}
- private readonly List _restoreWindows = new List();
+ private readonly Dictionary _restoreWindows = new();
///
/// The main editor window.
@@ -818,10 +810,14 @@ namespace FlaxEditor.Modules
internal void AddToRestore(AssetEditorWindow win)
{
- AddToRestore(win, win.GetType(), new WindowRestoreData
+ var assetItemId = win.Item.ID;
+ if (!_restoreWindows.TryGetValue(assetItemId, out var winData))
{
- AssetItemID = win.Item.ID,
- });
+ winData = new WindowRestoreData();
+ _restoreWindows.Add(assetItemId, winData);
+ }
+ winData.AssetItemID = assetItemId;
+ AddToRestore(win, win.GetType(), winData);
}
internal void AddToRestore(CustomEditorWindow win)
@@ -832,62 +828,65 @@ namespace FlaxEditor.Modules
if (constructor == null || type.IsGenericType)
return;
- AddToRestore(win.Window, type, new WindowRestoreData());
+ // TODO: Restore data for custom editors
+ var assetItemId = Guid.NewGuid();
+ if (!_restoreWindows.TryGetValue(assetItemId, out var winData))
+ {
+ winData = new WindowRestoreData();
+ _restoreWindows.Add(assetItemId, winData);
+ }
+ winData.AssetItemID = assetItemId;
+ AddToRestore(win.Window, type, winData);
}
private void AddToRestore(EditorWindow win, Type type, WindowRestoreData winData)
{
- // Ensure that this window is only selected following recompilation
- // if it was the active tab in its dock panel. Otherwise, there is a
- // risk of interrupting the user's workflow by potentially selecting
- // background tabs.
var window = win.RootWindow?.Window;
var panel = win.ParentDockPanel;
- winData.SelectOnShow = panel.SelectedTab == win;
- winData.DockedTabIndex = 0;
- if (panel is FloatWindowDockPanel && window != null && panel.TabsCount == 1)
+ winData.AssemblyName = type.Assembly.GetName().Name;
+ winData.TypeName = type.FullName;
+ if (panel is FloatWindowDockPanel)
{
- winData.DockState = DockState.Float;
- winData.FloatPosition = window.Position;
- winData.FloatSize = window.ClientSize;
- winData.Maximize = window.IsMaximized;
- winData.Minimize = window.IsMinimized;
- winData.DockedTo = panel;
+ // Populate data for other tabs now, the tab index may change after tab is destroyed
+ if (winData.DockedTo == null)
+ {
+ for (int i = 0; i < panel.Tabs.Count; i++)
+ {
+ if (panel.Tabs[i] is AssetEditorWindow assetEditorWindow)
+ {
+ window ??= assetEditorWindow.RootWindow?.Window; // The window handle is sometimes missing in some tabs
+ var assetItemId = assetEditorWindow.Item.ID;
+ if (!_restoreWindows.TryGetValue(assetItemId, out var tabWinData))
+ {
+ tabWinData = new WindowRestoreData();
+ _restoreWindows.Add(assetItemId, tabWinData);
+ }
+
+ tabWinData.DockedTabIndex = i;
+ tabWinData.SelectOnShow = panel.SelectedTab == assetEditorWindow;
+ tabWinData.DockState = DockState.DockFill;
+ tabWinData.DockedTo = panel;
+ }
+ }
+ winData.DockState = DockState.Float;
+ winData.WindowHandle = window?.NativePtr ?? IntPtr.Zero;
+ }
}
else
{
- for (int i = 0; i < panel.Tabs.Count; i++)
- {
- if (panel.Tabs[i] == win)
- {
- winData.DockedTabIndex = i;
- break;
- }
- }
- if (panel.TabsCount > 1)
- {
- winData.DockState = DockState.DockFill;
- winData.DockedTo = panel;
- }
- else
- {
- winData.DockState = panel.TryGetDockState(out var splitterValue);
- winData.DockedTo = panel.ParentDockPanel;
- winData.SplitterValue = splitterValue;
- }
+ winData.DockState = panel.TryGetDockState(out var splitterValue);
+ winData.DockedTo = panel.ParentDockPanel;
+ winData.SplitterValue = splitterValue;
}
- winData.AssemblyName = type.Assembly.GetName().Name;
- winData.TypeName = type.FullName;
- _restoreWindows.Add(winData);
}
private void OnWorkspaceRebuilt()
{
- // Go in reverse order to create floating Prefab windows first before docked windows
- for (int i = _restoreWindows.Count - 1; i >= 0; i--)
+ var assetEditorWindows = new Dictionary();
+
+ // Create float windows first, then add docked windows in order of the tabs
+ foreach (var (_, winData) in _restoreWindows)
{
- var winData = _restoreWindows[i];
-
try
{
var assembly = Utils.GetAssemblyByName(winData.AssemblyName);
@@ -904,75 +903,24 @@ namespace FlaxEditor.Modules
var assetType = assetItem.GetType();
var ctor = type.GetConstructor(new Type[] { typeof(Editor), assetType });
var win = (AssetEditorWindow)ctor.Invoke(new object[] { Editor.Instance, assetItem });
-
- win.Show(winData.DockState, winData.DockState != DockState.Float ? winData.DockedTo : null, winData.SelectOnShow, winData.SplitterValue);
+ var previouslyDockedTo = winData.DockedTo;
+ win.Restore(winData.WindowHandle, winData.DockState, winData.DockState != DockState.Float ? winData.DockedTo : null, winData.SelectOnShow, winData.SplitterValue);
if (winData.DockState == DockState.Float)
{
- var window = win.RootWindow.Window;
- window.Position = winData.FloatPosition;
- if (winData.Maximize)
- {
- window.Maximize();
- }
- else if (winData.Minimize)
- {
- window.Minimize();
- }
- else
- {
- window.ClientSize = winData.FloatSize;
- }
-
// Update panel reference in other windows docked to this panel
- foreach (ref var otherData in CollectionsMarshal.AsSpan(_restoreWindows))
+ foreach (var key in _restoreWindows.Keys)
{
- if (otherData.DockedTo == winData.DockedTo)
+ ref var otherData = ref CollectionsMarshal.GetValueRefOrNullRef(_restoreWindows, key);
+ if (otherData.DockedTo == previouslyDockedTo)
otherData.DockedTo = win.ParentDockPanel;
}
}
- var panel = win.ParentDockPanel;
- int currentTabIndex = 0;
- for (int pi = 0; pi < panel.TabsCount; pi++)
- {
- if (panel.Tabs[pi] == win)
- {
- currentTabIndex = pi;
- break;
- }
- }
- while (currentTabIndex > winData.DockedTabIndex)
- {
- win.ParentDockPanel.MoveTabLeft(currentTabIndex);
- currentTabIndex--;
- }
- while (currentTabIndex < winData.DockedTabIndex)
- {
- win.ParentDockPanel.MoveTabRight(currentTabIndex);
- currentTabIndex++;
- }
- panel.PerformLayout(true);
+ assetEditorWindows.Add(winData.AssetItemID, win);
}
else
{
var win = (CustomEditorWindow)Activator.CreateInstance(type);
- win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue);
- if (winData.DockState == DockState.Float)
- {
- var window = win.Window.RootWindow.Window;
- window.Position = winData.FloatPosition;
- if (winData.Maximize)
- {
- window.Maximize();
- }
- else if (winData.Minimize)
- {
- window.Minimize();
- }
- else
- {
- window.ClientSize = winData.FloatSize;
- }
- }
+ win.Restore(winData.WindowHandle, winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue);
}
}
catch (Exception ex)
@@ -981,6 +929,46 @@ namespace FlaxEditor.Modules
Editor.LogWarning(string.Format("Failed to restore window {0} (assembly: {1})", winData.TypeName, winData.AssemblyName));
}
}
+
+ // Reorder tabs to previous order
+ foreach (var (_, winData) in _restoreWindows)
+ {
+ var win = assetEditorWindows.GetValueOrDefault(winData.AssetItemID);
+ if (win == null)
+ continue;
+
+ var panel = win.ParentDockPanel;
+ int currentTabIndex = 0;
+ for (int pi = 0; pi < panel.TabsCount; pi++)
+ {
+ if (panel.Tabs[pi] == win)
+ {
+ currentTabIndex = pi;
+ break;
+ }
+ }
+ while (currentTabIndex > winData.DockedTabIndex)
+ {
+ win.ParentDockPanel.MoveTabLeft(currentTabIndex);
+ currentTabIndex--;
+ }
+ while (currentTabIndex < winData.DockedTabIndex)
+ {
+ win.ParentDockPanel.MoveTabRight(currentTabIndex);
+ currentTabIndex++;
+ }
+ }
+
+ // Restore last selected tab
+ foreach (var (_, winData) in _restoreWindows)
+ {
+ var win = assetEditorWindows.GetValueOrDefault(winData.AssetItemID);
+ if (win != null && winData.SelectOnShow)
+ {
+ win.ParentDockPanel.SelectTab(win, false);
+ win.ParentDockPanel.PerformLayout(true);
+ }
+ }
// Restored windows stole the focus from Editor
if (_restoreWindows.Count > 0)
diff --git a/Source/Editor/States/PlayingState.cs b/Source/Editor/States/PlayingState.cs
index 51dfd3677..a81487e43 100644
--- a/Source/Editor/States/PlayingState.cs
+++ b/Source/Editor/States/PlayingState.cs
@@ -159,11 +159,11 @@ namespace FlaxEditor.States
SceneDuplicated?.Invoke();
RestoreSelection();
+ Time.Synchronize(true);
+
Editor.OnPlayBegin();
IsPlayModeStarting = false;
Profiler.EndEvent();
-
- Time.Synchronize(true);
}
private void SetupEditorEnvOptions()
diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs
index d28d04e1e..b050d5ae2 100644
--- a/Source/Editor/Surface/SurfaceNode.cs
+++ b/Source/Editor/Surface/SurfaceNode.cs
@@ -76,6 +76,11 @@ namespace FlaxEditor.Surface
/// The footer rectangle (local space).
///
protected Rectangle _footerRect;
+
+ ///
+ /// The last mouse down position.
+ ///
+ protected Float2 _mouseDownMousePosition;
///
/// The node archetype.
@@ -163,8 +168,6 @@ namespace FlaxEditor.Surface
///
protected virtual Color ArchetypeColor => GroupArchetype.Color;
- private Float2 mouseDownMousePosition;
-
///
/// Calculates the size of the node including header, footer, and margins.
///
@@ -1166,10 +1169,9 @@ namespace FlaxEditor.Surface
if (base.OnMouseDown(location, button))
return true;
+ _mouseDownMousePosition = location;
if (button == MouseButton.Left && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location))
return true;
- if (button == MouseButton.Right)
- mouseDownMousePosition = Input.Mouse.Position;
return false;
}
@@ -1180,18 +1182,22 @@ namespace FlaxEditor.Surface
if (base.OnMouseUp(location, button))
return true;
- // Close/ delete
- bool canDelete = !Surface.IsConnecting && !Surface.WasSelecting && !Surface.WasMovingSelection;
- if (button == MouseButton.Left && canDelete && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location))
+ if (button == MouseButton.Left)
{
- Surface.Delete(this);
- return true;
+ // Close/delete
+ bool canDelete = !Surface.IsConnecting && !Surface.WasSelecting && !Surface.WasMovingSelection;
+ if (canDelete && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 &&
+ _closeButtonRect.Contains(ref location) && _closeButtonRect.Contains(ref _mouseDownMousePosition))
+ {
+ Surface.Delete(this);
+ return true;
+ }
}
// Secondary Context Menu
if (button == MouseButton.Right)
{
- float distance = Float2.Distance(mouseDownMousePosition, Input.Mouse.Position);
+ float distance = Float2.Distance(_mouseDownMousePosition, location);
if (distance > 2.5f)
return true;
diff --git a/Source/Editor/Surface/VisjectSurface.Connecting.cs b/Source/Editor/Surface/VisjectSurface.Connecting.cs
index 22616f6ad..621f6105f 100644
--- a/Source/Editor/Surface/VisjectSurface.Connecting.cs
+++ b/Source/Editor/Surface/VisjectSurface.Connecting.cs
@@ -287,6 +287,7 @@ namespace FlaxEditor.Surface
// Reset instigator list
_connectionInstigators.Clear();
+ EndMouseCapture();
}
}
}
diff --git a/Source/Editor/Windows/Assets/AssetEditorWindow.cs b/Source/Editor/Windows/Assets/AssetEditorWindow.cs
index 93d6c850c..802686b76 100644
--- a/Source/Editor/Windows/Assets/AssetEditorWindow.cs
+++ b/Source/Editor/Windows/Assets/AssetEditorWindow.cs
@@ -177,7 +177,7 @@ namespace FlaxEditor.Windows.Assets
Save();
}
Editor.Instance.Windows.AddToRestore(this);
- Close();
+ Close(ClosingReason.ScriptsReload);
}
}
diff --git a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
index 7772d59b2..461668783 100644
--- a/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
+++ b/Source/Editor/Windows/Assets/BehaviorTreeWindow.cs
@@ -274,7 +274,7 @@ namespace FlaxEditor.Windows.Assets
base.OnScriptsReloadBegin();
// TODO: impl hot-reload for BT to nicely refresh state (save asset, clear undo/properties, reload surface)
- Close();
+ Close(ClosingReason.ScriptsReload);
}
private void UpdateKnowledge()
diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs
index 3c721c09e..9cc1a6ab5 100644
--- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs
+++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs
@@ -148,7 +148,7 @@ namespace FlaxEditor.Windows.Assets
protected override void OnScriptsReloadBegin()
{
base.OnScriptsReloadBegin();
- Close();
+ Close(ClosingReason.ScriptsReload);
}
///
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs
index 06e007a17..da41b88c5 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.cs
@@ -366,7 +366,7 @@ namespace FlaxEditor.Windows.Assets
_viewport.Prefab = null;
_undo?.Clear(); // TODO: maybe don't clear undo?
- Close();
+ Close(ClosingReason.ScriptsReload);
}
private void OnUndoEvent(IUndoAction action)
diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index 8c3793627..4032e0aff 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Xml;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.GUI.Docking;
using FlaxEditor.GUI.Input;
using FlaxEditor.Modules;
using FlaxEditor.Options;
@@ -132,6 +133,8 @@ namespace FlaxEditor.Windows
private float _gameStartTime;
private GUI.Docking.DockState _maximizeRestoreDockState;
private GUI.Docking.DockPanel _maximizeRestoreDockTo;
+ private GUI.Docking.DockPanel _maximizeRestoreDockToParent;
+ private float _maximizeRestoreSplitterValue;
private CursorLockMode _cursorLockMode = CursorLockMode.None;
// Viewport scaling variables
@@ -295,7 +298,10 @@ namespace FlaxEditor.Windows
if (value)
{
_maximizeRestoreDockTo = _dockedTo;
- _maximizeRestoreDockState = _dockedTo.TryGetDockState(out _);
+ _maximizeRestoreDockToParent = _dockedTo.ParentDockPanel;
+ _maximizeRestoreDockState = _dockedTo.TryGetDockState(out _maximizeRestoreSplitterValue);
+ if (_dockedTo.Tabs.Count > 1)
+ _maximizeRestoreDockState = DockState.DockFill;
if (_maximizeRestoreDockState != GUI.Docking.DockState.Float)
{
var monitorBounds = Platform.GetMonitorBounds(PointToScreen(Size * 0.5f));
@@ -309,9 +315,11 @@ namespace FlaxEditor.Windows
// Restore
if (rootWindow != null)
rootWindow.Restore();
- if (_maximizeRestoreDockTo != null && _maximizeRestoreDockTo.IsDisposing)
- _maximizeRestoreDockTo = null;
- Show(_maximizeRestoreDockState, _maximizeRestoreDockTo);
+ var dockTo = _maximizeRestoreDockTo;
+ if (dockTo != null && dockTo.IsDisposing)
+ dockTo = _maximizeRestoreDockToParent;
+ if (_dockedTo != dockTo)
+ Show(_maximizeRestoreDockState, dockTo, splitterValue: _maximizeRestoreSplitterValue);
}
}
}
@@ -331,6 +339,9 @@ namespace FlaxEditor.Windows
{
IsFloating = true;
var rootWindow = RootWindow;
+ var floatWin = rootWindow.GetChild();
+ if (floatWin != null && floatWin.ShowDecorations)
+ floatWin.ShowDecorations = false;
var monitorBounds = Platform.GetMonitorBounds(rootWindow.RootWindow.Window.ClientPosition);
rootWindow.Window.Position = monitorBounds.Location;
rootWindow.Window.SetBorderless(true);
@@ -338,6 +349,9 @@ namespace FlaxEditor.Windows
}
else
{
+ var floatWin = RootWindow.GetChild();
+ if (floatWin != null && !floatWin.ShowDecorations && Utilities.Utils.UseCustomWindowDecorations())
+ floatWin.ShowDecorations = true;
IsFloating = false;
}
}
@@ -658,9 +672,10 @@ namespace FlaxEditor.Windows
IsBorderless = false;
Cursor = CursorType.Default;
Screen.CursorLock = CursorLockMode.None;
- if (Screen.MainWindow.IsMouseTracking)
- Screen.MainWindow.EndTrackingMouse();
- RootControl.GameRoot.EndMouseCapture();
+ var mainWindow = Screen.MainWindow;
+ if (mainWindow != null && mainWindow.IsMouseTracking)
+ mainWindow.EndTrackingMouse();
+ RootControl.GameRoot?.EndMouseCapture();
}
///
@@ -899,7 +914,7 @@ namespace FlaxEditor.Windows
///
public void FocusGameViewport()
{
- if (!IsDocked)
+ if (ParentDockPanel == null)
{
ShowFloating();
}
@@ -1004,7 +1019,7 @@ namespace FlaxEditor.Windows
Screen.CursorVisible = true;
Screen.CursorLock = CursorLockMode.None;
- if (Editor.IsPlayMode && IsDocked && IsSelected && RootWindow.FocusedControl == null)
+ if (Editor.IsPlayMode && ParentDockPanel != null && IsSelected && RootWindow.FocusedControl == null)
{
// Game UI cleared focus so regain it to maintain UI navigation just like game window does
FlaxEngine.Scripting.InvokeOnUpdate(Focus);
diff --git a/Source/Engine/Content/Storage/ContentStorageManager.cpp b/Source/Engine/Content/Storage/ContentStorageManager.cpp
index 33a16e031..5656fd647 100644
--- a/Source/Engine/Content/Storage/ContentStorageManager.cpp
+++ b/Source/Engine/Content/Storage/ContentStorageManager.cpp
@@ -122,11 +122,8 @@ FlaxStorageReference ContentStorageManager::EnsureAccess(const StringView& path)
// Note: because we want to create new storage package it may exists.
// So let's check if any storage container is referencing that location and try to close it.
auto storage = TryGetStorage(path);
- if (storage && storage->IsLoaded())
- {
- LOG(Info, "File \'{0}\' is in use. Trying to release handle to it.", path);
- storage->CloseFileHandles();
- }
+ if (storage && storage->IsLoaded() && storage->CloseFileHandles())
+ LOG(Warning, "Cannot release content storage handle for '{0}'.", path);
return storage;
}
diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp
index 4eaea48f8..b7be69468 100644
--- a/Source/Engine/Content/Storage/FlaxStorage.cpp
+++ b/Source/Engine/Content/Storage/FlaxStorage.cpp
@@ -1438,10 +1438,6 @@ FileReadStream* FlaxStorage::OpenFile()
bool FlaxStorage::CloseFileHandles()
{
- // Guard the whole process so if new thread wants to lock the chunks will need to wait for this to end
- Platform::InterlockedIncrement(&_isUnloadingData);
- SCOPE_EXIT{ Platform::InterlockedDecrement(&_isUnloadingData); };
-
if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0)
return false; // Early out when no files are opened
PROFILE_CPU();
@@ -1469,7 +1465,13 @@ bool FlaxStorage::CloseFileHandles()
}
}
}
- waitTime = 100;
+
+ // Guard the whole process so if new thread wants to lock the chunks will need to wait for this to end
+ Platform::InterlockedIncrement(&_isUnloadingData);
+ SCOPE_EXIT{ Platform::InterlockedDecrement(&_isUnloadingData); };
+
+ // Wait for chunks lock again (with larger timeout for longer tasks)
+ waitTime = 1000;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
Platform::Sleep(1);
if (Platform::AtomicRead(&_chunksLock) != 0)
diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
index cb8688dcf..d9f5729f9 100644
--- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs
+++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs
@@ -901,20 +901,21 @@ namespace FlaxEngine.Interop
Assembly assembly;
#if FLAX_EDITOR
- // Load assembly from loaded bytes to prevent file locking in Editor
- var assemblyBytes = File.ReadAllBytes(assemblyPath);
- using MemoryStream stream = new MemoryStream(assemblyBytes);
+ // Load assembly with stream to prevent runtime from locking the assembly file
+ using FileStream stream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read);
var pdbPath = Path.ChangeExtension(assemblyPath, "pdb");
if (File.Exists(pdbPath))
{
// Load including debug symbols
- using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open);
+ using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open, FileAccess.Read);
assembly = scriptingAssemblyLoadContext.LoadFromStream(stream, pdbStream);
}
else
{
assembly = scriptingAssemblyLoadContext.LoadFromStream(stream);
}
+
+ // TODO: Use new .NET 11 AssemblyLoadContext.SetAssemblyLocationOverride to specify correct Assembly.Location
#else
// Load assembly from file
assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(assemblyPath);
diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp
index 27959b727..938fef145 100644
--- a/Source/Engine/Graphics/Models/Mesh.cpp
+++ b/Source/Engine/Graphics/Models/Mesh.cpp
@@ -197,7 +197,7 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* ve
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
- return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, normals, tangents, uvs, colors);
+ return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R32_UInt, vertices, triangles, normals, tangents, uvs, colors);
}
bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer)
diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp
index 58ab94abd..bd315decd 100644
--- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp
+++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp
@@ -305,7 +305,7 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Flo
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
- return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors);
+ return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R32_UInt, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors);
}
void SkinnedMesh::Draw(const RenderContext& renderContext, const SkinnedMeshBones& pose, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals, DrawPass drawModes, float perInstanceRandom, int8 sortOrder, uint8 stencilValue) const
diff --git a/Source/Engine/Platform/Base/Enums.h b/Source/Engine/Platform/Base/Enums.h
index df83ec0cb..89c1f614b 100644
--- a/Source/Engine/Platform/Base/Enums.h
+++ b/Source/Engine/Platform/Base/Enums.h
@@ -28,6 +28,11 @@ API_ENUM() enum class ClosingReason
/// The close event.
///
CloseEvent,
+
+ ///
+ /// The scripts reload event.
+ ///
+ ScriptsReload,
};
///
diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp
index d147225b4..47a9c5306 100644
--- a/Source/Engine/ShadersCompilation/ShadersCompilation.cpp
+++ b/Source/Engine/ShadersCompilation/ShadersCompilation.cpp
@@ -26,6 +26,8 @@
#if USE_EDITOR
#define COMPILE_WITH_ASSETS_IMPORTER 1 // Hack to use shaders importing in this module
#include "Engine/ContentImporters/AssetsImportingManager.h"
+#include "Engine/Content/Storage/ContentStorageManager.h"
+#include "Engine/Utilities/Encryption.h"
#include "Engine/Platform/FileSystemWatcher.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/File.h"
@@ -499,6 +501,37 @@ String ShadersCompilation::CompactShaderPath(StringView path)
#if USE_EDITOR
+bool ShadersCompilation::IsShaderSourceAssetUpToDate(const StringView& sourcePath, const StringView& assetPath)
+{
+ PROFILE_CPU();
+ StringAnsi source;
+ if (File::ReadAllText(sourcePath, source))
+ return false;
+ if (!source.HasChars() || source[source.Length() - 1] != '\n')
+ source.Append('\n');
+
+ const auto storage = ContentStorageManager::GetStorage(assetPath);
+ AssetInitData data;
+ if (!storage
+ || storage->GetEntriesCount() != 1
+ || storage->GetEntry(0).TypeName != Shader::TypeName
+ || storage->LoadAssetHeader(0, data)
+ || data.SerializedVersion != Shader::SerializedVersion)
+ return false;
+
+ FlaxChunk* sourceChunk = data.Header.Chunks[SHADER_FILE_CHUNK_SOURCE];
+ if (!sourceChunk || storage->LoadAssetChunk(sourceChunk) || !sourceChunk->Data.IsValid())
+ return false;
+
+ BytesContainer embeddedSource;
+ embeddedSource.Copy(sourceChunk->Data);
+ if (embeddedSource.Length() != source.Length() + 1)
+ return false;
+ Encryption::DecryptBytes(embeddedSource.Get(), embeddedSource.Length());
+ embeddedSource.Get()[embeddedSource.Length() - 1] = 0;
+ return Platform::MemoryCompare(embeddedSource.Get(), source.Get(), source.Length()) == 0;
+}
+
namespace
{
Array ShadersSourcesWatchers;
@@ -515,6 +548,13 @@ namespace
return result;
}
+ bool ImportShaderIfChanged(const StringView& sourcePath, const StringView& assetPath, Guid& assetId)
+ {
+ if (ShadersCompilation::IsShaderSourceAssetUpToDate(sourcePath, assetPath))
+ return false;
+ return AssetsImportingManager::Import(sourcePath, assetPath, assetId);
+ }
+
void OnWatcherShadersEvent(const String& path, FileSystemAction action)
{
if (action == FileSystemAction::Delete || !path.EndsWith(TEXT(".shader")))
@@ -538,7 +578,7 @@ namespace
const String name = StringUtils::GetPathWithoutExtension(localPath);
const String outputPath = shadersAssetsPath / name + ASSET_FILES_EXTENSION_WITH_DOT;
Guid id = GetShaderAssetId(name);
- AssetsImportingManager::ImportIfEdited(path, outputPath, id);
+ ImportShaderIfChanged(path, outputPath, id);
}
void RegisterShaderWatchers(const ProjectInfo* project, HashSet& projects)
@@ -567,7 +607,7 @@ namespace
const String name = StringUtils::GetPathWithoutExtension(localPath);
const String outputPath = shadersAssetsPath / name + ASSET_FILES_EXTENSION_WITH_DOT;
Guid id = GetShaderAssetId(name);
- AssetsImportingManager::ImportIfEdited(path, outputPath, id);
+ ImportShaderIfChanged(path, outputPath, id);
}
}
diff --git a/Source/Engine/ShadersCompilation/ShadersCompilation.h b/Source/Engine/ShadersCompilation/ShadersCompilation.h
index b32c9f51a..af0a9ec61 100644
--- a/Source/Engine/ShadersCompilation/ShadersCompilation.h
+++ b/Source/Engine/ShadersCompilation/ShadersCompilation.h
@@ -47,6 +47,16 @@ public:
// Compacts the full shader file path into portable format with project name prefix such as './/ShaderFile.hlsl'.
static String CompactShaderPath(StringView path);
+#if USE_EDITOR
+ ///
+ /// Checks whether a shader asset embeds the current source file contents.
+ ///
+ /// The shader source file path.
+ /// The shader asset file path.
+ /// True when the embedded source matches, otherwise false.
+ static bool IsShaderSourceAssetUpToDate(const StringView& sourcePath, const StringView& assetPath);
+#endif
+
private:
static ShaderCompiler* RequestCompiler(ShaderProfile profile, PlatformType platform);
static void FreeCompiler(ShaderCompiler* compiler);
diff --git a/Source/Engine/Tests/TestContainerControl.cs b/Source/Engine/Tests/TestContainerControl.cs
index 3b3c4856a..ce2b4503e 100644
--- a/Source/Engine/Tests/TestContainerControl.cs
+++ b/Source/Engine/Tests/TestContainerControl.cs
@@ -26,6 +26,29 @@ namespace FlaxEngine.Tests
}
}
+ private sealed class RemovingControl : MyControl
+ {
+ private readonly Control[] _controlsToRemove;
+
+ public RemovingControl(float x, float y, float width, float height, params Control[] controlsToRemove)
+ : base(x, y, width, height)
+ {
+ _controlsToRemove = controlsToRemove;
+ }
+
+ public override void OnMouseEnter(Float2 location)
+ {
+ for (int i = 0; i < _controlsToRemove.Length; i++)
+ {
+ var control = _controlsToRemove[i];
+ if (control.Parent == Parent)
+ control.Parent = null;
+ }
+
+ base.OnMouseEnter(location);
+ }
+ }
+
[Test]
public void TestChildren()
{
@@ -53,6 +76,25 @@ namespace FlaxEngine.Tests
Assert.AreEqual(cc1.GetChildAt(new Vector2(15, 5)), cc2);
Assert.AreEqual(cc1.GetChildAtRecursive(new Vector2(35, 25)), c3);
}
+
+ [Test]
+ public void TestMouseMoveAllowsChildrenRemoval()
+ {
+ var container = new MyContainerControl(0, 0, 100, 100);
+ var first = new MyControl(0, 0, 100, 100);
+ var second = new MyControl(0, 0, 100, 100);
+ var removing = new RemovingControl(0, 0, 100, 100, first, second);
+ container.AddChild(first);
+ container.AddChild(second);
+ container.AddChild(removing);
+
+ // The top-most child removes multiple siblings during input dispatch.
+ // Traversal must not use the now-stale next index.
+ container.OnMouseMove(new Float2(50, 50));
+
+ Assert.AreEqual(1, container.ChildrenCount);
+ Assert.AreEqual(removing, container.GetChild(0));
+ }
}
}
#endif
diff --git a/Source/Engine/Tests/TestShaderSourceSync.cpp b/Source/Engine/Tests/TestShaderSourceSync.cpp
new file mode 100644
index 000000000..96bad398b
--- /dev/null
+++ b/Source/Engine/Tests/TestShaderSourceSync.cpp
@@ -0,0 +1,42 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#include "Engine/Core/ScopeExit.h"
+#include "Engine/Core/Types/DataContainer.h"
+#include "Engine/Engine/Globals.h"
+#include "Engine/Platform/File.h"
+#include "Engine/Platform/FileSystem.h"
+#include "Engine/ShadersCompilation/ShadersCompilation.h"
+#include
+
+#if COMPILE_WITH_SHADER_COMPILER && USE_EDITOR
+
+TEST_CASE("Shader source asset synchronization ignores timestamps")
+{
+ const String sourcePath = Globals::StartupFolder / TEXT("Source/Shaders/VolumetricFog.shader");
+ const String assetPath = Globals::EngineContentFolder / TEXT("Shaders/VolumetricFog.flax");
+ REQUIRE(FileSystem::FileExists(sourcePath));
+ REQUIRE(FileSystem::FileExists(assetPath));
+ CHECK(ShadersCompilation::IsShaderSourceAssetUpToDate(sourcePath, assetPath));
+
+ const String tempRoot = Globals::TemporaryFolder / (TEXT("ShaderSourceSync-") + Guid::New().ToString(Guid::FormatType::N));
+ REQUIRE(!FileSystem::CreateDirectory(tempRoot));
+ SCOPE_EXIT
+ {
+ FileSystem::DeleteDirectory(tempRoot, true);
+ };
+
+ DataContainer assetData;
+ StringAnsi modifiedSource;
+ REQUIRE(!File::ReadAllBytes(assetPath, assetData));
+ REQUIRE(!File::ReadAllText(sourcePath, modifiedSource));
+ modifiedSource.Append("// Deliberately different source\n");
+
+ const String tempSourcePath = tempRoot / TEXT("VolumetricFog.shader");
+ const String tempAssetPath = tempRoot / TEXT("VolumetricFog.flax");
+ REQUIRE(!File::WriteAllBytes(tempSourcePath, modifiedSource.Get(), modifiedSource.Length()));
+ REQUIRE(!File::WriteAllBytes(tempAssetPath, assetData.Get(), assetData.Length()));
+ CHECK_FALSE(FileSystem::GetFileLastEditTime(tempSourcePath) > FileSystem::GetFileLastEditTime(tempAssetPath));
+ CHECK_FALSE(ShadersCompilation::IsShaderSourceAssetUpToDate(tempSourcePath, tempAssetPath));
+}
+
+#endif
diff --git a/Source/Engine/Tests/Tests.Build.cs b/Source/Engine/Tests/Tests.Build.cs
index 1056d1002..d55456176 100644
--- a/Source/Engine/Tests/Tests.Build.cs
+++ b/Source/Engine/Tests/Tests.Build.cs
@@ -21,6 +21,7 @@ public class Tests : EngineModule
base.Setup(options);
options.PrivateDependencies.Add("ModelTool");
+ options.PrivateDependencies.Add("ShadersCompilation");
}
///
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
index d3ef6a016..ee2b76d9f 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
@@ -20,26 +20,29 @@
#include
#include
#include
-#include
#include
#include
-class AssimpLogStream : public Assimp::LogStream
+class AssimpLogger final : public Assimp::Logger
{
public:
- AssimpLogStream()
+ AssimpLogger()
+ : Logger(NORMAL)
{
- Assimp::DefaultLogger::create("");
- Assimp::DefaultLogger::get()->attachStream(this);
}
- ~AssimpLogStream()
+ bool attachStream(Assimp::LogStream*, unsigned int) override
{
- Assimp::DefaultLogger::get()->detachStream(this);
- Assimp::DefaultLogger::kill();
+ return false;
}
- void write(const char* message) override
+ bool detachStream(Assimp::LogStream*, unsigned int) override
+ {
+ return false;
+ }
+
+private:
+ static void Write(const Char* type, const char* message)
{
String s(message);
if (s.Length() <= 0)
@@ -52,10 +55,49 @@ public:
else if (c >= 255)
c = '?';
}
- LOG(Info, "[Assimp]: {0}", s);
+ LOG(Info, "[Assimp]: {0}: {1}", type, s);
+ }
+
+ void OnDebug(const char* message) override
+ {
+ if (m_Severity >= DEBUGGING)
+ Write(TEXT("Debug"), message);
+ }
+
+ void OnVerboseDebug(const char* message) override
+ {
+ if (m_Severity >= VERBOSE)
+ Write(TEXT("Debug"), message);
+ }
+
+ void OnInfo(const char* message) override
+ {
+ Write(TEXT("Info"), message);
+ }
+
+ void OnWarn(const char* message) override
+ {
+ Write(TEXT("Warn"), message);
+ }
+
+ void OnError(const char* message) override
+ {
+ Write(TEXT("Error"), message);
}
};
+Assimp::Logger* GetAssimpLogger()
+{
+ static Assimp::Logger* logger = []()
+ {
+ auto result = new AssimpLogger();
+ Assimp::DefaultLogger::set(result);
+ LOG(Info, "Assimp {0}.{1}.{2}", aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision());
+ return result;
+ }();
+ return logger;
+}
+
Float2 ToFloat2(const aiVector2D& v)
{
return Float2(v.x, v.y);
@@ -148,7 +190,6 @@ struct AssimpBone
struct AssimpImporterData
{
Assimp::Importer AssimpImporter;
- AssimpLogStream AssimpLogStream;
const String Path;
const aiScene* Scene = nullptr;
const ModelTool::Options& Options;
@@ -705,12 +746,7 @@ void ImportAnimation(int32 index, ModelData& data, AssimpImporterData& importerD
bool ModelTool::ImportDataAssimp(const String& path, ModelData& data, Options& options, String& errorMsg)
{
- static bool AssimpInited = false;
- if (!AssimpInited)
- {
- AssimpInited = true;
- LOG(Info, "Assimp {0}.{1}.{2}", aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision());
- }
+ GetAssimpLogger();
bool importMeshes = EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry);
bool importAnimations = EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations);
AssimpImporterData context(path, options);
diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs
index 55fcecf80..aa0078c19 100644
--- a/Source/Engine/UI/GUI/ContainerControl.cs
+++ b/Source/Engine/UI/GUI/ContainerControl.cs
@@ -910,7 +910,7 @@ namespace FlaxEngine.GUI
return false;
}
}
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible)
@@ -928,7 +928,7 @@ namespace FlaxEngine.GUI
public override void OnMouseEnter(Float2 location)
{
// Check all children collisions with mouse and fire events for them
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -948,7 +948,7 @@ namespace FlaxEngine.GUI
public override void OnMouseMove(Float2 location)
{
// Check all children collisions with mouse and fire events for them
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -998,7 +998,7 @@ namespace FlaxEngine.GUI
public override bool OnMouseWheel(Float2 location, float delta)
{
// Check all children collisions with mouse and fire events for them
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1019,7 +1019,7 @@ namespace FlaxEngine.GUI
public override bool OnMouseDown(Float2 location, MouseButton button)
{
// Check all children collisions with mouse and fire events for them
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1040,7 +1040,7 @@ namespace FlaxEngine.GUI
public override bool OnMouseUp(Float2 location, MouseButton button)
{
// Check all children collisions with mouse and fire events for them
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1061,7 +1061,7 @@ namespace FlaxEngine.GUI
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
// Check all children collisions with mouse and fire events for them
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1096,7 +1096,7 @@ namespace FlaxEngine.GUI
///
public override void OnTouchEnter(Float2 location, int pointerId)
{
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled && !child.IsTouchPointerOver(pointerId))
@@ -1114,7 +1114,7 @@ namespace FlaxEngine.GUI
///
public override bool OnTouchDown(Float2 location, int pointerId)
{
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1139,7 +1139,7 @@ namespace FlaxEngine.GUI
///
public override void OnTouchMove(Float2 location, int pointerId)
{
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1168,7 +1168,7 @@ namespace FlaxEngine.GUI
///
public override bool OnTouchUp(Float2 location, int pointerId)
{
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled && child.IsTouchPointerOver(pointerId))
@@ -1250,7 +1250,7 @@ namespace FlaxEngine.GUI
var result = base.OnDragEnter(ref location, data);
// Check all children collisions with mouse and fire events for them
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1275,7 +1275,7 @@ namespace FlaxEngine.GUI
var result = base.OnDragMove(ref location, data);
// Check all children collisions with mouse and fire events for them
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
@@ -1333,7 +1333,7 @@ namespace FlaxEngine.GUI
var result = base.OnDragDrop(ref location, data);
// Check all children collisions with mouse and fire events for them
- for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
+ for (int i = _children.Count - 1; i >= 0 && i < _children.Count; i--)
{
var child = _children[i];
if (child.Visible && child.Enabled)
diff --git a/Source/Shaders/Editor/Grid.shader b/Source/Shaders/Editor/Grid.shader
index e18cfef90..c4fc7c827 100644
--- a/Source/Shaders/Editor/Grid.shader
+++ b/Source/Shaders/Editor/Grid.shader
@@ -42,6 +42,7 @@ META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true)
VertexOutput VS_Grid(ModelInput input)
{
VertexOutput output;
+ input.Position.xyz += float3(ViewPos.x, 0, ViewPos.z);
output.WorldPosition = input.Position.xyz + ViewOrigin;
float3 geoPosition = input.Position.xyz - float3(0, ViewOrigin.y, 0);
output.Position = mul(float4(geoPosition, 1), ViewProjectionMatrix);
diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs
index 0d2889368..a0c518870 100644
--- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs
+++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs
@@ -136,7 +136,7 @@ namespace Flax.Build
///
/// The maximum SDK version.
///
- public static Version MaximumVersion => new Version(10, 0);
+ public static Version MaximumVersion => new Version(8, 0);
///
public override TargetPlatform[] Platforms
diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
index 9cca0dfa6..f158078f5 100644
--- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
+++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs
@@ -110,6 +110,11 @@ namespace Flax.Build.NativeCpp
return libFolder;
// Try to find nearest framework folder
+ if (Framework.StartsWith("netcoreapp"))
+ {
+ var version = System.Version.Parse(Framework.Substring(10));
+ return string.Empty;
+ }
if (Framework.StartsWith("net"))
{
var baseVersion = int.Parse(Framework.Substring(3, Framework.IndexOf('.') - 3));
@@ -144,7 +149,10 @@ namespace Flax.Build.NativeCpp
var dlls = Directory.Exists(libFolder) ? Directory.GetFiles(libFolder, "*.dll", SearchOption.TopDirectoryOnly) : [];
if (dlls.Length == 0)
{
- Log.Error($"Missing NuGet package \"{Name}, {Version}, {Framework}\" binaries (folder: {libFolder})");
+ if (!File.Exists(Path.Combine(libFolder, "_._"))) // Skip error for packages without a binary (eg. Microsoft.NET.Test.Sdk)
+ {
+ Log.Error($"Missing NuGet package \"{Name}, {Version}, {Framework}\" binaries (folder: {libFolder})");
+ }
return string.Empty;
}
return dlls[0];
diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs
index ffc95e047..e99b5b328 100644
--- a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs
+++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs
@@ -330,9 +330,7 @@ namespace Flax.Build.Projects.VisualStudio
foreach (var analyzer in configuration.TargetBuildOptions.ScriptingAPI.Analyzers)
{
csProjectFileContent.AppendLine(string.Format(" ", configuration.Name));
- csProjectFileContent.AppendLine(string.Format(" ", Path.GetFileNameWithoutExtension(analyzer)));
- csProjectFileContent.AppendLine(string.Format(" {0}", Utilities.MakePathRelativeTo(analyzer, projectDirectory).Replace('/', '\\')));
- csProjectFileContent.AppendLine(" ");
+ csProjectFileContent.AppendLine(string.Format(" ", Utilities.MakePathRelativeTo(analyzer, projectDirectory).Replace('/', '\\')));
csProjectFileContent.AppendLine(" ");
}