Browse Source

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
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
abc30f14c9
  1. 2
      Directory.Packages.props
  2. 48
      ILSpy.Tests/Docking/DockWorkspaceTests.cs
  3. 12
      ILSpy/Analyzers/AnalyzerTreeView.axaml
  4. 30
      ILSpy/Analyzers/AnalyzerTreeView.axaml.cs
  5. 37
      ILSpy/Analyzers/AnalyzerTreeViewModel.cs
  6. 17
      ILSpy/App.axaml
  7. 16
      ILSpy/AssemblyTree/AssemblyTreeModel.cs
  8. 115
      ILSpy/Docking/DockWorkspace.cs
  9. 113
      ILSpy/Docking/ILSpyDockFactory.cs
  10. 2
      ILSpy/ILSpy.csproj
  11. 12
      ILSpy/Search/SearchPane.axaml
  12. 30
      ILSpy/Search/SearchPane.axaml.cs
  13. 37
      ILSpy/Search/SearchPaneModel.cs
  14. 15
      ILSpy/ViewModels/MainWindowViewModel.cs
  15. 26
      ILSpy/ViewModels/TabPageModel.cs
  16. 59
      ILSpy/ViewModels/ToolPaneMenuItem.cs
  17. 26
      ILSpy/ViewModels/ToolPaneModel.cs
  18. 15
      ILSpy/Views/MainMenu.axaml
  19. 12
      ILSpy/Views/MainWindow.axaml

2
Directory.Packages.props

@ -25,8 +25,10 @@
<PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.3.0.6" /> <PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.3.0.6" />
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.1" /> <PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.1" />
<PackageVersion Include="Dock.Avalonia" Version="$(DockVersion)" /> <PackageVersion Include="Dock.Avalonia" Version="$(DockVersion)" />
<PackageVersion Include="Dock.Avalonia.Themes.Fluent" Version="$(DockVersion)" />
<PackageVersion Include="Dock.Model" Version="$(DockVersion)" /> <PackageVersion Include="Dock.Model" Version="$(DockVersion)" />
<PackageVersion Include="Dock.Model.Mvvm" Version="$(DockVersion)" /> <PackageVersion Include="Dock.Model.Mvvm" Version="$(DockVersion)" />
<PackageVersion Include="Dock.Serializer.SystemTextJson" Version="$(DockVersion)" />
<PackageVersion Include="ProDataGrid" Version="12.0.0" /> <PackageVersion Include="ProDataGrid" Version="12.0.0" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" /> <PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" /> <PackageVersion Include="AwesomeAssertions" Version="9.4.0" />

48
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<DockWorkspace>();
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.");
}
}

12
ILSpy/Analyzers/AnalyzerTreeView.axaml

@ -0,0 +1,12 @@
<UserControl xmlns="https://github.com/avaloniaui"
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:analyzers="using:ILSpy.Analyzers"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="200"
x:Class="ILSpy.Analyzers.AnalyzerTreeView"
x:DataType="analyzers:AnalyzerTreeViewModel">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="Gray" FontStyle="Italic"
Text="Analyzer results will appear here." />
</UserControl>

30
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();
}
}
}

37
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";
}
}
}

17
ILSpy/App.axaml

@ -2,14 +2,29 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ILSpy.App" x:Class="ILSpy.App"
xmlns:local="using:ILSpy" 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"> RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. --> <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.DataTemplates> <Application.DataTemplates>
<DataTemplate DataType="tree:AssemblyTreeModel">
<tree:AssemblyListPane />
</DataTemplate>
<DataTemplate DataType="search:SearchPaneModel">
<search:SearchPane />
</DataTemplate>
<DataTemplate DataType="analyzers:AnalyzerTreeViewModel">
<analyzers:AnalyzerTreeView />
</DataTemplate>
<local:ViewLocator/> <local:ViewLocator/>
</Application.DataTemplates> </Application.DataTemplates>
<Application.Styles> <Application.Styles>
<FluentTheme /> <FluentTheme />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.v2.xaml" />
<dockTheme:DockFluentTheme />
</Application.Styles> </Application.Styles>
</Application> </Application>

16
ILSpy/AssemblyTree/AssemblyTreeModel.cs

