Keep asset windows alive during scripts reload
Recreated asset editor windows are now reattached to the native window after scripts reload to keep the current state and Z-order of the windows untouched on platforms where window state can't be changed without user interaction (Wayland).
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reattaches the window control to existing floating window.
|
||||
/// </summary>
|
||||
/// <param name="windowHandle">The window handle.</param>
|
||||
/// <param name="state">Initial window state.</param>
|
||||
/// <param name="toDock">The panel to dock to, if any.</param>
|
||||
/// <param name="autoSelect">Only used if <paramref name="toDock"/> is set. If true the window will be selected after docking it.</param>
|
||||
/// <param name="splitterValue">The splitter value to use if toDock is not null. If not specified, a default value will be used.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reattaches the window control to existing floating window.
|
||||
/// </summary>
|
||||
/// <param name="windowHandle">The window handle.</param>
|
||||
/// <param name="state">Initial window state.</param>
|
||||
/// <param name="toDock">Panel to dock to it.</param>
|
||||
/// <param name="autoSelect">Only used if <paramref name="toDock"/> is set. If true the window will be selected after docking it.</param>
|
||||
/// <param name="splitterValue">Only used if <paramref name="toDock"/> is set. The splitter value to use. If not specified, a default value will be used.</param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the window.
|
||||
/// </summary>
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -89,6 +89,13 @@ namespace FlaxEditor.GUI.Docking
|
||||
}
|
||||
}
|
||||
|
||||
internal void UnlinkWindow()
|
||||
{
|
||||
_window?.Window.Closing -= OnClosing;
|
||||
_window?.Window.LeftButtonHit -= OnLeftButtonHit;
|
||||
_window = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<WindowRestoreData> _restoreWindows = new List<WindowRestoreData>();
|
||||
private readonly Dictionary<Guid, WindowRestoreData> _restoreWindows = new();
|
||||
|
||||
/// <summary>
|
||||
/// 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<Guid, AssetEditorWindow>();
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
Save();
|
||||
}
|
||||
Editor.Instance.Windows.AddToRestore(this);
|
||||
Close();
|
||||
Close(ClosingReason.ScriptsReload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
protected override void OnScriptsReloadBegin()
|
||||
{
|
||||
base.OnScriptsReloadBegin();
|
||||
Close();
|
||||
Close(ClosingReason.ScriptsReload);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -28,6 +28,11 @@ API_ENUM() enum class ClosingReason
|
||||
/// The close event.
|
||||
/// </summary>
|
||||
CloseEvent,
|
||||
|
||||
/// <summary>
|
||||
/// The scripts reload event.
|
||||
/// </summary>
|
||||
ScriptsReload,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user