fix(dock): ensure transactional tear-off and wire main window tabs

This commit is contained in:
2026-03-28 16:05:51 +09:00
parent 304df0a381
commit 299bcf520c
3 changed files with 41 additions and 19 deletions

View File

@@ -299,23 +299,32 @@ public sealed partial class DockLayout : Control
private void TabView_TabDroppedOutside(Microsoft.UI.Xaml.Controls.TabView sender, Microsoft.UI.Xaml.Controls.TabViewTabDroppedOutsideEventArgs args)
{
if (_sourceNode != null && _draggedItem != null)
if (_sourceNode != null && _draggedItem != null && TabTornOff != null)
{
int originalIndex = _sourceNode.Items.IndexOf(_draggedItem);
if (originalIndex == -1)
{
ClearDragOperationState();
return;
}
// Remove from current tree
if (_sourceNode.Items.Remove(_draggedItem))
{
DockMutationEngine.CleanupEmptyNodes(_sourceNode);
try
{
// Raise event to let the host handle window creation
TabTornOff?.Invoke(this, new TabTornOffEventArgs(_draggedItem));
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 if the tear-off handler fails
_sourceNode.Items.Add(_draggedItem);
Logger.LogError($"Failed to tear off tab: {ex.Message}");
// Rollback: Re-insert the item at original position if the tear-off handler fails
_sourceNode.Items.Insert(originalIndex, _draggedItem);
_sourceNode.SelectedItem = _draggedItem;
Logger.LogError(ex);
}
}

View File

@@ -79,10 +79,12 @@
</Grid.ColumnDefinitions>
<ghost:NavigationTabView
x:Name="PART_HierarchyTabView"
Grid.Column="0"
Width="350"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
VerticalAlignment="Stretch"
TabDroppedOutside="OnTabDroppedOutside">
<ghost:NavigationTabView.TabItems>
<TabViewItem Header="Hierarchy">
<TabViewItem.IconSource>
@@ -93,7 +95,10 @@
</ghost:NavigationTabView.TabItems>
</ghost:NavigationTabView>
<ghost:NavigationTabView Grid.Column="1">
<ghost:NavigationTabView
x:Name="PART_SceneTabView"
Grid.Column="1"
TabDroppedOutside="OnTabDroppedOutside">
<ghost:NavigationTabView.TabItems>
<ee:ScenePage Header="Scene">
<ee:ScenePage.IconSource>
@@ -104,10 +109,12 @@
</ghost:NavigationTabView>
<ghost:NavigationTabView
x:Name="PART_InspectorTabView"
Grid.Column="2"
Width="350"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
VerticalAlignment="Stretch"
TabDroppedOutside="OnTabDroppedOutside">
<ghost:NavigationTabView.TabItems>
<ee:InspectorPage Header="Inspector">
<ee:InspectorPage.IconSource>
@@ -118,7 +125,11 @@
</ghost:NavigationTabView>
</Grid>
<ghost:NavigationTabView Grid.Row="1" Height="350">
<ghost:NavigationTabView
x:Name="PART_BottomTabView"
Grid.Row="1"
Height="350"
TabDroppedOutside="OnTabDroppedOutside">
<ghost:NavigationTabView.TabItems>
<TabViewItem Header="Project">
<TabViewItem.IconSource>

View File

@@ -35,17 +35,19 @@ 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)
private void OnTabDroppedOutside(Microsoft.UI.Xaml.Controls.TabView sender, Microsoft.UI.Xaml.Controls.TabViewTabDroppedOutsideEventArgs args)
{
var newWindow = new DockWindow(e.TabContent);
App.AddSecondaryWindow(newWindow);
newWindow.Activate();
// For static tabs in EngineEditorWindow, we remove the item from TabItems
if (sender.TabItems.Contains(args.Item))
{
sender.TabItems.Remove(args.Item);
var newWindow = new DockWindow(args.Item);
App.AddSecondaryWindow(newWindow);
newWindow.Activate();
}
}
private void MainGrid_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)