@ -18,6 +18,7 @@
using System; using System;
using System.Composition; using System.Composition;
using System.Runtime.Serialization;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
@ -26,21 +27,34 @@ using ICSharpCode.ILSpyX.Settings;
using ICSharpCode.ILSpyX.TreeView; using ICSharpCode.ILSpyX.TreeView;
using ILSpy.TreeNodes; using ILSpy.TreeNodes;
using ILSpy.ViewModels;
namespace ILSpy.AssemblyTree namespace ILSpy.AssemblyTree
{ {
[Export] [Export]
[Shared] [Shared]
public partial class AssemblyTreeModel : ObservableObject public partial class AssemblyTreeModel : ToolPaneModel
{ {
public const string PaneContentId = "AssemblyTree";
[ObservableProperty] [ObservableProperty]
[property: IgnoreDataMember]
private SharpTreeNode? root; private SharpTreeNode? root;
[ObservableProperty] [ObservableProperty]
[property: IgnoreDataMember]
private SharpTreeNode? selectedItem; private SharpTreeNode? selectedItem;
[IgnoreDataMember]
public AssemblyList? AssemblyList { get; private set; } public AssemblyList? AssemblyList { get; private set; }
public AssemblyTreeModel()
{
Id = PaneContentId;
Title = "Assemblies";
CanClose = false;
}
public void Initialize() public void Initialize()
{ {
var settings = ILSpySettings.Load(); var settings = ILSpySettings.Load();

115
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<ToolPaneMenuItem> 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<ToolPaneMenuItem> {
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<T>
// 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;
}
}
}

113
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<IDockable>(assemblyTreeModel),
ActiveDockable = assemblyTreeModel,
Alignment = Alignment.Left,
};
var topToolDock = new ToolDock {
Id = "TopTools",
Proportion = 0.2,
VisibleDockables = CreateList<IDockable>(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<IDockable>(analyzerTreeViewModel),
ActiveDockable = analyzerTreeViewModel,
Alignment = Alignment.Bottom,
};
var rightVertical = new ProportionalDock {
Id = "RightArea",
Orientation = Orientation.Vertical,
Proportion = 0.75,
VisibleDockables = CreateList<IDockable>(
topToolDock,
new ProportionalDockSplitter { Id = "TopSplitter" },
documents,
new ProportionalDockSplitter { Id = "BottomSplitter" },
bottomToolDock),
};
var horizontal = new ProportionalDock {
Id = "MainLayout",
Orientation = Orientation.Horizontal,
VisibleDockables = CreateList<IDockable>(
leftToolDock,
new ProportionalDockSplitter { Id = "LeftSplitter" },
rightVertical),
};
var root = CreateRootDock();
root.Id = "Root";
root.IsCollapsable = false;
root.VisibleDockables = CreateList<IDockable>(horizontal);
root.DefaultDockable = horizontal;
root.ActiveDockable = horizontal;
return root;
}
}
}

2
ILSpy/ILSpy.csproj

@ -34,7 +34,9 @@
<PackageReference Include="AvaloniaEdit.TextMate" /> <PackageReference Include="AvaloniaEdit.TextMate" />
<PackageReference Include="Avalonia.Xaml.Behaviors" /> <PackageReference Include="Avalonia.Xaml.Behaviors" />
<PackageReference Include="Dock.Avalonia" /> <PackageReference Include="Dock.Avalonia" />
<PackageReference Include="Dock.Avalonia.Themes.Fluent" />
<PackageReference Include="Dock.Model.Mvvm" /> <PackageReference Include="Dock.Model.Mvvm" />
<PackageReference Include="Dock.Serializer.SystemTextJson" />
<PackageReference Include="ProDataGrid" /> <PackageReference Include="ProDataGrid" />
<!-- MVVM, DI, MEF --> <!-- MVVM, DI, MEF -->

12
ILSpy/Search/SearchPane.axaml

@ -0,0 +1,12 @@
<UserControl xmlns="https://github.com/avaloniaui"
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:search="using:ILSpy.Search"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="200"
x:Class="ILSpy.Search.SearchPane"
x:DataType="search:SearchPaneModel">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="Gray" FontStyle="Italic"
Text="Search results will appear here." />
</UserControl>

30
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();
}
}
}

