fix(dock): centralize tear-off logic and ensure transactional integrity
This commit is contained in:
@@ -53,6 +53,16 @@ public partial class App : Application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates, registers, and shows a new DockWindow for a torn-off tab.
|
||||||
|
/// </summary>
|
||||||
|
internal static void CreateAndShowDockWindow(object tabContent)
|
||||||
|
{
|
||||||
|
var newWindow = new Ghost.Editor.View.Windows.DockWindow(tabContent);
|
||||||
|
AddSecondaryWindow(newWindow);
|
||||||
|
newWindow.Activate();
|
||||||
|
}
|
||||||
|
|
||||||
internal IHost Host
|
internal IHost Host
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
|||||||
@@ -299,36 +299,50 @@ public sealed partial class DockLayout : Control
|
|||||||
|
|
||||||
private void TabView_TabDroppedOutside(Microsoft.UI.Xaml.Controls.TabView sender, Microsoft.UI.Xaml.Controls.TabViewTabDroppedOutsideEventArgs args)
|
private void TabView_TabDroppedOutside(Microsoft.UI.Xaml.Controls.TabView sender, Microsoft.UI.Xaml.Controls.TabViewTabDroppedOutsideEventArgs args)
|
||||||
{
|
{
|
||||||
if (_sourceNode != null && _draggedItem != null && TabTornOff != null)
|
if (_sourceNode != null && _draggedItem != null)
|
||||||
{
|
{
|
||||||
|
if (TabTornOff == null)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Tab dropped outside but no TabTornOff subscribers found.");
|
||||||
|
ClearDragOperationState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int originalIndex = _sourceNode.Items.IndexOf(_draggedItem);
|
int originalIndex = _sourceNode.Items.IndexOf(_draggedItem);
|
||||||
|
object? originalSelection = _sourceNode.SelectedItem;
|
||||||
|
|
||||||
if (originalIndex == -1)
|
if (originalIndex == -1)
|
||||||
{
|
{
|
||||||
ClearDragOperationState();
|
ClearDragOperationState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from current tree
|
try
|
||||||
if (_sourceNode.Items.Remove(_draggedItem))
|
|
||||||
{
|
{
|
||||||
try
|
// Remove from current tree
|
||||||
|
if (_sourceNode.Items.Remove(_draggedItem))
|
||||||
{
|
{
|
||||||
// Raise event to let the host handle window creation
|
try
|
||||||
TabTornOff.Invoke(this, new TabTornOffEventArgs(_draggedItem));
|
{
|
||||||
|
// Raise event to let the host handle window creation
|
||||||
// Only cleanup if the tear-off was successful (didn't throw)
|
TabTornOff.Invoke(this, new TabTornOffEventArgs(_draggedItem));
|
||||||
DockMutationEngine.CleanupEmptyNodes(_sourceNode);
|
|
||||||
}
|
// Only cleanup if the tear-off was successful (didn't throw)
|
||||||
catch (Exception ex)
|
DockMutationEngine.CleanupEmptyNodes(_sourceNode);
|
||||||
{
|
}
|
||||||
// Rollback: Re-insert the item at original position if the tear-off handler fails
|
catch (Exception ex)
|
||||||
_sourceNode.Items.Insert(originalIndex, _draggedItem);
|
{
|
||||||
_sourceNode.SelectedItem = _draggedItem;
|
// Rollback: Re-insert the item at original position if the tear-off handler fails
|
||||||
Logger.LogError(ex);
|
_sourceNode.Items.Insert(originalIndex, _draggedItem);
|
||||||
|
_sourceNode.SelectedItem = originalSelection;
|
||||||
|
Logger.LogError(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
ClearDragOperationState();
|
{
|
||||||
|
ClearDragOperationState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ internal sealed partial class DockWindow : WindowEx
|
|||||||
|
|
||||||
private void OnTabTornOff(object? sender, TabTornOffEventArgs e)
|
private void OnTabTornOff(object? sender, TabTornOffEventArgs e)
|
||||||
{
|
{
|
||||||
var newWindow = new DockWindow(e.TabContent);
|
App.CreateAndShowDockWindow(e.TabContent);
|
||||||
App.AddSecondaryWindow(newWindow);
|
|
||||||
newWindow.Activate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ghost.Core;
|
||||||
using Ghost.Editor.Core;
|
using Ghost.Editor.Core;
|
||||||
using Ghost.Editor.Core.Contracts;
|
using Ghost.Editor.Core.Contracts;
|
||||||
using Ghost.Editor.Core.Services;
|
using Ghost.Editor.Core.Services;
|
||||||
@@ -42,11 +43,29 @@ internal sealed partial class EngineEditorWindow : WindowEx
|
|||||||
// For static tabs in EngineEditorWindow, we remove the item from TabItems
|
// For static tabs in EngineEditorWindow, we remove the item from TabItems
|
||||||
if (sender.TabItems.Contains(args.Item))
|
if (sender.TabItems.Contains(args.Item))
|
||||||
{
|
{
|
||||||
sender.TabItems.Remove(args.Item);
|
int originalIndex = sender.TabItems.IndexOf(args.Item);
|
||||||
|
object? originalSelection = sender.SelectedItem;
|
||||||
var newWindow = new DockWindow(args.Item);
|
|
||||||
App.AddSecondaryWindow(newWindow);
|
try
|
||||||
newWindow.Activate();
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user