From abc30f14c98f8d17e613d85bffc3243b1de8acfe Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 25 Apr 2026 18:18:53 +0200 Subject: [PATCH] Integrate Dock.Avalonia for the layout Replaces the temporary 3-column Grid placeholder in MainWindow with a Dock.Avalonia DockControl driven by an ILSpyDockFactory and orchestrated by a [Shared] DockWorkspace exported via MEF. Assisted-by: Claude:claude-opus-4-7:Claude Code --- Directory.Packages.props | 2 + ILSpy.Tests/Docking/DockWorkspaceTests.cs | 48 +++++++++ ILSpy/Analyzers/AnalyzerTreeView.axaml | 12 +++ ILSpy/Analyzers/AnalyzerTreeView.axaml.cs | 30 ++++++ ILSpy/Analyzers/AnalyzerTreeViewModel.cs | 37 +++++++ ILSpy/App.axaml | 17 +++- ILSpy/AssemblyTree/AssemblyTreeModel.cs | 16 ++- ILSpy/Docking/DockWorkspace.cs | 115 ++++++++++++++++++++++ ILSpy/Docking/ILSpyDockFactory.cs | 113 +++++++++++++++++++++ ILSpy/ILSpy.csproj | 2 + ILSpy/Search/SearchPane.axaml | 12 +++ ILSpy/Search/SearchPane.axaml.cs | 30 ++++++ ILSpy/Search/SearchPaneModel.cs | 37 +++++++ ILSpy/ViewModels/MainWindowViewModel.cs | 15 ++- ILSpy/ViewModels/TabPageModel.cs | 26 +++++ ILSpy/ViewModels/ToolPaneMenuItem.cs | 59 +++++++++++ ILSpy/ViewModels/ToolPaneModel.cs | 26 +++++ ILSpy/Views/MainMenu.axaml | 15 ++- ILSpy/Views/MainWindow.axaml | 12 +-- 19 files changed, 612 insertions(+), 12 deletions(-) create mode 100644 ILSpy.Tests/Docking/DockWorkspaceTests.cs create mode 100644 ILSpy/Analyzers/AnalyzerTreeView.axaml create mode 100644 ILSpy/Analyzers/AnalyzerTreeView.axaml.cs create mode 100644 ILSpy/Analyzers/AnalyzerTreeViewModel.cs create mode 100644 ILSpy/Docking/DockWorkspace.cs create mode 100644 ILSpy/Docking/ILSpyDockFactory.cs create mode 100644 ILSpy/Search/SearchPane.axaml create mode 100644 ILSpy/Search/SearchPane.axaml.cs create mode 100644 ILSpy/Search/SearchPaneModel.cs create mode 100644 ILSpy/ViewModels/TabPageModel.cs create mode 100644 ILSpy/ViewModels/ToolPaneMenuItem.cs create mode 100644 ILSpy/ViewModels/ToolPaneModel.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 4a8615af4..106d4c6a5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,8 +25,10 @@ + + diff --git a/ILSpy.Tests/Docking/DockWorkspaceTests.cs b/ILSpy.Tests/Docking/DockWorkspaceTests.cs new file mode 100644 index 000000000..9c204317e --- /dev/null +++ b/ILSpy.Tests/Docking/DockWorkspaceTests.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Avalonia.Headless.NUnit; + +using AwesomeAssertions; + +using ILSpy.AppEnv; +using ILSpy.Docking; + +using NUnit.Framework; + +namespace ICSharpCode.ILSpy.Tests.Docking; + +// DockWorkspace is the MEF-shared wrapper that holds the Dock.Avalonia factory + the root +// layout. If MEF can't construct it (e.g., missing AssemblyTreeModel / SearchPaneModel / +// AnalyzerTreeViewModel imports), the entire dock surface never materializes. Asserting +// at the export layer catches that before we wire it into the DockControl. +[TestFixture] +public class DockWorkspaceTests +{ + [AvaloniaTest] + public void DockWorkspace_resolves_and_exposes_root_layout_with_three_tool_panes() + { + var workspace = AppComposition.Current.GetExport(); + workspace.Should().NotBeNull("DockWorkspace is [Export][Shared] in ILSpy.Docking."); + + workspace.Layout.Should().NotBeNull("ILSpyDockFactory.CreateLayout() wires the root dock in the ctor."); + workspace.Factory.Should().NotBeNull(); + workspace.ToolPaneMenuItems.Should().HaveCount(3, + "AssemblyTree, Search, and Analyzers are the three tool panes wired at this point."); + } +} diff --git a/ILSpy/Analyzers/AnalyzerTreeView.axaml b/ILSpy/Analyzers/AnalyzerTreeView.axaml new file mode 100644 index 000000000..62dce9e1c --- /dev/null +++ b/ILSpy/Analyzers/AnalyzerTreeView.axaml @@ -0,0 +1,12 @@ + + + diff --git a/ILSpy/Analyzers/AnalyzerTreeView.axaml.cs b/ILSpy/Analyzers/AnalyzerTreeView.axaml.cs new file mode 100644 index 000000000..74f32f7fc --- /dev/null +++ b/ILSpy/Analyzers/AnalyzerTreeView.axaml.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Avalonia.Controls; + +namespace ILSpy.Analyzers +{ + public partial class AnalyzerTreeView : UserControl + { + public AnalyzerTreeView() + { + InitializeComponent(); + } + } +} diff --git a/ILSpy/Analyzers/AnalyzerTreeViewModel.cs b/ILSpy/Analyzers/AnalyzerTreeViewModel.cs new file mode 100644 index 000000000..c4f638ea3 --- /dev/null +++ b/ILSpy/Analyzers/AnalyzerTreeViewModel.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Composition; + +using ILSpy.ViewModels; + +namespace ILSpy.Analyzers +{ + [Export] + [Shared] + public class AnalyzerTreeViewModel : ToolPaneModel + { + public const string PaneContentId = "Analyzer"; + + public AnalyzerTreeViewModel() + { + Id = PaneContentId; + Title = "Analyzer"; + } + } +} diff --git a/ILSpy/App.axaml b/ILSpy/App.axaml index dd927346e..8b274a36e 100644 --- a/ILSpy/App.axaml +++ b/ILSpy/App.axaml @@ -2,14 +2,29 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ILSpy.App" xmlns:local="using:ILSpy" + xmlns:tree="using:ILSpy.AssemblyTree" + xmlns:search="using:ILSpy.Search" + xmlns:analyzers="using:ILSpy.Analyzers" + xmlns:dockTheme="using:Dock.Avalonia.Themes.Fluent" RequestedThemeVariant="Default"> + + + + + + + + + - + + + \ No newline at end of file diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs index 04cea8e00..3ea4677a4 100644 --- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -18,6 +18,7 @@ using System; using System.Composition; +using System.Runtime.Serialization; using CommunityToolkit.Mvvm.ComponentModel; @@ -26,21 +27,34 @@ using ICSharpCode.ILSpyX.Settings; using ICSharpCode.ILSpyX.TreeView; using ILSpy.TreeNodes; +using ILSpy.ViewModels; namespace ILSpy.AssemblyTree { [Export] [Shared] - public partial class AssemblyTreeModel : ObservableObject + public partial class AssemblyTreeModel : ToolPaneModel { + public const string PaneContentId = "AssemblyTree"; + [ObservableProperty] + [property: IgnoreDataMember] private SharpTreeNode? root; [ObservableProperty] + [property: IgnoreDataMember] private SharpTreeNode? selectedItem; + [IgnoreDataMember] public AssemblyList? AssemblyList { get; private set; } + public AssemblyTreeModel() + { + Id = PaneContentId; + Title = "Assemblies"; + CanClose = false; + } + public void Initialize() { var settings = ILSpySettings.Load(); diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs new file mode 100644 index 000000000..cefcdcf23 --- /dev/null +++ b/ILSpy/Docking/DockWorkspace.cs @@ -0,0 +1,115 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Composition; + +using Dock.Model.Controls; +using Dock.Model.Core; +using Dock.Model.Core.Events; + +using ILSpy.Analyzers; +using ILSpy.AssemblyTree; +using ILSpy.Search; +using ILSpy.ViewModels; + +namespace ILSpy.Docking +{ + [Export] + [Shared] + public class DockWorkspace + { + readonly ILSpyDockFactory factory; + + public IFactory Factory => factory; + + public IRootDock Layout { get; } + + public IReadOnlyList ToolPaneMenuItems { get; } + + [ImportingConstructor] + public DockWorkspace( + AssemblyTreeModel assemblyTreeModel, + SearchPaneModel searchPaneModel, + AnalyzerTreeViewModel analyzerTreeViewModel) + { + factory = new ILSpyDockFactory(assemblyTreeModel, searchPaneModel, analyzerTreeViewModel); + Layout = factory.CreateLayout(); + // Layout/factory initialization (locators, parent/factory wiring) is done by + // the DockControl in MainWindow.axaml via InitializeFactory/InitializeLayout. + + ToolPaneMenuItems = new List { + new(assemblyTreeModel, factory), + new(searchPaneModel, factory), + new(analyzerTreeViewModel, factory), + }; + + factory.DockableAdded += OnDocumentMembershipChanged; + factory.DockableRemoved += OnDocumentMembershipChanged; + factory.DockableClosed += OnDocumentMembershipChanged; + factory.DockableClosing += OnDockableClosing; + + // TODO: layout persistence (load on startup, save on exit). DockSerializer.SystemTextJson + // trips System.Text.Json's MaxDepth=64 limit on our layout because Dock's JsonConverterList + // calls JsonSerializer.Serialize per list element, which doesn't preserve the writer's + // reference-tracking state between calls — so Owner/Factory back-refs aren't deduplicated as + // $ref. Options when revisiting: try Dock.Serializer.Newtonsoft (better cycle handling), + // or build a custom JsonSerializerOptions with MaxDepth raised + replicate Dock's internal + // polymorphic resolver. + } + + void OnDocumentMembershipChanged(object? sender, EventArgs e) => UpdateLastDocumentCanClose(); + + // Convert a tool pane's "close" (X button) into "hide" so the Window menu can restore it. + // Documents are still closed for real — they get garbage-collected. + void OnDockableClosing(object? sender, DockableClosingEventArgs e) + { + if (e.Dockable is ToolPaneModel toolPane) + { + e.Cancel = true; + factory.HideDockable(toolPane); + } + } + + // When only one document is open, make it un-closeable so the user can't end up with an + // empty document area. + void UpdateLastDocumentCanClose() + { + var docs = factory.Documents?.VisibleDockables; + if (docs == null) + return; + bool canClose = docs.Count > 1; + foreach (var d in docs) + { + d.CanClose = canClose; + } + } + + public TabPageModel OpenNewTab(TabPageModel tab) + { + if (factory.Documents != null) + { + factory.AddDockable(factory.Documents, tab); + factory.SetActiveDockable(tab); + factory.SetFocusedDockable(factory.Documents, tab); + } + return tab; + } + } +} diff --git a/ILSpy/Docking/ILSpyDockFactory.cs b/ILSpy/Docking/ILSpyDockFactory.cs new file mode 100644 index 000000000..1ebd847fd --- /dev/null +++ b/ILSpy/Docking/ILSpyDockFactory.cs @@ -0,0 +1,113 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Dock.Model.Controls; +using Dock.Model.Core; +using Dock.Model.Mvvm; +using Dock.Model.Mvvm.Controls; + +using ILSpy.Analyzers; +using ILSpy.AssemblyTree; +using ILSpy.Search; + +namespace ILSpy.Docking +{ + public class ILSpyDockFactory : Factory + { + readonly AssemblyTreeModel assemblyTreeModel; + readonly SearchPaneModel searchPaneModel; + readonly AnalyzerTreeViewModel analyzerTreeViewModel; + + public IDocumentDock? Documents { get; private set; } + + public ILSpyDockFactory( + AssemblyTreeModel assemblyTreeModel, + SearchPaneModel searchPaneModel, + AnalyzerTreeViewModel analyzerTreeViewModel) + { + this.assemblyTreeModel = assemblyTreeModel; + this.searchPaneModel = searchPaneModel; + this.analyzerTreeViewModel = analyzerTreeViewModel; + } + + public override IRootDock CreateLayout() + { + var leftToolDock = new ToolDock { + Id = "LeftTools", + Proportion = 0.25, + VisibleDockables = CreateList(assemblyTreeModel), + ActiveDockable = assemblyTreeModel, + Alignment = Alignment.Left, + }; + + var topToolDock = new ToolDock { + Id = "TopTools", + Proportion = 0.2, + VisibleDockables = CreateList(searchPaneModel), + ActiveDockable = searchPaneModel, + Alignment = Alignment.Top, + }; + + var documents = new DocumentDock { + Id = "Documents", + Title = "Documents", + IsCollapsable = false, + Proportion = 0.6, + }; + Documents = documents; + + var bottomToolDock = new ToolDock { + Id = "BottomTools", + Proportion = 0.2, + VisibleDockables = CreateList(analyzerTreeViewModel), + ActiveDockable = analyzerTreeViewModel, + Alignment = Alignment.Bottom, + }; + + var rightVertical = new ProportionalDock { + Id = "RightArea", + Orientation = Orientation.Vertical, + Proportion = 0.75, + VisibleDockables = CreateList( + topToolDock, + new ProportionalDockSplitter { Id = "TopSplitter" }, + documents, + new ProportionalDockSplitter { Id = "BottomSplitter" }, + bottomToolDock), + }; + + var horizontal = new ProportionalDock { + Id = "MainLayout", + Orientation = Orientation.Horizontal, + VisibleDockables = CreateList( + leftToolDock, + new ProportionalDockSplitter { Id = "LeftSplitter" }, + rightVertical), + }; + + var root = CreateRootDock(); + root.Id = "Root"; + root.IsCollapsable = false; + root.VisibleDockables = CreateList(horizontal); + root.DefaultDockable = horizontal; + root.ActiveDockable = horizontal; + + return root; + } + } +} diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index c6eb08ba5..105dbbcaf 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -34,7 +34,9 @@ + + diff --git a/ILSpy/Search/SearchPane.axaml b/ILSpy/Search/SearchPane.axaml new file mode 100644 index 000000000..1a9912b6c --- /dev/null +++ b/ILSpy/Search/SearchPane.axaml @@ -0,0 +1,12 @@ + + + diff --git a/ILSpy/Search/SearchPane.axaml.cs b/ILSpy/Search/SearchPane.axaml.cs new file mode 100644 index 000000000..18d818f9a --- /dev/null +++ b/ILSpy/Search/SearchPane.axaml.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Avalonia.Controls; + +namespace ILSpy.Search +{ + public partial class SearchPane : UserControl + { + public SearchPane() + { + InitializeComponent(); + } + } +} diff --git a/ILSpy/Search/SearchPaneModel.cs b/ILSpy/Search/SearchPaneModel.cs new file mode 100644 index 000000000..ce2b4bef2 --- /dev/null +++ b/ILSpy/Search/SearchPaneModel.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Composition; + +using ILSpy.ViewModels; + +namespace ILSpy.Search +{ + [Export] + [Shared] + public class SearchPaneModel : ToolPaneModel + { + public const string PaneContentId = "Search"; + + public SearchPaneModel() + { + Id = PaneContentId; + Title = "Search"; + } + } +} diff --git a/ILSpy/ViewModels/MainWindowViewModel.cs b/ILSpy/ViewModels/MainWindowViewModel.cs index 545e5bd3b..50da5d803 100644 --- a/ILSpy/ViewModels/MainWindowViewModel.cs +++ b/ILSpy/ViewModels/MainWindowViewModel.cs @@ -18,7 +18,11 @@ using System.Composition; +using Dock.Model.Controls; +using Dock.Model.Core; + using ILSpy.AssemblyTree; +using ILSpy.Docking; namespace ILSpy.ViewModels { @@ -28,12 +32,21 @@ namespace ILSpy.ViewModels { public AssemblyTreeModel AssemblyTreeModel { get; } + public DockWorkspace DockWorkspace { get; } + + public IFactory DockFactory => DockWorkspace.Factory; + + public IRootDock DockLayout => DockWorkspace.Layout; + + public System.Collections.Generic.IReadOnlyList ToolPaneMenuItems => DockWorkspace.ToolPaneMenuItems; + public string Title => "ILSpy"; [ImportingConstructor] - public MainWindowViewModel(AssemblyTreeModel assemblyTreeModel) + public MainWindowViewModel(AssemblyTreeModel assemblyTreeModel, DockWorkspace dockWorkspace) { AssemblyTreeModel = assemblyTreeModel; + DockWorkspace = dockWorkspace; } } } diff --git a/ILSpy/ViewModels/TabPageModel.cs b/ILSpy/ViewModels/TabPageModel.cs new file mode 100644 index 000000000..7db529e09 --- /dev/null +++ b/ILSpy/ViewModels/TabPageModel.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Dock.Model.Mvvm.Controls; + +namespace ILSpy.ViewModels +{ + public abstract class TabPageModel : Document + { + } +} diff --git a/ILSpy/ViewModels/ToolPaneMenuItem.cs b/ILSpy/ViewModels/ToolPaneMenuItem.cs new file mode 100644 index 000000000..66d1a5791 --- /dev/null +++ b/ILSpy/ViewModels/ToolPaneMenuItem.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using CommunityToolkit.Mvvm.ComponentModel; + +using Dock.Model.Controls; +using Dock.Model.Core; + +namespace ILSpy.ViewModels +{ + public class ToolPaneMenuItem : ObservableObject + { + readonly ToolPaneModel pane; + readonly IFactory factory; + + public ToolPaneMenuItem(ToolPaneModel pane, IFactory factory) + { + this.pane = pane; + this.factory = factory; + factory.DockableHidden += OnFactoryVisibilityChanged; + factory.DockableRestored += OnFactoryVisibilityChanged; + } + + public string? Title => pane.Title; + + public bool IsPaneVisible { + get => pane.Owner is not IRootDock; + set { + if (value == IsPaneVisible) + return; + if (value) + factory.RestoreDockable(pane); + else + factory.HideDockable(pane); + OnPropertyChanged(); + } + } + + void OnFactoryVisibilityChanged(object? sender, System.EventArgs e) + { + OnPropertyChanged(nameof(IsPaneVisible)); + } + } +} diff --git a/ILSpy/ViewModels/ToolPaneModel.cs b/ILSpy/ViewModels/ToolPaneModel.cs new file mode 100644 index 000000000..7fce1a6ac --- /dev/null +++ b/ILSpy/ViewModels/ToolPaneModel.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Dock.Model.Mvvm.Controls; + +namespace ILSpy.ViewModels +{ + public abstract class ToolPaneModel : Tool + { + } +} diff --git a/ILSpy/Views/MainMenu.axaml b/ILSpy/Views/MainMenu.axaml index f97224195..c27081e23 100644 --- a/ILSpy/Views/MainMenu.axaml +++ b/ILSpy/Views/MainMenu.axaml @@ -2,8 +2,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:vm="using:ILSpy.ViewModels" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="23" - x:Class="ILSpy.MainMenu"> + x:Class="ILSpy.MainMenu" + x:DataType="vm:MainWindowViewModel"> @@ -11,8 +13,15 @@ - - + + + + + + diff --git a/ILSpy/Views/MainWindow.axaml b/ILSpy/Views/MainWindow.axaml index c2599e31f..021c95f04 100644 --- a/ILSpy/Views/MainWindow.axaml +++ b/ILSpy/Views/MainWindow.axaml @@ -2,6 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="using:ILSpy.ViewModels" xmlns:local="using:ILSpy" + xmlns:tree="using:ILSpy.AssemblyTree" + xmlns:dock="using:Dock.Avalonia.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="500" @@ -29,11 +31,9 @@ Margin="8,0" Name="statusLabel" Text="Stand by..." /> - - - - +