diff --git a/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs b/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs
index a349551..d045357 100644
--- a/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs
+++ b/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs
@@ -306,20 +306,23 @@ public sealed partial class DockLayout : Control
{
DockMutationEngine.CleanupEmptyNodes(_sourceNode);
- // Raise event to let the host handle window creation
- TabTornOff?.Invoke(this, new TabTornOffEventArgs(_draggedItem));
+ try
+ {
+ // Raise event to let the host handle window creation
+ TabTornOff?.Invoke(this, new TabTornOffEventArgs(_draggedItem));
+ }
+ catch (Exception ex)
+ {
+ // Rollback: Re-insert the item if the tear-off handler fails
+ _sourceNode.Items.Add(_draggedItem);
+ Logger.LogError($"Failed to tear off tab: {ex.Message}");
+ }
}
ClearDragOperationState();
}
}
- public class TabTornOffEventArgs : EventArgs
- {
- public object TabContent { get; }
- public TabTornOffEventArgs(object tabContent) => TabContent = tabContent;
- }
-
private object? _draggedItem;
private DockPanelNode? _sourceNode;
private DockPosition _currentDropPosition = DockPosition.None;
diff --git a/src/Editor/Ghost.Editor/View/Controls/TabTornOffEventArgs.cs b/src/Editor/Ghost.Editor/View/Controls/TabTornOffEventArgs.cs
new file mode 100644
index 0000000..0515ea5
--- /dev/null
+++ b/src/Editor/Ghost.Editor/View/Controls/TabTornOffEventArgs.cs
@@ -0,0 +1,17 @@
+namespace Ghost.Editor.View.Controls;
+
+///
+/// Event arguments for the TabTornOff event.
+///
+public sealed class TabTornOffEventArgs : EventArgs
+{
+ ///
+ /// Gets the content of the tab being torn off.
+ ///
+ public object TabContent { get; }
+
+ public TabTornOffEventArgs(object tabContent)
+ {
+ TabContent = tabContent;
+ }
+}
diff --git a/src/Editor/Ghost.Editor/View/Windows/DockWindow.xaml.cs b/src/Editor/Ghost.Editor/View/Windows/DockWindow.xaml.cs
index 2a89b92..8bb1e79 100644
--- a/src/Editor/Ghost.Editor/View/Windows/DockWindow.xaml.cs
+++ b/src/Editor/Ghost.Editor/View/Windows/DockWindow.xaml.cs
@@ -1,4 +1,5 @@
using Ghost.Editor.Core.Controls.Internal.Docking;
+using Ghost.Editor.View.Controls;
using WinUIEx;
namespace Ghost.Editor.View.Windows;
@@ -16,5 +17,13 @@ internal sealed partial class DockWindow : WindowEx
rootGroup.AddChild(panel);
PART_DockLayout.Root = rootGroup;
+ PART_DockLayout.TabTornOff += OnTabTornOff;
+ }
+
+ private void OnTabTornOff(object? sender, TabTornOffEventArgs e)
+ {
+ var newWindow = new DockWindow(e.TabContent);
+ App.AddSecondaryWindow(newWindow);
+ newWindow.Activate();
}
}
diff --git a/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs b/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs
index a0f8668..cb1b0c3 100644
--- a/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs
+++ b/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs
@@ -1,6 +1,7 @@
using Ghost.Editor.Core;
using Ghost.Editor.Core.Contracts;
using Ghost.Editor.Core.Services;
+using Ghost.Editor.View.Controls;
using Ghost.Editor.ViewModels.Windows;
using Windows.ApplicationModel;
using WinUIEx;
@@ -34,6 +35,17 @@ internal sealed partial class EngineEditorWindow : WindowEx
SetTitleBar(PART_TitleBar);
this.CenterOnScreen();
+
+ // Note: DockLayout is not directly in the XAML but created by RenderTree.
+ // However, we can subscribe to the event if we find it or if it's exposed.
+ // Since DockLayout is a TemplatePart or child of a Grid, we might need to wait for it.
+ }
+
+ private void OnTabTornOff(object? sender, TabTornOffEventArgs e)
+ {
+ var newWindow = new DockWindow(e.TabContent);
+ App.AddSecondaryWindow(newWindow);
+ newWindow.Activate();
}
private void MainGrid_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)