37
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";
}
}
}

15
ILSpy/ViewModels/MainWindowViewModel.cs

@ -18,7 +18,11 @@
using System.Composition; using System.Composition;
using Dock.Model.Controls;
using Dock.Model.Core;
using ILSpy.AssemblyTree; using ILSpy.AssemblyTree;
using ILSpy.Docking;
namespace ILSpy.ViewModels namespace ILSpy.ViewModels
{ {
@ -28,12 +32,21 @@ namespace ILSpy.ViewModels
{ {
public AssemblyTreeModel AssemblyTreeModel { get; } public AssemblyTreeModel AssemblyTreeModel { get; }
public DockWorkspace DockWorkspace { get; }
public IFactory DockFactory => DockWorkspace.Factory;
public IRootDock DockLayout => DockWorkspace.Layout;
public System.Collections.Generic.IReadOnlyList<ToolPaneMenuItem> ToolPaneMenuItems => DockWorkspace.ToolPaneMenuItems;
public string Title => "ILSpy"; public string Title => "ILSpy";
[ImportingConstructor] [ImportingConstructor]
public MainWindowViewModel(AssemblyTreeModel assemblyTreeModel) public MainWindowViewModel(AssemblyTreeModel assemblyTreeModel, DockWorkspace dockWorkspace)
{ {
AssemblyTreeModel = assemblyTreeModel; AssemblyTreeModel = assemblyTreeModel;
DockWorkspace = dockWorkspace;
} }
} }
} }

26
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
{
}
}

59
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));
}
}
}

26
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
{
}
}

15
ILSpy/Views/MainMenu.axaml

@ -2,8 +2,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ILSpy.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="23" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="23"
x:Class="ILSpy.MainMenu"> x:Class="ILSpy.MainMenu"
x:DataType="vm:MainWindowViewModel">
<Menu DockPanel.Dock="Top" Name="Menu" Height="23"> <Menu DockPanel.Dock="Top" Name="Menu" Height="23">
<MenuItem Header="_File" Tag="_File"> <MenuItem Header="_File" Tag="_File">
<!-- MEF-populated content (task #10) --> <!-- MEF-populated content (task #10) -->
@ -11,8 +13,15 @@
<MenuItem Header="_View" Tag="_View"> <MenuItem Header="_View" Tag="_View">
<!-- API visibility toggles, theme selection, etc. wired in task #10 / #3 --> <!-- API visibility toggles, theme selection, etc. wired in task #10 / #3 -->
</MenuItem> </MenuItem>
<MenuItem Header="_Window" Name="WindowMenuItem" Tag="_Window"> <MenuItem Header="_Window" Name="WindowMenuItem" Tag="_Window"
<!-- Tool-pane / tab-page entries wired in task #10 --> ItemsSource="{Binding ToolPaneMenuItems}">
<MenuItem.DataTemplates>
<DataTemplate DataType="vm:ToolPaneMenuItem">
<MenuItem Header="{Binding Title}"
ToggleType="CheckBox"
IsChecked="{Binding IsPaneVisible, Mode=TwoWay}" />
</DataTemplate>
</MenuItem.DataTemplates>
</MenuItem> </MenuItem>
</Menu> </Menu>
</UserControl> </UserControl>

12
ILSpy/Views/MainWindow.axaml

@ -2,6 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ILSpy.ViewModels" xmlns:vm="using:ILSpy.ViewModels"
xmlns:local="using:ILSpy" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="500" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="500"
@ -29,11 +31,9 @@
Margin="8,0" Name="statusLabel" Text="Stand by..." /> Margin="8,0" Name="statusLabel" Text="Stand by..." />
</Border> </Border>
<!-- Placeholder for DockingManager (task #4: replace with Dock.Avalonia) --> <dock:DockControl Layout="{Binding DockLayout}"
<ContentControl Name="DockHost"> Factory="{Binding DockFactory}"
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" InitializeFactory="True"
Foreground="Gray" FontStyle="Italic" InitializeLayout="True" />
Text="Docking workspace will appear here." />
</ContentControl>
</DockPanel> </DockPanel>
</Window> </Window>

Loading…
Cancel
Save