From 0a2f2e388b2320b429e9dea788714f62dc20c76e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 4 Jun 2026 16:18:19 +0200 Subject: [PATCH] Freeze the preview tab when it is floated out into its own window MoveDockable freezes the One on in-strip and cross-dock drags, but the tear-off-to-floating-window gesture never reaches it: Dock funnels that through CreateWindowFrom (FloatDockable -> SplitToWindow -> CreateWindowFrom), where the torn-off document is still the One before it gets wrapped in a fresh dock. Overriding it makes a float-out one more way to keep the tab, consistent with every other drag. --- .../Docking/PreviewTabPromotionTests.cs | 25 +++++++++++++++++++ ILSpy/Docking/ILSpyDockFactory.cs | 11 ++++++++ 2 files changed, 36 insertions(+) diff --git a/ILSpy.Tests/Docking/PreviewTabPromotionTests.cs b/ILSpy.Tests/Docking/PreviewTabPromotionTests.cs index ecdd135c4..992d28561 100644 --- a/ILSpy.Tests/Docking/PreviewTabPromotionTests.cs +++ b/ILSpy.Tests/Docking/PreviewTabPromotionTests.cs @@ -727,6 +727,31 @@ public class PreviewTabPromotionTests "freeze-on-drag does not rotate MainTab; the next tree selection forges a fresh One"); } + [AvaloniaTest] + public async Task Floating_The_Preview_Out_Freezes_It() + { + // Tearing the One out into its own floating window does NOT go through MoveDockable -- the + // drag-to-float gesture funnels through CreateWindowFrom (FloatDockable -> SplitToWindow -> + // CreateWindowFrom). That override must freeze the One just like an in-strip drag. Drive + // CreateWindowFrom directly (what the float gesture ultimately calls). + var (_, vm) = await TestHarness.BootAsync(3); + var factory = (ILSpyDockFactory)vm.DockWorkspace.Factory; + + var typeA = vm.AssemblyTreeModel.FindNode( + "System.Linq", "System.Linq", "System.Linq.Enumerable"); + vm.AssemblyTreeModel.SelectNode(typeA); + vm.DockWorkspace.SettleSelection(); + var theOne = factory.MainTab!; + theOne.IsPreview.Should().BeTrue("precondition: the One starts as the preview"); + + factory.CreateWindowFrom(theOne); + + theOne.IsPreview.Should().BeFalse( + "floating the One out must freeze it -- it becomes an ordinary, movable tab"); + factory.MainTab.Should().BeSameAs(theOne, + "float-out does not rotate MainTab; the next tree selection forges a fresh One"); + } + [AvaloniaTest] public async Task Frozen_Tab_Cannot_Be_Reordered_Before_The_Preview() { diff --git a/ILSpy/Docking/ILSpyDockFactory.cs b/ILSpy/Docking/ILSpyDockFactory.cs index c37d722e9..47981265c 100644 --- a/ILSpy/Docking/ILSpyDockFactory.cs +++ b/ILSpy/Docking/ILSpyDockFactory.cs @@ -122,6 +122,17 @@ namespace ILSpy.Docking base.MoveDockable(sourceDock, targetDock, sourceDockable, targetDockable); } + // Tearing the One out into its own floating window doesn't go through MoveDockable -- the + // drag-to-float gesture funnels through CreateWindowFrom (FloatDockable -> SplitToWindow -> + // CreateWindowFrom), with the torn-off document passed straight through before it's wrapped + // in a fresh dock. Freeze the One here too so a float-out is just another way to keep it. + public override IDockWindow? CreateWindowFrom(IDockable dockable) + { + if (ReferenceEquals(dockable, MainTab) && MainTab is { IsPreview: true }) + FreezeCurrentMainTab(); + return base.CreateWindowFrom(dockable); + } + // Self-healing insurance: re-assert the One at index 0 after any drag -- but ONLY while it is // still the preview. Once dragged (which freezes it) it's an ordinary tab and must stay where // the user dropped it. Idempotent; guards against a future Dock version routing a reorder