Browse Source

Activate the welcome page instead of duplicating it as About

The startup welcome page renders the same About content in the reusable main tab. Help > About opened a second, static About tab beside it, so the user saw the page twice. Track the welcome content and, while it is still the live main-tab content, have Help > About activate it rather than open the singleton. Falls through to the singleton once a tree-node selection has replaced the welcome page.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3755/head
Siegfried Pammer 1 month ago
parent
commit
76b8db794a
  1. 34
      ILSpy.Tests/AssemblyTree/StartupAboutWelcomeTests.cs
  2. 7
      ILSpy.Tests/Commands/HelpCommandTests.cs
  3. 14
      ILSpy.Tests/Docking/SingletonDocumentTabTests.cs
  4. 5
      ILSpy/Commands/AboutCommand.cs
  5. 34
      ILSpy/Docking/DockWorkspace.cs

34
ILSpy.Tests/AssemblyTree/StartupAboutWelcomeTests.cs

@ -16,16 +16,20 @@ @@ -16,16 +16,20 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Headless.NUnit;
using Avalonia.Threading;
using AwesomeAssertions;
using ICSharpCode.ILSpy.Properties;
using ILSpy.AppEnv;
using ILSpy.Commands;
using ILSpy.Docking;
using ILSpy.TextView;
using ILSpy.ViewModels;
using ILSpy.Views;
@ -63,4 +67,34 @@ public class StartupAboutWelcomeTests @@ -63,4 +67,34 @@ public class StartupAboutWelcomeTests
((object?)active!.Title).Should().Be(Resources.About);
active.Text.Should().NotBeNullOrWhiteSpace("the About page must have rendered its content");
}
[AvaloniaTest]
public async Task Help_About_While_Welcome_Page_Is_Visible_Activates_It_Without_Opening_A_Second_Tab()
{
// The welcome page renders the same About content in the reusable main tab. Invoking Help >
// About while it is on screen must just activate it, not spawn a duplicate static About tab.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
var dock = AppComposition.Current.GetExport<DockWorkspace>();
await Waiters.WaitForAsync(
() => dock.IsWelcomePageVisible,
description: "the welcome (About) page must be showing before we invoke Help > About");
int tabsBefore = dock.Documents!.VisibleDockables!.OfType<ContentTabPage>().Count();
AppComposition.Current.GetExport<MainMenuCommandRegistry>()
.GetCommand(nameof(Resources._About)).Execute(null);
Dispatcher.UIThread.RunJobs();
dock.Documents!.VisibleDockables!.OfType<ContentTabPage>()
.Any(t => t.Content is DecompilerTabPageModel { IsStaticContent: true, Title: var title } && Equals(title, Resources.About))
.Should().BeFalse("invoking About while the welcome page is visible must not open a static About tab");
dock.Documents!.VisibleDockables!.OfType<ContentTabPage>().Count().Should().Be(tabsBefore,
"no new tab should be added when the welcome page already shows the About content");
dock.IsWelcomePageVisible.Should().BeTrue("the welcome page must remain the live main-tab content");
}
}

7
ILSpy.Tests/Commands/HelpCommandTests.cs

@ -58,6 +58,13 @@ public class HelpCommandTests @@ -58,6 +58,13 @@ public class HelpCommandTests
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
// Dismiss the startup welcome page first: it renders the same About content in the reusable
// main tab, and while it is visible Help > About activates it rather than opening a tab.
// Selecting a node replaces the welcome content so About then opens its own tab as asserted.
vm.AssemblyTreeModel.SelectNode(vm.AssemblyTreeModel.Root!.Children[0]);
await Waiters.WaitForAsync(() => !vm.DockWorkspace.IsWelcomePageVisible,
description: "the welcome page must be dismissed so Help > About opens a fresh tab");
var registry = AppComposition.Current.GetExport<MainMenuCommandRegistry>();
var aboutCmd = registry.Commands
.Single(c => c.Metadata.Header == nameof(Resources._About))

