diff --git a/src/Editor/Ghost.Editor/App.xaml.cs b/src/Editor/Ghost.Editor/App.xaml.cs
index cbc36df..760f966 100644
--- a/src/Editor/Ghost.Editor/App.xaml.cs
+++ b/src/Editor/Ghost.Editor/App.xaml.cs
@@ -53,6 +53,50 @@ public partial class App : Application
}
}
+ ///
+ /// Attempts to tear off a tab into a new window.
+ ///
+ /// The collection to remove the item from.
+ /// The item to tear off.
+ /// Optional callback after successful tear-off.
+ /// A result indicating success or failure.
+ internal static Result TryTearOffTab(System.Collections.IList sourceItems, object tabItem, Action? onSuccess = null)
+ {
+ int originalIndex = sourceItems.IndexOf(tabItem);
+ if (originalIndex == -1)
+ {
+ return Result.Failure("Item not found in source collection.");
+ }
+
+ object? originalSelection = null;
+ // Try to capture selection if the source is a DockPanelNode or similar
+ // For now, we'll just handle the collection mutation.
+
+ try
+ {
+ sourceItems.Remove(tabItem);
+
+ try
+ {
+ CreateAndShowDockWindow(tabItem);
+ onSuccess?.Invoke();
+ return Result.Success();
+ }
+ catch (Exception ex)
+ {
+ // Rollback
+ sourceItems.Insert(originalIndex, tabItem);
+ Logger.LogError(ex);
+ return Result.Failure($"Failed to create tear-off window: {ex.Message}");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex);
+ return Result.Failure($"Failed to remove item from source: {ex.Message}");
+ }
+ }
+
///
/// Creates, registers, and shows a new DockWindow for a torn-off tab.
///
diff --git a/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs b/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs
index 794968e..4760afe 100644
--- a/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs
+++ b/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs
@@ -1,6 +1,7 @@
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
+using Ghost.Core;
using Ghost.Editor.Core.Controls.Internal.Docking;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -308,41 +309,27 @@ public sealed partial class DockLayout : Control
return;
}
- int originalIndex = _sourceNode.Items.IndexOf(_draggedItem);
object? originalSelection = _sourceNode.SelectedItem;
- if (originalIndex == -1)
+ App.TryTearOffTab(_sourceNode.Items, _draggedItem, () =>
{
- ClearDragOperationState();
- return;
- }
-
- try
- {
- // Remove from current tree
- if (_sourceNode.Items.Remove(_draggedItem))
+ try
{
- try
- {
- // Raise event to let the host handle window creation
- TabTornOff.Invoke(this, new TabTornOffEventArgs(_draggedItem));
-
- // Only cleanup if the tear-off was successful (didn't throw)
- DockMutationEngine.CleanupEmptyNodes(_sourceNode);
- }
- catch (Exception ex)
- {
- // Rollback: Re-insert the item at original position if the tear-off handler fails
- _sourceNode.Items.Insert(originalIndex, _draggedItem);
- _sourceNode.SelectedItem = originalSelection;
- Logger.LogError(ex);
- }
+ // Raise event to let the host handle window creation
+ TabTornOff.Invoke(this, new TabTornOffEventArgs(_draggedItem));
+
+ // Only cleanup if the tear-off was successful
+ DockMutationEngine.CleanupEmptyNodes(_sourceNode);
}
- }
- finally
- {
- ClearDragOperationState();
- }
+ catch (Exception ex)
+ {
+ // If the event handler fails, we still need to cleanup or restore selection
+ _sourceNode.SelectedItem = originalSelection;
+ throw; // Re-throw to trigger rollback in TryTearOffTab
+ }
+ });
+
+ ClearDragOperationState();
}
}
diff --git a/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs b/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs
index 00b4162..9e69aa6 100644
--- a/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs
+++ b/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs
@@ -43,28 +43,18 @@ internal sealed partial class EngineEditorWindow : WindowEx
// For static tabs in EngineEditorWindow, we remove the item from TabItems
if (sender.TabItems.Contains(args.Item))
{
- int originalIndex = sender.TabItems.IndexOf(args.Item);
object? originalSelection = sender.SelectedItem;
-
- try
+
+ App.TryTearOffTab(sender.TabItems, args.Item, () =>
{
- sender.TabItems.Remove(args.Item);
-
- try
- {
- App.CreateAndShowDockWindow(args.Item);
- }
- catch (Exception ex)
- {
- // Rollback: Re-insert the item at original position if the tear-off fails
- sender.TabItems.Insert(originalIndex, args.Item);
- sender.SelectedItem = originalSelection;
- Logger.LogError(ex);
- }
- }
- catch (Exception ex)
+ // If we need to do anything else on success
+ });
+
+ // Note: If TryTearOffTab fails, it will have already re-inserted the item.
+ // We might want to restore selection if it was the selected item.
+ if (sender.TabItems.Contains(args.Item) && sender.SelectedItem == null)
{
- Logger.LogError(ex);
+ sender.SelectedItem = originalSelection;
}
}
}