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/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs
index 8cde79ce8..20ce85b0a 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;
@@ -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();
diff --git a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
index b47d76bdd..c7481d338 100644
--- a/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
+++ b/Source/Editor/GUI/Docking/FloatWindowDockPanel.cs
@@ -89,6 +89,13 @@ namespace FlaxEditor.GUI.Docking
}
}
+ internal void UnlinkWindow()
+ {
+ _window?.Window.Closing -= OnClosing;
+ _window?.Window.LeftButtonHit -= OnLeftButtonHit;
+ _window = null;
+ }
+
///
protected override void PerformLayoutBeforeChildren()
{
@@ -191,9 +198,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 +249,7 @@ namespace FlaxEditor.GUI.Docking
{
_masterPanel?.FloatingPanels.Remove(this);
+ UnlinkWindow();
base.OnDestroy();
}
}
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index 1a03c7c57..8074ab814 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.
@@ -822,10 +814,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)
@@ -836,62 +832,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);
@@ -908,75 +907,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)
@@ -985,6 +933,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/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 4d9c04942..696f63b25 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/Engine/Platform/Base/Enums.h b/Source/Engine/Platform/Base/Enums.h
index 4a903ad19..4af4df785 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,
};
///