14
ILSpy.Tests/Docking/SingletonDocumentTabTests.cs

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE.
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Headless.NUnit;
@ -82,7 +83,7 @@ public class SingletonDocumentTabTests @@ -82,7 +83,7 @@ public class SingletonDocumentTabTests
}
[AvaloniaTest]
public void Reopening_About_Reuses_The_Same_Tab()
public async Task Reopening_About_Reuses_The_Same_Tab()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
@ -90,9 +91,16 @@ public class SingletonDocumentTabTests @@ -90,9 +91,16 @@ public class SingletonDocumentTabTests
var dock = vm.DockWorkspace;
TestCapture.Step("booted");
// Dismiss the startup welcome page first: while it is visible Help > About activates it
// rather than opening the static singleton this test pins. Selecting a node replaces the
// welcome content in the main tab, so About then takes the singleton path deterministically.
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1);
vm.AssemblyTreeModel.SelectNode(vm.AssemblyTreeModel.Root!.Children[0]);
await Waiters.WaitForAsync(() => !dock.IsWelcomePageVisible,
description: "the welcome page must be dismissed so Help > About opens the static singleton");
// Match the singleton menu-About tab specifically (IsStaticContent), NOT the transient boot
// "welcome" page, which also carries Title == About but is a non-static main-tab page. The
// welcome page's presence races with boot, so matching it too made this a ~50% flake.
// "welcome" page, which also carries Title == About but is a non-static main-tab page.
bool IsAbout(ContentTabPage t) => t.Content is DecompilerTabPageModel { Title: var title, IsStaticContent: true } && title == Resources.About;
Invoke(window, nameof(Resources._About));

5
ILSpy/Commands/AboutCommand.cs

@ -58,6 +58,11 @@ namespace ILSpy.Commands @@ -58,6 +58,11 @@ namespace ILSpy.Commands
public override void Execute(object? parameter)
{
// The startup welcome page already shows this exact About content in the reusable main
// tab. While it is still on screen, just activate it rather than opening a second, static
// About tab next to it.
if (dockWorkspace.TryActivateWelcomePage())
return;
// Singleton: one retained About tab, reused across close/reopen rather than rebuilt.
dockWorkspace.OpenSingletonTab("resource:aboutpage", () => {
var output = BuildAboutOutput();

34
ILSpy/Docking/DockWorkspace.cs

@ -686,6 +686,12 @@ namespace ILSpy.Docking @@ -686,6 +686,12 @@ namespace ILSpy.Docking
// Created lazily on first need.
DecompilerTabPageModel? decompilerContent;
// The startup welcome page (About content in the reusable MainTab, non-static). Tracked so
// Help > About can activate it instead of spawning a duplicate static About tab while it is
// still on screen. Self-correcting: once a tree-node selection swaps MainTab.Content to the
// decompiler content, this reference no longer equals MainTab.Content (see IsWelcomePageVisible).
DecompilerTabPageModel? welcomeContent;
// Retained static-content singleton tabs (Options, About, embedded resource pages).
// Keeping the ContentTabPage instance across close/reopen preserves its owned view and
// content state (e.g. the selected options page) instead of rebuilding the tab each time.
@ -1160,9 +1166,37 @@ namespace ILSpy.Docking @@ -1160,9 +1166,37 @@ namespace ILSpy.Docking
if (factory.Documents is { } docs && !ReferenceEquals(docs.ActiveDockable, main))
return;
main.Content = content;
welcomeContent = content;
ActivateMainTabIfNeeded(main);
}
/// <summary>
/// True while the startup welcome page is still the live MainTab content (it has not been
/// replaced by a tree-node selection). The welcome page renders the same About text as
/// Help &gt; About, so callers use this to avoid opening a duplicate About tab on top of it.
/// </summary>
public bool IsWelcomePageVisible
=> welcomeContent is { } w && factory.MainTab is { } main && ReferenceEquals(main.Content, w);
/// <summary>
/// If the welcome page is still showing, bring its MainTab to the front and report success.
/// Help &gt; About calls this first so that, while the welcome page (identical About content)
/// is visible, the menu just activates it instead of spawning a second, static About tab.
/// </summary>
public bool TryActivateWelcomePage()
{
if (!IsWelcomePageVisible || factory.MainTab is not { } main)
return false;
if (factory.Documents is { } docs)
{
if (docs.VisibleDockables?.Contains(main) != true)
return false;
factory.SetActiveDockable(main);
factory.SetFocusedDockable(docs, main);
}
return true;
}
/// <summary>
/// VS-style "freeze tab" gesture. The current <c>factory.MainTab</c> keeps its position,
/// content, and active state but flips to frozen

Loading…
Cancel
Save