Browse Source

Merge branch 'master' into compiledbinding-methods

pull/7246/head
Dan Walmsley 5 years ago
committed by GitHub
parent
commit
58f3e54dc3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      Avalonia.sln
  2. 33
      azure-pipelines.yml
  3. 35
      build/MicroCom.targets
  4. 8
      nukebuild/MicroComGen.cs
  5. 6
      nukebuild/_build.csproj
  6. 2
      packages/Avalonia/Avalonia.csproj
  7. 38
      samples/ControlCatalog/App.xaml.cs
  8. 1
      samples/ControlCatalog/DecoratedWindow.xaml.cs
  9. 42
      samples/ControlCatalog/MainView.xaml.cs
  10. 5
      samples/ControlCatalog/MainWindow.xaml.cs
  11. 12
      src/Android/Avalonia.Android/AndroidPlatform.cs
  12. 2
      src/Avalonia.Animation/Avalonia.Animation.csproj
  13. 3
      src/Avalonia.Base/ApiCompatBaseline.txt
  14. 2
      src/Avalonia.Base/Avalonia.Base.csproj
  15. 9
      src/Avalonia.Base/Threading/Dispatcher.cs
  16. 9
      src/Avalonia.Base/Threading/IDispatcher.cs
  17. 83
      src/Avalonia.Base/Threading/JobRunner.cs
  18. 2
      src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs
  19. 2
      src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj
  20. 2
      src/Avalonia.Controls/Avalonia.Controls.csproj
  21. 7
      src/Avalonia.Controls/ButtonSpinner.cs
  22. 19
      src/Avalonia.Controls/TextBox.cs
  23. 2
      src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj
  24. 4
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  25. 2
      src/Avalonia.Desktop/Avalonia.Desktop.csproj
  26. 2
      src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj
  27. 2
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  28. 44
      src/Avalonia.Diagnostics/DevToolsExtensions.cs
  29. 47
      src/Avalonia.Diagnostics/Diagnostics/Behaviors/ColumnDefinition.cs
  30. 119
      src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
  31. 24
      src/Avalonia.Diagnostics/Diagnostics/Converters/GetTypeNameConverter.cs
  32. 17
      src/Avalonia.Diagnostics/Diagnostics/Convetions.cs
  33. 95
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  34. 16
      src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
  35. 17
      src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs
  36. 28
      src/Avalonia.Diagnostics/Diagnostics/KeyGestureExtesions.cs
  37. 29
      src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs
  38. 85
      src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
  39. 33
      src/Avalonia.Diagnostics/Diagnostics/TypeExtesnions.cs
  40. 13
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
  41. 12
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
  42. 54
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  43. 90
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
  44. 157
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  45. 13
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs
  46. 9
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  47. 14
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs
  48. 6
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  49. 80
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
  50. 36
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  51. 49
      src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
  52. 4
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
  53. 21
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  54. 7
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
  55. 71
      src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs
  56. 2
      src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
  57. 2
      src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
  58. 2
      src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj
  59. 2
      src/Avalonia.Headless/Avalonia.Headless.csproj
  60. 4
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  61. 2
      src/Avalonia.Input/Avalonia.Input.csproj
  62. 2
      src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
  63. 11
      src/Avalonia.Input/TouchDevice.cs
  64. 2
      src/Avalonia.Interactivity/Avalonia.Interactivity.csproj
  65. 2
      src/Avalonia.Layout/Avalonia.Layout.csproj
  66. 6
      src/Avalonia.MicroCom/MicroComRuntime.cs
  67. 5
      src/Avalonia.MicroCom/MicroComVtblBase.cs
  68. 7
      src/Avalonia.Native/Avalonia.Native.csproj
  69. 28
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  70. 2
      src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
  71. 2
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  72. 57
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  73. 52
      src/Avalonia.ReactiveUI/ViewModelViewHost.cs
  74. 2
      src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj
  75. 2
      src/Avalonia.Styling/Avalonia.Styling.csproj
  76. 2
      src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
  77. 1
      src/Avalonia.Themes.Default/ButtonSpinner.xaml
  78. 2
      src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
  79. 1
      src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml
  80. 96
      src/Avalonia.Themes.Fluent/FluentTheme.cs
  81. 6
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  82. 2
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  83. 4
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
  84. 10
      src/Avalonia.Visuals/Platform/IPlatformSettings.cs
  85. 2
      src/Avalonia.X11/Avalonia.X11.csproj
  86. 12
      src/Avalonia.X11/Stubs.cs
  87. 1
      src/Avalonia.X11/X11Platform.cs
  88. 2
      src/Avalonia.X11/X11Window.cs
  89. 6
      src/Directory.Build.props
  90. 2
      src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj
  91. 4
      src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
  92. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
  93. 9
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  94. 6
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  95. 2
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  96. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
  97. 2
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  98. 10
      src/Shared/ModuleInitializer.cs
  99. 2
      src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
  100. 2
      src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs

27
Avalonia.sln

@ -221,8 +221,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroComGenerator", "src\tools\MicroComGenerator\MicroComGenerator.csproj", "{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}"
@ -2027,30 +2025,6 @@ Global
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhone.Build.0 = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@ -2253,7 +2227,6 @@ Global
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098}

33
azure-pipelines.yml

@ -2,6 +2,33 @@ variables:
MSBuildEnableWorkloadResolver: 'false'
jobs:
- job: GetPRNumber
pool:
vmImage: 'windows-2022'
variables:
SolutionDir: '$(Build.SourcesDirectory)'
steps:
- task: PowerShell@2
displayName: Get PR Number
inputs:
targetType: 'inline'
script: |
$prId = $env:System_PullRequest_PullRequestNumber
Write-Host "PR Number is:-" $env:System_PullRequest_PullRequestNumber
if (!([string]::IsNullOrWhiteSpace($prId)))
{
Set-Content -Path $env:Build_ArtifactStagingDirectory\prId.txt -Value $prId
}
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'PRNumber'
publishLocation: 'Container'
- job: Linux
pool:
vmImage: 'ubuntu-20.04'
@ -58,8 +85,10 @@ jobs:
displayName: 'Generate avalonia-native'
inputs:
script: |
export PATH="`pwd`/sdk:$PATH"
cd src/tools/MicroComGenerator; dotnet run -f net6.0 -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h
export COREHOST_TRACE=0
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export DOTNET_CLI_TELEMETRY_OPTOUT=1
./build.sh --target GenerateCppHeaders --configuration Release
- task: Xcode@5
inputs:

35
build/MicroCom.targets

@ -1,35 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Ensure that code generator is actually built -->
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\tools\MicroComGenerator\MicroComGenerator.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<ExcludeAssets>all</ExcludeAssets>
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
<SetTargetFramework>TargetFramework=net6.0</SetTargetFramework>
</ProjectReference>
</ItemGroup>
<Target Name="GenerateAvaloniaNativeComInterop"
BeforeTargets="CoreCompile"
DependsOnTargets="ResolveReferences"
Inputs="@(AvnComIdl);$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/**/*.cs"
Outputs="%(AvnComIdl.OutputFile)">
<Message Importance="high" Text="Generating file %(AvnComIdl.OutputFile) from @(AvnComIdl)" />
<Exec Command="dotnet &quot;$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/bin/$(Configuration)/net6.0/MicroComGenerator.dll&quot; -i @(AvnComIdl) --cs %(AvnComIdl.OutputFile)"
LogStandardErrorAsError="true" />
<ItemGroup>
<!-- Remove and re-add generated file, this is needed for the clean build -->
<Compile Remove="%(AvnComIdl.OutputFile)"/>
<Compile Include="%(AvnComIdl.OutputFile)"/>
</ItemGroup>
</Target>
<ItemGroup>
<UpToDateCheckInput Include="@(AvnComIdl)"/>
<UpToDateCheckInput Include="$(MSBuildThisFileDirectory)/../src/tools/MicroComGenerator/**/*.cs"/>
</ItemGroup>
<PropertyGroup>
<_AvaloniaPatchComInterop>true</_AvaloniaPatchComInterop>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)/BuildTargets.targets" />
</Project>

8
nukebuild/MicroComGen.cs

@ -1,14 +1,14 @@
using System.IO;
using MicroComGenerator;
using MicroCom.CodeGenerator;
using Nuke.Common;
partial class Build : NukeBuild
{
Target GenerateCppHeaders => _ => _.Executes(() =>
{
var text = File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl");
var ast = AstParser.Parse(text);
var file = MicroComCodeGenerator.Parse(
File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"));
File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h",
CppGen.GenerateCpp(ast));
file.GenerateCppHeader());
});
}

6
nukebuild/_build.csproj

@ -15,7 +15,7 @@
<PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" />
<PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
<PackageReference Include="ILRepack.NETStandard" Version="2.0.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" />
<PackageReference Include="MicroCom.CodeGenerator" Version="0.10.4" />
<!-- Keep in sync with Avalonia.Build.Tasks -->
<PackageReference Include="Mono.Cecil" Version="0.11.2" />
</ItemGroup>
@ -37,10 +37,6 @@
<None Include="..\GitVersion.yml" Condition="Exists('..\GitVersion.yml')" />
<Compile Remove="Numerge/**/*.*" />
<Compile Include="Numerge/Numerge/**/*.cs" />
<Compile Include="..\src\tools\MicroComGenerator\**\*.cs" Exclude="..\src\tools\MicroComGenerator\obj\**">
<Link>MicroComGenerator\%(Filename)%(Extension)</Link>
</Compile>
<Compile Remove="..\src\tools\MicroComGenerator\Program.cs" />
</ItemGroup>
</Project>

2
packages/Avalonia/Avalonia.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net461;netcoreapp2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0;net461;netcoreapp2.0</TargetFrameworks>
<PackageId>Avalonia</PackageId>
</PropertyGroup>

38
samples/ControlCatalog/App.xaml.cs

@ -5,6 +5,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
using Avalonia.Themes.Fluent;
using ControlCatalog.ViewModels;
namespace ControlCatalog
@ -16,33 +17,17 @@ namespace ControlCatalog
DataContext = new ApplicationViewModel();
}
private static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
};
private static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
public static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Default.xaml")
};
public static Styles FluentDark = new Styles
{
new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml")
},
DataGridFluent
};
public static Styles FluentLight = new Styles
{
new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml")
},
DataGridFluent
};
public static FluentTheme Fluent = new FluentTheme(new Uri("avares://ControlCatalog/Styles"));
public static Styles DefaultLight = new Styles
{
@ -65,8 +50,7 @@ namespace ControlCatalog
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
},
DataGridDefault
}
};
public static Styles DefaultDark = new Styles
@ -90,14 +74,13 @@ namespace ControlCatalog
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
},
DataGridDefault
}
};
public override void Initialize()
{
Styles.Insert(0, FluentLight);
Styles.Insert(0, Fluent);
Styles.Insert(1, DataGridFluent);
AvaloniaXamlLoader.Load(this);
}
@ -111,6 +94,11 @@ namespace ControlCatalog
singleViewLifetime.MainView = new MainView();
base.OnFrameworkInitializationCompleted();
this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
{
StartupScreenIndex = 1,
});
}
}
}

1
samples/ControlCatalog/DecoratedWindow.xaml.cs

@ -11,7 +11,6 @@ namespace ControlCatalog
public DecoratedWindow()
{
this.InitializeComponent();
this.AttachDevTools();
}
void SetupSide(string name, StandardCursorType cursor, WindowEdge edge)

42
samples/ControlCatalog/MainView.xaml.cs

@ -3,14 +3,12 @@ using System.Collections;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Markup.Xaml.XamlIl;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using ControlCatalog.Pages;
using Avalonia.Themes.Fluent;
using ControlCatalog.Models;
using ControlCatalog.Pages;
namespace ControlCatalog
{
@ -43,14 +41,36 @@ namespace ControlCatalog
{
if (themes.SelectedItem is CatalogTheme theme)
{
Application.Current.Styles[0] = theme switch
var themeStyle = Application.Current.Styles[0];
if (theme == CatalogTheme.FluentLight)
{
if (App.Fluent.Mode != FluentThemeMode.Light)
{
App.Fluent.Mode = FluentThemeMode.Light;
}
Application.Current.Styles[0] = App.Fluent;
Application.Current.Styles[1] = App.DataGridFluent;
}
else if (theme == CatalogTheme.FluentDark)
{
if (App.Fluent.Mode != FluentThemeMode.Dark)
{
App.Fluent.Mode = FluentThemeMode.Dark;
}
Application.Current.Styles[0] = App.Fluent;
Application.Current.Styles[1] = App.DataGridFluent;
}
else if (theme == CatalogTheme.DefaultLight)
{
Application.Current.Styles[0] = App.DefaultLight;
Application.Current.Styles[1] = App.DataGridDefault;
}
else if (theme == CatalogTheme.DefaultDark)
{
CatalogTheme.FluentLight => App.FluentLight,
CatalogTheme.FluentDark => App.FluentDark,
CatalogTheme.DefaultLight => App.DefaultLight,
CatalogTheme.DefaultDark => App.DefaultDark,
_ => Application.Current.Styles[0]
};
Application.Current.Styles[0] = App.DefaultDark;
Application.Current.Styles[1] = App.DataGridDefault;
}
}
};

5
samples/ControlCatalog/MainWindow.xaml.cs

@ -17,10 +17,7 @@ namespace ControlCatalog
public MainWindow()
{
this.InitializeComponent();
this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
{
StartupScreenIndex = 1,
});
//Renderer.DrawFps = true;
//Renderer.DrawDirtyRects = Renderer.DrawFps = true;

12
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -33,8 +33,16 @@ namespace Avalonia.Android
{
public static readonly AndroidPlatform Instance = new AndroidPlatform();
public static AndroidPlatformOptions Options { get; private set; }
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(4, 4);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(200);
public Size DoubleClickSize => TouchDoubleClickSize;
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
public static void Initialize(Type appType, AndroidPlatformOptions options)
{

2
src/Avalonia.Animation/Avalonia.Animation.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />

3
src/Avalonia.Base/ApiCompatBaseline.txt

@ -0,0 +1,3 @@
Compat issues with assembly Avalonia.Base:
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Threading.IDispatcher.Post<T>(System.Action<T>, T, Avalonia.Threading.DispatcherPriority)' is present in the implementation but not in the contract.
Total Issues: 1

2
src/Avalonia.Base/Avalonia.Base.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AssemblyName>Avalonia.Base</AssemblyName>
<RootNamespace>Avalonia</RootNamespace>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>

9
src/Avalonia.Base/Threading/Dispatcher.cs

@ -81,7 +81,7 @@ namespace Avalonia.Threading
_ = action ?? throw new ArgumentNullException(nameof(action));
return _jobRunner.InvokeAsync(action, priority);
}
/// <inheritdoc/>
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal)
{
@ -110,6 +110,13 @@ namespace Avalonia.Threading
_jobRunner.Post(action, priority);
}
/// <inheritdoc/>
public void Post<T>(Action<T> action, T arg, DispatcherPriority priority = DispatcherPriority.Normal)
{
_ = action ?? throw new ArgumentNullException(nameof(action));
_jobRunner.Post(action, arg, priority);
}
/// <summary>
/// This is needed for platform backends that don't have internal priority system (e. g. win32)
/// To ensure that there are no jobs with higher priority

9
src/Avalonia.Base/Threading/IDispatcher.cs

@ -26,6 +26,15 @@ namespace Avalonia.Threading
/// <param name="priority">The priority with which to invoke the method.</param>
void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary>
/// Posts an action that will be invoked on the dispatcher thread.
/// </summary>
/// <typeparam name="T">type of argument</typeparam>
/// <param name="action">The method to call.</param>
/// <param name="arg">The argument of method to call.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
void Post<T>(Action<T> action, T arg, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary>
/// Invokes a action on the dispatcher thread.
/// </summary>

83
src/Avalonia.Base/Threading/JobRunner.cs

@ -13,7 +13,7 @@ namespace Avalonia.Threading
{
private IPlatformThreadingInterface? _platform;
private readonly Queue<IJob>[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1)
private readonly Queue<IJob>[] _queues = Enumerable.Range(0, (int)DispatcherPriority.MaxValue + 1)
.Select(_ => new Queue<IJob>()).ToArray();
public JobRunner(IPlatformThreadingInterface? platform)
@ -59,7 +59,7 @@ namespace Avalonia.Threading
/// <returns>A task that can be used to track the method's execution.</returns>
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority)
{
var job = new Job<TResult>(function, priority);
var job = new JobWithResult<TResult>(function, priority);
AddJob(job);
return job.Task;
}
@ -75,6 +75,17 @@ namespace Avalonia.Threading
AddJob(new Job(action, priority, true));
}
/// <summary>
/// Post action that will be invoked on main thread
/// </summary>
/// <param name="action">The method to call.</param>
/// <param name="parameter">The parameter of method to call.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
internal void Post<T>(Action<T> action, T parameter, DispatcherPriority priority)
{
AddJob(new Job<T>(action, parameter, priority, true));
}
/// <summary>
/// Allows unit tests to change the platform threading interface.
/// </summary>
@ -86,7 +97,7 @@ namespace Avalonia.Threading
private void AddJob(IJob job)
{
bool needWake;
var queue = _queues[(int) job.Priority];
var queue = _queues[(int)job.Priority];
lock (queue)
{
needWake = queue.Count == 0;
@ -98,7 +109,7 @@ namespace Avalonia.Threading
private IJob? GetNextJob(DispatcherPriority minimumPriority)
{
for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--)
for (int c = (int)DispatcherPriority.MaxValue; c >= (int)minimumPriority; c--)
{
var q = _queues[c];
lock (q)
@ -109,14 +120,14 @@ namespace Avalonia.Threading
}
return null;
}
private interface IJob
{
/// <summary>
/// Gets the job priority.
/// </summary>
DispatcherPriority Priority { get; }
/// <summary>
/// Runs the job.
/// </summary>
@ -177,11 +188,61 @@ namespace Avalonia.Threading
}
}
}
/// <summary>
/// A job to run.
/// A typed job to run.
/// </summary>
/// <typeparam name="T">Type of job parameter</typeparam>
private sealed class Job<T> : IJob
{
private readonly Action<T> _action;
private readonly T _parameter;
private readonly TaskCompletionSource<bool>? _taskCompletionSource;
/// <summary>
/// Initializes a new instance of the <see cref="Job"/> class.
/// </summary>
/// <param name="action">The method to call.</param>
/// <param name="parameter">The parameter of method to call.</param>
/// <param name="priority">The job priority.</param>
/// <param name="throwOnUiThread">Do not wrap exception in TaskCompletionSource</param>
public Job(Action<T> action, T parameter, DispatcherPriority priority, bool throwOnUiThread)
{
_action = action;
_parameter = parameter;
Priority = priority;
_taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<bool>();
}
/// <inheritdoc/>
public DispatcherPriority Priority { get; }
/// <inheritdoc/>
void IJob.Run()
{
if (_taskCompletionSource == null)
{
_action(_parameter);
return;
}
try
{
_action(_parameter);
_taskCompletionSource.SetResult(default);
}
catch (Exception e)
{
_taskCompletionSource.SetException(e);
}
}
}
/// <summary>
/// A job to run thath return value.
/// </summary>
private sealed class Job<TResult> : IJob
/// <typeparam name="TResult">Type of job result</typeparam>
private sealed class JobWithResult<TResult> : IJob
{
private readonly Func<TResult> _function;
private readonly TaskCompletionSource<TResult> _taskCompletionSource;
@ -191,7 +252,7 @@ namespace Avalonia.Threading
/// </summary>
/// <param name="function">The method to call.</param>
/// <param name="priority">The job priority.</param>
public Job(Func<TResult> function, DispatcherPriority priority)
public JobWithResult(Func<TResult> function, DispatcherPriority priority)
{
_function = function;
Priority = priority;
@ -200,7 +261,7 @@ namespace Avalonia.Threading
/// <inheritdoc/>
public DispatcherPriority Priority { get; }
/// <summary>
/// The task.
/// </summary>

2
src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs

@ -180,7 +180,7 @@ namespace Avalonia.Utilities
{
var r = _data[c];
if (r?.TryGetTarget(out var sub) == true)
sub.OnEvent(sender, eventArgs);
sub!.OnEvent(sender, eventArgs);
else
needCompact = true;
}

2
src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<PackageId>Avalonia.Controls.DataGrid</PackageId>
</PropertyGroup>
<ItemGroup>

2
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" />

7
src/Avalonia.Controls/ButtonSpinner.cs

@ -1,4 +1,4 @@
using System;
using System;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
@ -196,13 +196,14 @@ namespace Avalonia.Controls
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
base.OnPointerWheelChanged(e);
if (!e.Handled && AllowSpin)
if (AllowSpin && IsKeyboardFocusWithin)
{
if (e.Delta.Y != 0)
{
var spinnerEventArgs = new SpinEventArgs(SpinEvent, (e.Delta.Y < 0) ? SpinDirection.Decrease : SpinDirection.Increase, true);
OnSpin(spinnerEventArgs);
e.Handled = spinnerEventArgs.Handled;
e.Handled = true;
}
}
}

19
src/Avalonia.Controls/TextBox.cs

@ -1006,13 +1006,28 @@ namespace Avalonia.Controls
if (text != null && clickInfo.Properties.IsLeftButtonPressed && !(clickInfo.Pointer?.Captured is Border))
{
var point = e.GetPosition(_presenter);
var index = CaretIndex = _presenter.GetCaretIndex(point);
var index = _presenter.GetCaretIndex(point);
var clickToSelect = index != CaretIndex && e.KeyModifiers.HasFlag(KeyModifiers.Shift);
if (!clickToSelect)
{
CaretIndex = index;
}
#pragma warning disable CS0618 // Type or member is obsolete
switch (e.ClickCount)
#pragma warning restore CS0618 // Type or member is obsolete
{
case 1:
SelectionStart = SelectionEnd = index;
if (clickToSelect)
{
SelectionStart = Math.Min(index, CaretIndex);
SelectionEnd = Math.Max(index, CaretIndex);
}
else
{
SelectionStart = SelectionEnd = index;
}
break;
case 2:
if (!StringUtils.IsStartOfWord(text, index))

2
src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<!-- WARNING! The designer support version number needs to be frozen
To allow projects that implement designer functionality to still
work with newer versions of Avalonia. This version number only

4
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -64,5 +64,9 @@ namespace Avalonia.DesignerSupport.Remote
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16, 16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
}

2
src/Avalonia.Desktop/Avalonia.Desktop.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<PackageId>Avalonia.Desktop</PackageId>
</PropertyGroup>

2
src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;net461;netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

2
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<RootNamespace>Avalonia</RootNamespace>
<PackageId>Avalonia.Diagnostics</PackageId>
</PropertyGroup>

44
src/Avalonia.Diagnostics/DevToolsExtensions.cs

@ -37,5 +37,49 @@ namespace Avalonia
{
DevTools.Attach(root, options);
}
/// <summary>
/// Attaches DevTools to a Application, to be opened with the specified options.
/// </summary>
/// <param name="application">The Application to attach DevTools to.</param>
public static void AttachDevTools(this Application application)
{
DevTools.Attach(application, new DevToolsOptions());
}
/// <summary>
/// Attaches DevTools to a Application, to be opened with the specified options.
/// </summary>
/// <param name="application">The Application to attach DevTools to.</param>
/// <param name="options">Additional settings of DevTools.</param>
/// <remarks>
/// Attach DevTools should only be called after application initialization is complete. A good point is <see cref="Application.OnFrameworkInitializationCompleted"/>
/// </remarks>
/// <example>
/// <code>
/// public class App : Application
/// {
/// public override void OnFrameworkInitializationCompleted()
/// {
/// if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
/// {
/// desktopLifetime.MainWindow = new MainWindow();
/// }
/// else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
/// singleViewLifetime.MainView = new MainView();
///
/// base.OnFrameworkInitializationCompleted();
/// this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
/// {
/// StartupScreenIndex = 1,
/// });
/// }
/// }
/// </code>
/// </example>
public static void AttachDevTools(this Application application, DevToolsOptions options)
{
DevTools.Attach(application, options);
}
}
}

47
src/Avalonia.Diagnostics/Diagnostics/Behaviors/ColumnDefinition.cs

@ -0,0 +1,47 @@
namespace Avalonia.Diagnostics.Behaviors
{
/// <summary>
/// See discussion https://github.com/AvaloniaUI/Avalonia/discussions/6773
/// </summary>
static class ColumnDefinition
{
private readonly static Avalonia.Controls.GridLength ZeroWidth =
new Avalonia.Controls.GridLength(0, Avalonia.Controls.GridUnitType.Pixel);
private readonly static AttachedProperty<Avalonia.Controls.GridLength?> LastWidthProperty =
AvaloniaProperty.RegisterAttached<Avalonia.Controls.ColumnDefinition, Avalonia.Controls.GridLength?>("LastWidth"
, typeof(ColumnDefinition)
, default);
public readonly static AttachedProperty<bool> IsVisibleProperty =
AvaloniaProperty.RegisterAttached<Avalonia.Controls.ColumnDefinition, bool>("IsVisible"
, typeof(ColumnDefinition)
, true
, coerce: (element, visibility) =>
{
var lastWidth = element.GetValue(LastWidthProperty);
if (visibility == true && lastWidth is { })
{
element.SetValue(Avalonia.Controls.ColumnDefinition.WidthProperty, lastWidth);
}
else if (visibility == false)
{
element.SetValue(LastWidthProperty, element.GetValue(Avalonia.Controls.ColumnDefinition.WidthProperty));
element.SetValue(Avalonia.Controls.ColumnDefinition.WidthProperty, ZeroWidth);
}
return visibility;
}
);
public static bool GetIsVisible(Avalonia.Controls.ColumnDefinition columnDefinition)
{
return columnDefinition.GetValue(IsVisibleProperty);
}
public static void SetIsVisible(Avalonia.Controls.ColumnDefinition columnDefinition, bool visibility)
{
columnDefinition.SetValue(IsVisibleProperty, visibility);
}
}
}

119
src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs

@ -0,0 +1,119 @@
using System;
using Avalonia.Controls;
using Lifetimes = Avalonia.Controls.ApplicationLifetimes;
using App = Avalonia.Application;
namespace Avalonia.Diagnostics.Controls
{
class Application : AvaloniaObject
, Input.ICloseable
{
private readonly App _application;
private static readonly Version s_version = typeof(IAvaloniaObject).Assembly?.GetName()?.Version
?? Version.Parse("0.0.00");
public event EventHandler? Closed;
public Application(App application)
{
_application = application;
if (_application.ApplicationLifetime is Lifetimes.IControlledApplicationLifetime controller)
{
EventHandler<Lifetimes.ControlledApplicationLifetimeExitEventArgs> eh = default!;
eh = (s, e) =>
{
controller.Exit -= eh;
Closed?.Invoke(s, e);
};
controller.Exit += eh;
}
RendererRoot = application.ApplicationLifetime switch
{
Lifetimes.IClassicDesktopStyleApplicationLifetime classic => classic.MainWindow.Renderer,
Lifetimes.ISingleViewApplicationLifetime single => (single.MainView as VisualTree.IVisual)?.VisualRoot?.Renderer,
_ => null
};
}
internal App Instance => _application;
/// <summary>
/// Defines the <see cref="DataContext"/> property.
/// </summary>
public object? DataContext =>
_application.DataContext;
/// <summary>
/// Gets or sets the application's global data templates.
/// </summary>
/// <value>
/// The application's global data templates.
/// </value>
public Avalonia.Controls.Templates.DataTemplates DataTemplates =>
_application.DataTemplates;
/// <summary>
/// Gets the application's focus manager.
/// </summary>
/// <value>
/// The application's focus manager.
/// </value>
public Input.IFocusManager? FocusManager =>
_application.FocusManager;
/// <summary>
/// Gets the application's input manager.
/// </summary>
/// <value>
/// The application's input manager.
/// </value>
public Input.InputManager? InputManager =>
_application.InputManager;
/// <summary>
/// Gets the application clipboard.
/// </summary>
public Input.Platform.IClipboard? Clipboard =>
_application.Clipboard;
/// <summary>
/// Gets the application's global resource dictionary.
/// </summary>
public IResourceDictionary Resources =>
_application.Resources;
/// <summary>
/// Gets the application's global styles.
/// </summary>
/// <value>
/// The application's global styles.
/// </value>
/// <remarks>
/// Global styles apply to all windows in the application.
/// </remarks>
public Styling.Styles Styles =>
_application.Styles;
/// <summary>
/// Application lifetime, use it for things like setting the main window and exiting the app from code
/// Currently supported lifetimes are:
/// - <see cref="Lifetimes.IClassicDesktopStyleApplicationLifetime"/>
/// - <see cref="Lifetimes.ISingleViewApplicationLifetime"/>
/// - <see cref="Lifetimes.IControlledApplicationLifetime"/>
/// </summary>
public Lifetimes.IApplicationLifetime? ApplicationLifetime =>
_application.ApplicationLifetime;
/// <summary>
/// Application name to be used for various platform-specific purposes
/// </summary>
public string? Name =>
_application.Name;
/// <summary>
/// Gets the root of the visual tree, if the control is attached to a visual tree.
/// </summary>
internal Rendering.IRenderer? RendererRoot { get; }
}
}

24
src/Avalonia.Diagnostics/Diagnostics/Converters/GetTypeNameConverter.cs

@ -0,0 +1,24 @@
using System;
using System.Globalization;
using Avalonia.Data;
using Avalonia.Data.Converters;
namespace Avalonia.Diagnostics.Converters
{
internal class GetTypeNameConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Type type)
{
return type.GetTypeName();
}
return BindingOperations.DoNothing;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return BindingOperations.DoNothing;
}
}
}

17
src/Avalonia.Diagnostics/Diagnostics/Convetions.cs

@ -0,0 +1,17 @@
using System;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics
{
static class Convetions
{
public static string DefaultScreenshotsRoot =>
System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create),
"Screenshots");
public static IScreenshotHandler DefaultScreenshotHandler { get; } =
new Screenshots.FilePickerHandler();
}
}

95
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@ -1,17 +1,21 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Diagnostics.Views;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
namespace Avalonia.Diagnostics
{
public static class DevTools
{
private static readonly Dictionary<TopLevel, MainWindow> s_open =
new Dictionary<TopLevel, MainWindow>();
private static readonly Dictionary<AvaloniaObject, MainWindow> s_open =
new Dictionary<AvaloniaObject, MainWindow>();
private static bool s_attachedToApplication;
public static IDisposable Attach(TopLevel root, KeyGesture gesture)
{
@ -23,6 +27,11 @@ namespace Avalonia.Diagnostics
public static IDisposable Attach(TopLevel root, DevToolsOptions options)
{
if (s_attachedToApplication == true)
{
throw new ArgumentException("DevTools already attached to application", nameof(root));
}
void PreviewKeyDown(object? sender, KeyEventArgs e)
{
if (options.Gesture.Matches(e))
@ -37,45 +46,95 @@ namespace Avalonia.Diagnostics
RoutingStrategies.Tunnel);
}
public static IDisposable Open(TopLevel root) => Open(root, new DevToolsOptions());
public static IDisposable Open(TopLevel root) =>
Open(Application.Current,new DevToolsOptions(),root as Window);
public static IDisposable Open(TopLevel root, DevToolsOptions options) =>
Open(Application.Current, options, root as Window);
public static IDisposable Open(TopLevel root, DevToolsOptions options)
private static void DevToolsClosed(object? sender, EventArgs e)
{
if (s_open.TryGetValue(root, out var window))
var window = (MainWindow)sender!;
window.Closed -= DevToolsClosed;
if (window.Root is Controls.Application host)
{
s_open.Remove(host.Instance);
}
else
{
s_open.Remove(window.Root!);
}
}
internal static IDisposable Attach(Application? application, DevToolsOptions options, Window? owner = null)
{
if (application is null)
{
throw new ArgumentNullException(nameof(application));
}
var result = Disposable.Empty;
// Skip if call on Design Mode
if (!Avalonia.Controls.Design.IsDesignMode
&& !s_attachedToApplication)
{
var lifeTime = application.ApplicationLifetime
as Avalonia.Controls.ApplicationLifetimes.IControlledApplicationLifetime;
if (lifeTime is null)
{
throw new ArgumentNullException(nameof(Application.ApplicationLifetime));
}
if (application.InputManager is { })
{
s_attachedToApplication = true;
application.InputManager.PreProcess.OfType<RawKeyEventArgs>().Subscribe(e =>
{
if (options.Gesture.Matches(e))
{
result = Open(application, options, owner);
}
});
}
}
return result;
}
private static IDisposable Open(Application? application, DevToolsOptions options, Window? owner = default)
{
if (application is null)
{
throw new ArgumentNullException(nameof(application));
}
if (s_open.TryGetValue(application, out var window))
{
window.Activate();
}
else
{
window = new MainWindow
{
Root = root,
Root = new Controls.Application(application),
Width = options.Size.Width,
Height = options.Size.Height,
};
window.SetOptions(options);
window.Closed += DevToolsClosed;
s_open.Add(root, window);
if (options.ShowAsChildWindow && root is Window inspectedWindow)
s_open.Add(application, window);
if (options.ShowAsChildWindow && owner is { })
{
window.Show(inspectedWindow);
window.Show(owner);
}
else
{
window.Show();
}
}
return Disposable.Create(() => window?.Close());
}
private static void DevToolsClosed(object? sender, EventArgs e)
{
var window = (MainWindow)sender!;
s_open.Remove(window.Root!);
window.Closed -= DevToolsClosed;
}
}
}

16
src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs

@ -1,4 +1,5 @@
using Avalonia.Input;
using System;
using Avalonia.Input;
namespace Avalonia.Diagnostics
{
@ -16,6 +17,7 @@ namespace Avalonia.Diagnostics
/// Gets or sets a value indicating whether DevTools should be displayed as a child window
/// of the window being inspected. The default value is true.
/// </summary>
/// <remarks>This setting is ignored if DevTools is attached to <see cref="Application"/></remarks>
public bool ShowAsChildWindow { get; set; } = true;
/// <summary>
@ -27,5 +29,17 @@ namespace Avalonia.Diagnostics
/// Get or set the startup screen index where the DevTools window will be displayed.
/// </summary>
public int? StartupScreenIndex { get; set; }
/// <summary>
/// Gets or sets a value indicating whether DevTools should be displayed implemented interfaces on Control details. The default value is true.
/// </summary>
public bool ShowImplementedInterfaces { get; set; } = true;
/// <summary>
/// Allow to customizze SreenshotHandler
/// </summary>
/// <remarks>Default handler is <see cref="Screenshots.FilePickerHandler"/></remarks>
public IScreenshotHandler ScreenshotHandler { get; set; }
= Convetions.DefaultScreenshotHandler;
}
}

17
src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs

@ -0,0 +1,17 @@
using System.Threading.Tasks;
using Avalonia.Controls;
namespace Avalonia.Diagnostics
{
/// <summary>
/// Allowed to define custom handler for Shreeshot
/// </summary>
public interface IScreenshotHandler
{
/// <summary>
/// Handle the Screenshot
/// </summary>
/// <returns></returns>
Task Take(IControl control);
}
}

28
src/Avalonia.Diagnostics/Diagnostics/KeyGestureExtesions.cs

@ -0,0 +1,28 @@
using Avalonia.Input;
using Avalonia.Input.Raw;
namespace Avalonia.Diagnostics
{
static class KeyGestureExtesions
{
public static bool Matches(this KeyGesture gesture, RawKeyEventArgs keyEvent) =>
keyEvent != null &&
(KeyModifiers)(keyEvent.Modifiers & RawInputModifiers.KeyboardMask) == gesture.KeyModifiers &&
ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(gesture.Key);
private static Key ResolveNumPadOperationKey(Key key)
{
switch (key)
{
case Key.Add:
return Key.OemPlus;
case Key.Subtract:
return Key.OemMinus;
case Key.Decimal:
return Key.OemPeriod;
default:
return key;
}
}
}
}

29
src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs

@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Avalonia.Controls;
namespace Avalonia.Diagnostics.Screenshots
{
/// <summary>
/// Base class for render Screenshto to stream
/// </summary>
public abstract class BaseRenderToStreamHandler : IScreenshotHandler
{
/// <summary>
/// Get stream
/// </summary>
/// <param name="control"></param>
/// <returns>stream to render the control</returns>
protected abstract Task<System.IO.Stream?> GetStream(IControl control);
public async Task Take(IControl control)
{
using var output = await GetStream(control);
if (output is { })
{
control.RenderTo(output);
await output.FlushAsync();
}
}
}
}

85
src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs

@ -0,0 +1,85 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Lifetimes = Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Diagnostics.Screenshots
{
/// <summary>
/// Show a FileSavePicker to select where save screenshot
/// </summary>
public sealed class FilePickerHandler : BaseRenderToStreamHandler
{
/// <summary>
/// Instance FilePickerHandler
/// </summary>
public FilePickerHandler()
{
}
/// <summary>
/// Instance FilePickerHandler with specificated parameter
/// </summary>
/// <param name="title">SaveFilePicker Title</param>
/// <param name="screenshotRoot"></param>
public FilePickerHandler(string? title
, string? screenshotRoot = default
)
{
if (title is { })
Title = title;
if (screenshotRoot is { })
ScreenshotsRoot = screenshotRoot;
}
/// <summary>
/// Get the root folder where screeshots well be stored.
/// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots.
/// </summary>
public string ScreenshotsRoot { get; }
= Convetions.DefaultScreenshotsRoot;
/// <summary>
/// SaveFilePicker Title
/// </summary>
public string Title { get; } = "Save Screenshot to ...";
Window GetWindow(IControl control)
{
var window = control.VisualRoot as Window;
var app = Application.Current;
if (app?.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime desktop)
{
window = desktop.Windows.FirstOrDefault(w => w is Views.MainWindow);
}
return window!;
}
protected async override Task<Stream?> GetStream(IControl control)
{
Stream? output = default;
var result = await new SaveFileDialog()
{
Title = Title,
Filters = new() { new FileDialogFilter() { Name = "PNG", Extensions = new() { "png" } } },
Directory = ScreenshotsRoot,
}.ShowAsync(GetWindow(control));
if (!string.IsNullOrWhiteSpace(result))
{
var foldler = Path.GetDirectoryName(result);
// Directory information for path, or null if path denotes a root directory or is
// null. Returns System.String.Empty if path does not contain directory information.
if (!string.IsNullOrWhiteSpace(foldler))
{
if (!Directory.Exists(foldler))
{
Directory.CreateDirectory(foldler);
}
output = new FileStream(result, FileMode.Create);
}
}
return output;
}
}
}

33
src/Avalonia.Diagnostics/Diagnostics/TypeExtesnions.cs

@ -0,0 +1,33 @@
using System;
using System.Runtime.CompilerServices;
using System.Linq;
namespace Avalonia.Diagnostics
{
internal static class TypeExtesnions
{
private static readonly ConditionalWeakTable<Type, string> s_getTypeNameCache =
new ConditionalWeakTable<Type, string>();
public static string GetTypeName(this Type type)
{
if (!s_getTypeNameCache.TryGetValue(type, out var name))
{
name = type.Name;
if (Nullable.GetUnderlyingType(type) is Type nullable)
{
name = nullable.Name + "?";
}
else if (type.IsGenericType)
{
var definition = type.GetGenericTypeDefinition();
var arguments = type.GetGenericArguments();
name = definition.Name.Substring(0, definition.Name.IndexOf('`'));
name = $"{name}<{string.Join(",", arguments.Select(GetTypeName))}>";
}
s_getTypeNameCache.Add(type, name);
}
return name;
}
}
}

13
src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs

@ -3,10 +3,11 @@ namespace Avalonia.Diagnostics.ViewModels
internal class AvaloniaPropertyViewModel : PropertyViewModel
{
private readonly AvaloniaObject _target;
private System.Type _type;
private System.Type _assignedType;
private object? _value;
private string _priority;
private string _group;
private readonly System.Type _propertyType;
#nullable disable
// Remove "nullable disable" after MemberNotNull will work on our CI.
@ -20,6 +21,7 @@ namespace Avalonia.Diagnostics.ViewModels
$"[{property.OwnerType.Name}.{property.Name}]" :
property.Name;
DeclaringType = property.OwnerType;
_propertyType = property.PropertyType;
Update();
}
@ -32,7 +34,7 @@ namespace Avalonia.Diagnostics.ViewModels
public override string Priority =>
_priority;
public override System.Type Type => _type;
public override System.Type AssignedType => _assignedType;
public override string? Value
{
@ -43,6 +45,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
var convertedValue = ConvertFromString(value, Property.PropertyType);
_target.SetValue(Property, convertedValue);
Update();
}
catch { }
}
@ -51,6 +54,7 @@ namespace Avalonia.Diagnostics.ViewModels
public override string Group => _group;
public override System.Type? DeclaringType { get; }
public override System.Type PropertyType => _propertyType;
// [MemberNotNull(nameof(_type), nameof(_group), nameof(_priority))]
public override void Update()
@ -58,7 +62,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (Property.IsDirect)
{
RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value));
RaiseAndSetIfChanged(ref _type, _value?.GetType() ?? Property.PropertyType, nameof(Type));
RaiseAndSetIfChanged(ref _assignedType,_value?.GetType() ?? Property.PropertyType, nameof(AssignedType));
RaiseAndSetIfChanged(ref _priority, "Direct", nameof(Priority));
_group = "Properties";
@ -68,7 +72,7 @@ namespace Avalonia.Diagnostics.ViewModels
var val = _target.GetDiagnostic(Property);
RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value));
RaiseAndSetIfChanged(ref _type, _value?.GetType() ?? Property.PropertyType, nameof(Type));
RaiseAndSetIfChanged(ref _assignedType, _value?.GetType() ?? Property.PropertyType, nameof(AssignedType));
if (val != null)
{
@ -81,6 +85,7 @@ namespace Avalonia.Diagnostics.ViewModels
RaiseAndSetIfChanged(ref _group, "Unset", nameof(Group));
}
}
RaisePropertyChanged(nameof(Type));
}
}
}

12
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs

@ -5,8 +5,9 @@ namespace Avalonia.Diagnostics.ViewModels
internal class ClrPropertyViewModel : PropertyViewModel
{
private readonly object _target;
private System.Type _type;
private System.Type _assignedType;
private object? _value;
private readonly System.Type _propertyType;
#nullable disable
// Remove "nullable disable" after MemberNotNull will work on our CI.
@ -25,6 +26,8 @@ namespace Avalonia.Diagnostics.ViewModels
Name = property.DeclaringType.Name + '.' + property.Name;
}
DeclaringType = property.DeclaringType;
_propertyType = property.PropertyType;
Update();
}
@ -33,7 +36,8 @@ namespace Avalonia.Diagnostics.ViewModels
public override string Name { get; }
public override string Group => "CLR Properties";
public override System.Type Type => _type;
public override System.Type AssignedType => _assignedType;
public override System.Type PropertyType => _propertyType;
public override string? Value
{
@ -44,6 +48,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
var convertedValue = ConvertFromString(value, Property.PropertyType);
Property.SetValue(_target, convertedValue);
Update();
}
catch { }
}
@ -62,7 +67,8 @@ namespace Avalonia.Diagnostics.ViewModels
{
var val = Property.GetValue(_target);
RaiseAndSetIfChanged(ref _value, val, nameof(Value));
RaiseAndSetIfChanged(ref _type, _value?.GetType() ?? Property.PropertyType, nameof(Type));
RaiseAndSetIfChanged(ref _assignedType, _value?.GetType() ?? Property.PropertyType, nameof(AssignedType));
RaisePropertyChanged(nameof(Type));
}
}
}

54
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -16,7 +16,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
internal class ControlDetailsViewModel : ViewModelBase, IDisposable
{
private readonly IVisual _control;
private readonly IAvaloniaObject _avaloniaObject;
private IDictionary<object, List<PropertyViewModel>>? _propertyIndex;
private PropertyViewModel? _selectedProperty;
private DataGridCollectionView? _propertiesView;
@ -27,21 +27,23 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly Stack<(string Name,object Entry)> _selectedEntitiesStack = new();
private string? _selectedEntityName;
private string? _selectedEntityType;
private bool _showImplementedInterfaces;
public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control)
public ControlDetailsViewModel(TreePageViewModel treePage, IAvaloniaObject avaloniaObject)
{
_control = control;
_avaloniaObject = avaloniaObject;
TreePage = treePage;
Layout = new ControlLayoutViewModel(control);
TreePage = treePage;
Layout = avaloniaObject is IVisual
? new ControlLayoutViewModel((IVisual)avaloniaObject)
: default;
NavigateToProperty(control, (control as IControl)?.Name ?? control.ToString());
NavigateToProperty(_avaloniaObject, (_avaloniaObject as IControl)?.Name ?? _avaloniaObject.ToString());
AppliedStyles = new ObservableCollection<StyleViewModel>();
PseudoClasses = new ObservableCollection<PseudoClassViewModel>();
if (control is StyledElement styledElement)
if (avaloniaObject is StyledElement styledElement)
{
styledElement.Classes.CollectionChanged += OnClassesChanged;
@ -181,7 +183,7 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _styleStatus, value);
}
public ControlLayoutViewModel Layout { get; }
public ControlLayoutViewModel? Layout { get; }
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
@ -215,17 +217,17 @@ namespace Avalonia.Diagnostics.ViewModels
public void Dispose()
{
if (_control is INotifyPropertyChanged inpc)
if (_avaloniaObject is INotifyPropertyChanged inpc)
{
inpc.PropertyChanged -= ControlPropertyChanged;
}
if (_control is AvaloniaObject ao)
if (_avaloniaObject is AvaloniaObject ao)
{
ao.PropertyChanged -= ControlPropertyChanged;
}
if (_control is StyledElement se)
if (_avaloniaObject is StyledElement se)
{
se.Classes.CollectionChanged -= OnClassesChanged;
}
@ -245,18 +247,21 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
private IEnumerable<PropertyViewModel> GetClrProperties(object o)
private IEnumerable<PropertyViewModel> GetClrProperties(object o, bool showImplementedInterfaces)
{
foreach (var p in GetClrProperties(o, o.GetType()))
{
yield return p;
}
foreach (var i in o.GetType().GetInterfaces())
if (showImplementedInterfaces)
{
foreach (var p in GetClrProperties(o, i))
foreach (var i in o.GetType().GetInterfaces())
{
yield return p;
foreach (var p in GetClrProperties(o, i))
{
yield return p;
}
}
}
}
@ -278,7 +283,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
Layout.ControlPropertyChanged(sender, e);
Layout?.ControlPropertyChanged(sender, e);
}
private void ControlPropertyChanged(object? sender, PropertyChangedEventArgs e)
@ -405,8 +410,8 @@ namespace Avalonia.Diagnostics.ViewModels
var selectedEntityName = SelectedEntityName;
if (selectedEntity == null
|| selectedProperty == null
|| selectedProperty.Type == typeof(string)
|| selectedProperty.Type.IsValueType
|| selectedProperty.PropertyType == typeof(string)
|| selectedProperty.PropertyType.IsValueType
)
return;
@ -420,7 +425,7 @@ namespace Avalonia.Diagnostics.ViewModels
property = selectedEntity.GetType().GetProperties()
.FirstOrDefault(pi => pi.Name == selectedProperty.Name
&& pi.DeclaringType == selectedProperty.DeclaringType
&& pi.PropertyType.Name == selectedProperty.Type.Name)
&& pi.PropertyType.Name == selectedProperty.PropertyType.Name)
?.GetValue(selectedEntity);
}
if (property == null) return;
@ -453,7 +458,7 @@ namespace Avalonia.Diagnostics.ViewModels
SelectedEntityName = entityName;
SelectedEntityType = o.ToString();
var properties = GetAvaloniaProperties(o)
.Concat(GetClrProperties(o))
.Concat(GetClrProperties(o, _showImplementedInterfaces))
.OrderBy(x => x, PropertyComparer.Instance)
.ThenBy(x => x.Name)
.ToList();
@ -474,5 +479,12 @@ namespace Avalonia.Diagnostics.ViewModels
inpc2.PropertyChanged += ControlPropertyChanged;
}
}
internal void UpdatePropertiesView(bool showImplementedInterfaces)
{
_showImplementedInterfaces = showImplementedInterfaces;
SelectedProperty = null;
NavigateToProperty(_avaloniaObject, (_avaloniaObject as IControl)?.Name ?? _avaloniaObject.ToString());
}
}
}

90
src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs

@ -1,23 +1,31 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Lifetimes = Avalonia.Controls.ApplicationLifetimes;
using System.Linq;
namespace Avalonia.Diagnostics.ViewModels
{
internal class LogicalTreeNode : TreeNode
{
public LogicalTreeNode(ILogical logical, TreeNode? parent)
: base((Control)logical, parent)
public LogicalTreeNode(IAvaloniaObject avaloniaObject, TreeNode? parent)
: base(avaloniaObject, parent)
{
Children = new LogicalTreeNodeCollection(this, logical);
Children = avaloniaObject switch
{
ILogical logical => new LogicalTreeNodeCollection(this, logical),
Controls.Application host => new ApplicationHostLogical(this, host),
_ => TreeNodeCollection.Empty
};
}
public override TreeNodeCollection Children { get; }
public static LogicalTreeNode[] Create(object control)
{
var logical = control as ILogical;
var logical = control as IAvaloniaObject;
return logical != null ? new[] { new LogicalTreeNode(logical, null) } : Array.Empty<LogicalTreeNode>();
}
@ -41,10 +49,82 @@ namespace Avalonia.Diagnostics.ViewModels
protected override void Initialize(AvaloniaList<TreeNode> nodes)
{
_subscription = _control.LogicalChildren.ForEachItem(
(i, item) => nodes.Insert(i, new LogicalTreeNode(item, Owner)),
(i, item) => nodes.Insert(i, new LogicalTreeNode((IAvaloniaObject)item, Owner)),
(i, item) => nodes.RemoveAt(i),
() => nodes.Clear());
}
}
internal class ApplicationHostLogical : TreeNodeCollection
{
readonly Controls.Application _application;
CompositeDisposable _subscriptions = new CompositeDisposable(2);
public ApplicationHostLogical(TreeNode owner, Controls.Application host) :
base(owner)
{
_application = host;
}
protected override void Initialize(AvaloniaList<TreeNode> nodes)
{
if (_application.ApplicationLifetime is Lifetimes.ISingleViewApplicationLifetime single)
{
nodes.Add(new LogicalTreeNode(single.MainView, Owner));
}
if (_application.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime classic)
{
for (int i = 0; i < classic.Windows.Count; i++)
{
var window = classic.Windows[i];
if (window is Views.MainWindow)
{
continue;
}
nodes.Add(new LogicalTreeNode(window, Owner));
}
_subscriptions = new System.Reactive.Disposables.CompositeDisposable()
{
Window.WindowOpenedEvent.AddClassHandler(typeof(Window), (s,e)=>
{
if (s is Views.MainWindow)
{
return;
}
nodes.Add(new LogicalTreeNode((IAvaloniaObject)s!,Owner));
}),
Window.WindowClosedEvent.AddClassHandler(typeof(Window), (s,e)=>
{
if (s is Views.MainWindow)
{
return;
}
var item = nodes.FirstOrDefault(node=>object.ReferenceEquals(node.Visual,s));
if(!(item is null))
{
nodes.Remove(item);
}
if(nodes.Count == 0)
{
if (Avalonia.Application.Current?.ApplicationLifetime is Lifetimes.IControlledApplicationLifetime controller)
{
controller.Shutdown();
}
else
{
Environment.Exit(0);
}
}
}),
};
}
}
public override void Dispose()
{
_subscriptions?.Dispose();
base.Dispose();
}
}
}
}

157
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -1,15 +1,20 @@
using System;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
using Avalonia.Input;
using Avalonia.Metadata;
using Avalonia.Threading;
using System.Reactive.Linq;
using System.Linq;
namespace Avalonia.Diagnostics.ViewModels
{
internal class MainViewModel : ViewModelBase, IDisposable
{
private readonly TopLevel _root;
private readonly AvaloniaObject _root;
private readonly TreePageViewModel _logicalTree;
private readonly TreePageViewModel _visualTree;
private readonly EventsPageViewModel _events;
@ -17,13 +22,18 @@ namespace Avalonia.Diagnostics.ViewModels
private ViewModelBase? _content;
private int _selectedTab;
private string? _focusedControl;
private string? _pointerOverElement;
private IInputElement? _pointerOverElement;
private bool _shouldVisualizeMarginPadding = true;
private bool _shouldVisualizeDirtyRects;
private bool _showFpsOverlay;
private bool _freezePopups;
public MainViewModel(TopLevel root)
private string? _pointerOverElementName;
private IInputRoot? _pointerOverRoot;
private IScreenshotHandler? _screenshotHandler;
private bool _showPropertyType;
private bool _showImplementedInterfaces;
public MainViewModel(AvaloniaObject root)
{
_root = root;
_logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root));
@ -35,8 +45,24 @@ namespace Avalonia.Diagnostics.ViewModels
if (KeyboardDevice.Instance is not null)
KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged;
SelectedTab = 0;
_pointerOverSubscription = root.GetObservable(TopLevel.PointerOverElementProperty)
.Subscribe(x => PointerOverElement = x?.GetType().Name);
if (root is TopLevel topLevel)
{
_pointerOverSubscription = topLevel.GetObservable(TopLevel.PointerOverElementProperty)
.Subscribe(x => PointerOverElement = x);
}
else
{
#nullable disable
_pointerOverSubscription = InputManager.Instance.PreProcess
.OfType<Input.Raw.RawPointerEventArgs>()
.Subscribe(e =>
{
PointerOverRoot = e.Root;
PointerOverElement = e.Root.GetInputElementsAt(e.Position).FirstOrDefault();
});
#nullable restore
}
Console = new ConsoleViewModel(UpdateConsoleContext);
}
@ -51,13 +77,26 @@ namespace Avalonia.Diagnostics.ViewModels
get => _shouldVisualizeMarginPadding;
set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value);
}
public bool ShouldVisualizeDirtyRects
{
get => _shouldVisualizeDirtyRects;
set
{
_root.Renderer.DrawDirtyRects = value;
var changed = true;
if (_root is TopLevel topLevel && topLevel.Renderer is { })
{
topLevel.Renderer.DrawDirtyRects = value;
}
else if (_root is Controls.Application app && app.RendererRoot is { })
{
app.RendererRoot.DrawDirtyRects = value;
}
else
{
changed = false;
}
if (changed)
RaiseAndSetIfChanged(ref _shouldVisualizeDirtyRects, value);
}
}
@ -77,8 +116,21 @@ namespace Avalonia.Diagnostics.ViewModels
get => _showFpsOverlay;
set
{
_root.Renderer.DrawFps = value;
RaiseAndSetIfChanged(ref _showFpsOverlay, value);
var changed = true;
if (_root is TopLevel topLevel && topLevel.Renderer is { })
{
topLevel.Renderer.DrawFps = value;
}
else if (_root is Controls.Application app && app.RendererRoot is { })
{
app.RendererRoot.DrawFps = value;
}
else
{
changed = false;
}
if(changed)
RaiseAndSetIfChanged(ref _showFpsOverlay, value);
}
}
@ -150,12 +202,28 @@ namespace Avalonia.Diagnostics.ViewModels
private set { RaiseAndSetIfChanged(ref _focusedControl, value); }
}
public string? PointerOverElement
public IInputRoot? PointerOverRoot
{
get => _pointerOverRoot;
private set => RaiseAndSetIfChanged( ref _pointerOverRoot , value);
}
public IInputElement? PointerOverElement
{
get { return _pointerOverElement; }
private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); }
private set
{
RaiseAndSetIfChanged(ref _pointerOverElement, value);
PointerOverElementName = value?.GetType()?.Name;
}
}
public string? PointerOverElementName
{
get => _pointerOverElementName;
private set => RaiseAndSetIfChanged(ref _pointerOverElementName, value);
}
private void UpdateConsoleContext(ConsoleContext context)
{
context.root = _root;
@ -188,8 +256,11 @@ namespace Avalonia.Diagnostics.ViewModels
_pointerOverSubscription.Dispose();
_logicalTree.Dispose();
_visualTree.Dispose();
_root.Renderer.DrawDirtyRects = false;
_root.Renderer.DrawFps = false;
if (_root is TopLevel top)
{
top.Renderer.DrawDirtyRects = false;
top.Renderer.DrawFps = false;
}
}
private void UpdateFocusedControl()
@ -220,10 +291,66 @@ namespace Avalonia.Diagnostics.ViewModels
}
public int? StartupScreenIndex { get; private set; } = default;
[DependsOn(nameof(TreePageViewModel.SelectedNode))]
[DependsOn(nameof(Content))]
bool CanShot(object? parameter)
{
return Content is TreePageViewModel tree
&& tree.SelectedNode != null
&& tree.SelectedNode.Visual is VisualTree.IVisual visual
&& visual.VisualRoot != null;
}
async void Shot(object? parameter)
{
if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control
&& _screenshotHandler is { }
)
{
try
{
await _screenshotHandler.Take(control);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
//TODO: Notify error
}
}
}
public void SetOptions(DevToolsOptions options)
{
_screenshotHandler = options.ScreenshotHandler;
StartupScreenIndex = options.StartupScreenIndex;
ShowImplementedInterfaces = options.ShowImplementedInterfaces;
}
public bool ShowImplementedInterfaces
{
get => _showImplementedInterfaces;
private set => RaiseAndSetIfChanged(ref _showImplementedInterfaces , value);
}
public void ToggleShowImplementedInterfaces(object parametr)
{
ShowImplementedInterfaces = !ShowImplementedInterfaces;
if (Content is TreePageViewModel viewModel)
{
viewModel.UpdatePropertiesView();
}
}
public bool ShowDettailsPropertyType
{
get => _showPropertyType;
private set => RaiseAndSetIfChanged(ref _showPropertyType , value);
}
public void ToggleShowDettailsPropertyType(object paramter)
{
ShowDettailsPropertyType = !ShowDettailsPropertyType;
}
}
}

13
src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs

@ -14,12 +14,17 @@ namespace Avalonia.Diagnostics.ViewModels
public abstract object Key { get; }
public abstract string Name { get; }
public abstract string Group { get; }
public abstract Type Type { get; }
public abstract Type AssignedType { get; }
public abstract Type? DeclaringType { get; }
public abstract string? Value { get; set; }
public abstract string Priority { get; }
public abstract bool? IsAttached { get; }
public abstract void Update();
public abstract bool? IsAttached { get; }
public abstract void Update();
public abstract Type PropertyType { get; }
public string Type => PropertyType == AssignedType
? PropertyType.GetTypeName()
: $"{PropertyType.GetTypeName()} {{{AssignedType.GetTypeName()}}}";
protected static string? ConvertToString(object? value)
{
@ -31,7 +36,7 @@ namespace Avalonia.Diagnostics.ViewModels
var converter = TypeDescriptor.GetConverter(value);
//CollectionConverter does not deliver any important information. It just displays "(Collection)".
if (!converter.CanConvertTo(typeof(string)) ||
if (!converter.CanConvertTo(typeof(string)) ||
converter.GetType() == typeof(CollectionConverter))
{
return value.ToString() ?? "(null)";

9
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs

@ -16,12 +16,13 @@ namespace Avalonia.Diagnostics.ViewModels
private string _classes;
private bool _isExpanded;
protected TreeNode(IVisual visual, TreeNode? parent, string? customName = null)
protected TreeNode(IAvaloniaObject avaloniaObject, TreeNode? parent, string? customName = null)
{
_classes = string.Empty;
Parent = parent;
Type = customName ?? visual.GetType().Name;
Visual = visual;
var visual = avaloniaObject ;
Type = customName ?? avaloniaObject.GetType().Name;
Visual = visual!;
FontWeight = IsRoot ? FontWeight.Bold : FontWeight.Normal;
if (visual is IControl control)
@ -76,7 +77,7 @@ namespace Avalonia.Diagnostics.ViewModels
get;
}
public IVisual Visual
public IAvaloniaObject Visual
{
get;
}

14
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs

@ -10,6 +10,20 @@ namespace Avalonia.Diagnostics.ViewModels
{
internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList<TreeNode>, IDisposable
{
private class EmptyTreeNodeCollection : TreeNodeCollection
{
public EmptyTreeNodeCollection():base(default!)
{
}
protected override void Initialize(AvaloniaList<TreeNode> nodes)
{
}
}
static readonly internal TreeNodeCollection Empty = new EmptyTreeNodeCollection();
private AvaloniaList<TreeNode>? _inner;
public TreeNodeCollection(TreeNode owner) => Owner = owner;

6
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -39,6 +39,7 @@ namespace Avalonia.Diagnostics.ViewModels
Details = value != null ?
new ControlDetailsViewModel(this, value.Visual) :
null;
Details?.UpdatePropertiesView(MainView.ShowImplementedInterfaces);
Details?.UpdateStyleFilters();
}
}
@ -135,5 +136,10 @@ namespace Avalonia.Diagnostics.ViewModels
return null;
}
internal void UpdatePropertiesView()
{
Details?.UpdatePropertiesView(MainView?.ShowImplementedInterfaces ?? true);
}
}
}

80
src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs

@ -7,15 +7,22 @@ using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Primitives;
using Avalonia.Styling;
using Avalonia.VisualTree;
using Lifetimes = Avalonia.Controls.ApplicationLifetimes;
using System.Linq;
namespace Avalonia.Diagnostics.ViewModels
{
internal class VisualTreeNode : TreeNode
{
public VisualTreeNode(IVisual visual, TreeNode? parent, string? customName = null)
: base(visual, parent, customName)
public VisualTreeNode(IAvaloniaObject avaloniaObject, TreeNode? parent, string? customName = null)
: base(avaloniaObject, parent, customName)
{
Children = new VisualTreeNodeCollection(this, visual);
Children = avaloniaObject switch
{
IVisual visual => new VisualTreeNodeCollection(this, visual),
Controls.Application host => new ApplicationHostVisuals(this, host),
_ => TreeNodeCollection.Empty
};
if (Visual is IStyleable styleable)
IsInTemplate = styleable.TemplatedParent != null;
@ -27,7 +34,7 @@ namespace Avalonia.Diagnostics.ViewModels
public static VisualTreeNode[] Create(object control)
{
return control is IVisual visual ?
return control is IAvaloniaObject visual ?
new[] { new VisualTreeNode(visual, null) } :
Array.Empty<VisualTreeNode>();
}
@ -130,7 +137,7 @@ namespace Avalonia.Diagnostics.ViewModels
_subscriptions.Add(
_control.VisualChildren.ForEachItem(
(i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)),
(i, item) => nodes.Insert(i, new VisualTreeNode((IAvaloniaObject)item, Owner)),
(i, item) => nodes.RemoveAt(i),
() => nodes.Clear()));
}
@ -147,5 +154,68 @@ namespace Avalonia.Diagnostics.ViewModels
public string? CustomName { get; }
}
}
internal class ApplicationHostVisuals : TreeNodeCollection
{
readonly Controls.Application _application;
CompositeDisposable _subscriptions = new CompositeDisposable(2);
public ApplicationHostVisuals(TreeNode owner, Controls.Application host) :
base(owner)
{
_application = host;
}
protected override void Initialize(AvaloniaList<TreeNode> nodes)
{
if (_application.ApplicationLifetime is Lifetimes.ISingleViewApplicationLifetime single)
{
nodes.Add(new VisualTreeNode(single.MainView, Owner));
}
if (_application.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime classic)
{
for (int i = 0; i < classic.Windows.Count; i++)
{
var window = classic.Windows[i];
if (window is Views.MainWindow)
{
continue;
}
nodes.Add(new VisualTreeNode(window, Owner));
}
_subscriptions = new System.Reactive.Disposables.CompositeDisposable()
{
Window.WindowOpenedEvent.AddClassHandler(typeof(Window), (s,e)=>
{
if (s is Views.MainWindow)
{
return;
}
nodes.Add(new VisualTreeNode((IAvaloniaObject)s!,Owner));
}),
Window.WindowClosedEvent.AddClassHandler(typeof(Window), (s,e)=>
{
if (s is Views.MainWindow)
{
return;
}
var item = nodes.FirstOrDefault(node=>object.ReferenceEquals(node.Visual,s));
if(!(item is null))
{
nodes.Remove(item);
}
}),
};
}
}
public override void Dispose()
{
_subscriptions?.Dispose();
base.Dispose();
}
}
}
}

36
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -4,15 +4,29 @@
xmlns:local="clr-namespace:Avalonia.Diagnostics.Views"
xmlns:controls="clr-namespace:Avalonia.Diagnostics.Controls"
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
xmlns:lb="using:Avalonia.Diagnostics.Behaviors"
x:Class="Avalonia.Diagnostics.Views.ControlDetailsView"
x:Name="Main">
<UserControl.Resources>
<conv:BoolToOpacityConverter x:Key="BoolToOpacity" Opacity="0.6"/>
<conv:GetTypeNameConverter x:Key="GetTypeName"/>
</UserControl.Resources>
<Grid ColumnDefinitions="*,Auto,320">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<!--
When selecting the Application node, we need this trick to hide Layout Visualizer
because when using the GridSplitter it sets the Witdth property of ColumnDefinition
(see https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Controls/GridSplitter.cs#L528)
and if we hide the contents of the column, the space is not reclaimed
(see discussion https://github.com/AvaloniaUI/Avalonia/discussions/6773).
-->
<ColumnDefinition Width="320" lb:ColumnDefinition.IsVisible="{Binding Layout, Converter={x:Static ObjectConverters.IsNotNull}}" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" RowDefinitions="Auto,Auto,*">
<Grid ColumnDefinitions="Auto, *" RowDefinitions="Auto, Auto">
@ -40,7 +54,18 @@
<DataGrid.Columns>
<DataGridTextColumn Header="Property" Binding="{Binding Name}" IsReadOnly="True" />
<DataGridTextColumn Header="Value" Binding="{Binding Value}" />
<DataGridTextColumn Header="Type" Binding="{Binding Type.Name}" />
<DataGridTextColumn Header="Type" Binding="{Binding Type}"
IsReadOnly="True"
IsVisible="{Binding !$parent[UserControl;2].DataContext.ShowDettailsPropertyType}"
/>
<DataGridTextColumn Header="Assinged Type" Binding="{Binding AssignedType, Converter={StaticResource GetTypeName}}"
IsReadOnly="True"
IsVisible="{Binding $parent[UserControl;2].DataContext.ShowDettailsPropertyType}"
/>
<DataGridTextColumn Header="Property Type" Binding="{Binding PropertyType, Converter={StaticResource GetTypeName}}"
IsReadOnly="True"
IsVisible="{Binding $parent[UserControl;2].DataContext.ShowDettailsPropertyType}"
/>
<DataGridTextColumn Header="Priority" Binding="{Binding Priority}" IsReadOnly="True" />
</DataGrid.Columns>
@ -53,9 +78,10 @@
</Grid>
<GridSplitter Grid.Column="1" />
<GridSplitter Grid.Column="1"/>
<Grid Grid.Column="2" RowDefinitions="*,Auto,*" >
<Grid Grid.Column="2" RowDefinitions="*,Auto,*"
IsVisible="{Binding Layout, Converter={x:Static ObjectConverters.IsNotNull}}">
<Grid RowDefinitions="Auto,*" Grid.Row="0">
<TextBlock FontWeight="Bold" Grid.Row="0" Text="Layout Visualizer" Margin="4" />

49
src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml

@ -6,6 +6,36 @@
<Menu>
<MenuItem Header="_File">
<MenuItem Header="E_xit" Command="{Binding $parent[Window].Close}" />
<MenuItem Header="Screenshot" Command="{Binding Shot}" HotKey="F8">
<MenuItem.Icon>
<Image>
<DrawingImage>
<DrawingGroup>
<GeometryDrawing Geometry="F1 M 13.4533,17.56C 13.4533,19.8827 15.344,21.772 17.6653,21.772L 17.6653,21.772C 19.988,21.772 21.8773,19.8827 21.8773,17.56L 21.8773,17.56C 21.8773,15.2373 19.988,13.348 17.6653,13.348L 17.6653,13.348C 15.344,13.348 13.4533,15.2373 13.4533,17.56 Z ">
<GeometryDrawing.Brush>
<RadialGradientBrush Center="0.245696,0.288009" GradientOrigin="0.245696,0.288009" Radius="0.499952">
<RadialGradientBrush.GradientStops>
<GradientStop Color="#FF878A8C" Offset="0" />
<GradientStop Color="#FF544A4C" Offset="0.991379" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Geometry="F1 M 13.332,6.22803L 10.2227,9.72668L 8.49866,9.72668L 8.49866,7.56136L 5.33333,7.56136L 5.33333,9.72668L 3.33333,9.72668L 3.33333,24.3947L 13.1213,24.3947C 14.424,25.264 15.9853,25.772 17.6653,25.772L 17.6653,25.772C 19.344,25.772 20.9067,25.264 22.2094,24.3947L 28.6667,24.3947L 28.6667,9.72668L 24.944,9.72668L 21.8333,6.22803M 12.12,17.56C 12.12,14.5013 14.608,12.0147 17.6653,12.0147L 17.6653,12.0147C 20.7227,12.0147 23.2107,14.5013 23.2107,17.56L 23.2107,17.56C 23.2107,20.6174 20.7227,23.104 17.6653,23.104L 17.6653,23.104C 14.608,23.104 12.12,20.6174 12.12,17.56 Z ">
<GeometryDrawing.Brush>
<RadialGradientBrush Center="0.196943,0.216757" GradientOrigin="0.196943,0.216757" Radius="0.44654">
<RadialGradientBrush.GradientStops>
<GradientStop Color="#FF87898C" Offset="0" />
<GradientStop Color="#FF544A4C" Offset="1" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage>
</Image>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="_View">
<MenuItem Header="_Console" Command="{Binding $parent[UserControl].ToggleConsole}">
@ -15,6 +45,23 @@
IsEnabled="False" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Control _Details">
<MenuItem Header="Show Implemented Interfaces" Command="{Binding ToggleShowImplementedInterfaces}">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsChecked="{Binding ShowImplementedInterfaces}"
IsEnabled="False" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Split Property Type" Command="{Binding ToggleShowDettailsPropertyType}">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsChecked="{Binding ShowDettailsPropertyType}"
IsEnabled="False"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</MenuItem>
<MenuItem Header="_Options">
<MenuItem Header="Visualize margin/padding" Command="{Binding ToggleVisualizeMarginPadding}">
@ -73,7 +120,7 @@
<TextBlock Text="{Binding FocusedControl}" />
<Separator Width="8" />
<TextBlock>Pointer Over:</TextBlock>
<TextBlock Text="{Binding PointerOverElement}" />
<TextBlock Text="{Binding PointerOverElementName}" />
</StackPanel>
<TextBlock Grid.Column="1"

4
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml

@ -18,6 +18,8 @@
<StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml" />
<StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml" />
</Window.Styles>
<Window.KeyBindings>
<KeyBinding Gesture="F8" Command="{Binding Shot}"/>
</Window.KeyBindings>
<views:MainView/>
</Window>

21
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -19,7 +19,7 @@ namespace Avalonia.Diagnostics.Views
{
private readonly IDisposable? _keySubscription;
private readonly Dictionary<Popup, IDisposable> _frozenPopupStates;
private TopLevel? _root;
private AvaloniaObject? _root;
public MainWindow()
{
@ -50,23 +50,23 @@ namespace Avalonia.Diagnostics.Views
this.Opened += lh;
}
public TopLevel? Root
public AvaloniaObject? Root
{
get => _root;
set
{
if (_root != value)
{
if (_root != null)
if (_root is ICloseable oldClosable)
{
_root.Closed -= RootClosed;
oldClosable.Closed -= RootClosed;
}
_root = value;
if (_root != null)
if (_root is ICloseable newClosable)
{
_root.Closed += RootClosed;
newClosable.Closed += RootClosed;
DataContext = new MainViewModel(_root);
}
else
@ -91,9 +91,9 @@ namespace Avalonia.Diagnostics.Views
_frozenPopupStates.Clear();
if (_root != null)
if (_root is ICloseable cloneable)
{
_root.Closed -= RootClosed;
cloneable.Closed -= RootClosed;
_root = null;
}
@ -123,7 +123,7 @@ namespace Avalonia.Diagnostics.Views
.FirstOrDefault();
}
private static List<PopupRoot> GetPopupRoots(IVisual root)
private static List<PopupRoot> GetPopupRoots(TopLevel root)
{
var popupRoots = new List<PopupRoot>();
@ -160,7 +160,8 @@ namespace Avalonia.Diagnostics.Views
return;
}
var root = Root;
var root = Root as TopLevel
?? vm.PointerOverRoot as TopLevel;
if (root is null)
{
return;

7
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs

@ -47,7 +47,12 @@ namespace Avalonia.Diagnostics.Views
return;
}
var visual = (Visual)node.Visual;
var visual = node.Visual as Visual;
if (visual is null)
{
return;
}
_currentLayer = AdornerLayer.GetAdornerLayer(visual);

71
src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs

@ -0,0 +1,71 @@
using System;
using System.IO;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media.Imaging;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics
{
internal static class VisualExtensions
{
/// <summary>
/// Render control to the destination stream.
/// </summary>
/// <param name="source">Control to be rendered.</param>
/// <param name="destination">Destination stream.</param>
/// <param name="dpi">Dpi quality.</param>
public static void RenderTo(this IControl source, Stream destination, double dpi = 96)
{
if (source.TransformedBounds == null)
{
return;
}
var rect = source.TransformedBounds.Value.Clip;
var top = rect.TopLeft;
var pixelSize = new PixelSize((int)rect.Width, (int)rect.Height);
var dpiVector = new Vector(dpi, dpi);
// get Visual root
var root = (source.VisualRoot
?? source.GetVisualRoot())
as IControl ?? source;
IDisposable? clipSetter = default;
IDisposable? clipToBoundsSetter = default;
IDisposable? renderTransformOriginSetter = default;
IDisposable? renderTransformSetter = default;
try
{
// Set clip region
var clipRegion = new Media.RectangleGeometry(rect);
clipToBoundsSetter = root.SetValue(Visual.ClipToBoundsProperty, true, BindingPriority.Animation);
clipSetter = root.SetValue(Visual.ClipProperty, clipRegion, BindingPriority.Animation);
// Translate origin
renderTransformOriginSetter = root.SetValue(Visual.RenderTransformOriginProperty,
new RelativePoint(top, RelativeUnit.Absolute),
BindingPriority.Animation);
renderTransformSetter = root.SetValue(Visual.RenderTransformProperty,
new Media.TranslateTransform(-top.X, -top.Y),
BindingPriority.Animation);
using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector))
{
bitmap.Render(root);
bitmap.Save(destination);
}
}
finally
{
// Restore values before trasformation
renderTransformSetter?.Dispose();
renderTransformOriginSetter?.Dispose();
clipSetter?.Dispose();
clipToBoundsSetter?.Dispose();
source?.InvalidateVisual();
}
}
}
}

2
src/Avalonia.Dialogs/Avalonia.Dialogs.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

2
src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

2
src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

2
src/Avalonia.Headless/Avalonia.Headless.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

4
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -67,6 +67,10 @@ namespace Avalonia.Headless
{
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16,16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
class HeadlessSystemDialogsStub : ISystemDialogImpl

2
src/Avalonia.Input/Avalonia.Input.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" />

2
src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -71,7 +71,7 @@ namespace Avalonia.Input.GestureRecognizers
EndGesture();
_tracking = e.Pointer;
_gestureId = ScrollGestureEventArgs.GetNextFreeId();;
_trackedRootPoint = e.GetPosition(null);
_trackedRootPoint = e.GetPosition(_target);
}
}

11
src/Avalonia.Input/TouchDevice.cs

@ -58,20 +58,17 @@ namespace Avalonia.Input
}
else
{
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
if (settings == null)
{
throw new Exception("IPlatformSettings can not be null");
}
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
if (!_lastClickRect.Contains(args.Position)
|| ev.Timestamp - _lastClickTime > settings.DoubleClickTime.TotalMilliseconds)
|| ev.Timestamp - _lastClickTime > settings.TouchDoubleClickTime.TotalMilliseconds)
{
_clickCount = 0;
}
++_clickCount;
_lastClickTime = ev.Timestamp;
_lastClickRect = new Rect(args.Position, new Size())
.Inflate(new Thickness(16, 16));
.Inflate(new Thickness(settings.TouchDoubleClickSize.Width / 2, settings.TouchDoubleClickSize.Height / 2));
}
target.RaiseEvent(new PointerPressedEventArgs(target, pointer,

2
src/Avalonia.Interactivity/Avalonia.Interactivity.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />

2
src/Avalonia.Layout/Avalonia.Layout.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />

6
src/Avalonia.MicroCom/MicroComRuntime.cs

@ -36,7 +36,13 @@ namespace Avalonia.MicroCom
public static T CreateProxyFor<T>(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
public static T CreateProxyFor<T>(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle);
public static T CreateProxyOrNullFor<T>(void* pObject, bool ownsHandle) where T : class =>
pObject == null ? null : (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
public static T CreateProxyOrNullFor<T>(IntPtr pObject, bool ownsHandle) where T : class =>
pObject == IntPtr.Zero ? null : (T)CreateProxyFor(typeof(T), pObject, ownsHandle);
public static object CreateProxyFor(Type type, IntPtr pObject, bool ownsHandle) => _factories[type](pObject, ownsHandle);
public static IntPtr GetNativeIntPtr<T>(this T obj, bool owned = false) where T : IUnknown

5
src/Avalonia.MicroCom/MicroComVtblBase.cs

@ -21,6 +21,11 @@ namespace Avalonia.MicroCom
AddMethod((AddRefDelegate)Release);
}
protected void AddMethod(void* f)
{
_methods.Add(new IntPtr(f));
}
protected void AddMethod(Delegate d)
{
GCHandle.Alloc(d);

7
src/Avalonia.Native/Avalonia.Native.csproj

@ -4,8 +4,9 @@
<PackAvaloniaNative Condition="'$(PackAvaloniaNative)' == ''">$([MSBuild]::IsOSPlatform(OSX))</PackAvaloniaNative>
<IsPackable>$(PackAvaloniaNative)</IsPackable>
<IsPackable Condition="'$([MSBuild]::IsOSPlatform(OSX))' == 'True'">true</IsPackable>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<MicroComGeneratorRuntimeNamespace>Avalonia.MicroCom</MicroComGeneratorRuntimeNamespace>
</PropertyGroup>
<ItemGroup Condition="'$(PackAvaloniaNative)' == 'true'">
@ -20,7 +21,7 @@
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<AvnComIdl Include="avn.idl" OutputFile="Interop.Generated.cs" />
<PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.10.4" PrivateAssets="all" />
<MicroComIdl Include="avn.idl" CSharpInteropPath="Interop.Generated.cs" />
</ItemGroup>
<Import Project="../../build/MicroCom.targets" />
</Project>

28
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -26,9 +26,15 @@ namespace Avalonia.Native
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(16, 16);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options)
{
var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor<IAvaloniaNativeFactory>(factory, true));
var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor<IAvaloniaNativeFactory>(factory, true));
result.DoInitialize(options);
return result;
@ -55,14 +61,14 @@ namespace Avalonia.Native
return Initialize(CreateAvaloniaNative(), options);
}
public void SetupApplicationMenuExporter ()
public void SetupApplicationMenuExporter()
{
var exporter = new AvaloniaNativeMenuExporter(_factory);
}
public void SetupApplicationName ()
public void SetupApplicationName()
{
if(!string.IsNullOrWhiteSpace(Application.Current.Name))
if (!string.IsNullOrWhiteSpace(Application.Current.Name))
{
_factory.MacOptions.SetApplicationTitle(Application.Current.Name);
}
@ -80,13 +86,13 @@ namespace Avalonia.Native
GCHandle.FromIntPtr(handle).Free();
}
}
void DoInitialize(AvaloniaNativePlatformOptions options)
{
_options = options;
var applicationPlatform = new AvaloniaNativeApplicationPlatform();
_factory.Initialize(new GCHandleDeallocator(), applicationPlatform);
if (_factory.MacOptions != null)
{
@ -118,7 +124,7 @@ namespace Avalonia.Native
hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers));
hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
if (_options.UseGpu)
{
try
@ -133,7 +139,7 @@ namespace Avalonia.Native
}
}
public ITrayIconImpl CreateTrayIcon ()
public ITrayIconImpl CreateTrayIcon()
{
return new TrayIconImpl(_factory);
}
@ -159,8 +165,8 @@ namespace Avalonia.Native
ShowInDock = true;
}
public bool ShowInDock
{
public bool ShowInDock
{
get => _showInDock;
set
{

2
src/Avalonia.OpenGL/Avalonia.OpenGL.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

2
src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<PackageId>Avalonia.ReactiveUI</PackageId>
<SignAssembly>false</SignAssembly>
</PropertyGroup>

57
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -57,7 +57,13 @@ namespace Avalonia.ReactiveUI
/// </summary>
public static readonly StyledProperty<RoutingState?> RouterProperty =
AvaloniaProperty.Register<RoutedViewHost, RoutingState?>(nameof(Router));
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewContract"/> property.
/// </summary>
public static readonly StyledProperty<string?> ViewContractProperty =
AvaloniaProperty.Register<ViewModelViewHost, string?>(nameof(ViewContract));
/// <summary>
/// Initializes a new instance of the <see cref="RoutedViewHost"/> class.
/// </summary>
@ -70,15 +76,18 @@ namespace Avalonia.ReactiveUI
.Where(router => router == null)!
.Cast<object?>();
var viewContract = this.WhenAnyValue(x => x.ViewContract);
this.WhenAnyValue(x => x.Router)
.Where(router => router != null)
.SelectMany(router => router!.CurrentViewModel)
.Merge(routerRemoved)
.Subscribe(NavigateToViewModel)
.CombineLatest(viewContract)
.Subscribe(tuple => NavigateToViewModel(tuple.First, tuple.Second))
.DisposeWith(disposables);
});
}
/// <summary>
/// Gets or sets the <see cref="RoutingState"/> of the view model stack.
/// </summary>
@ -87,17 +96,27 @@ namespace Avalonia.ReactiveUI
get => GetValue(RouterProperty);
set => SetValue(RouterProperty, value);
}
/// <summary>
/// Gets or sets the view contract.
/// </summary>
public string? ViewContract
{
get => GetValue(ViewContractProperty);
set => SetValue(ViewContractProperty, value);
}
/// <summary>
/// Gets or sets the ReactiveUI view locator used by this router.
/// </summary>
public IViewLocator? ViewLocator { get; set; }
/// <summary>
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>
/// <param name="viewModel">ViewModel to which the user navigates.</param>
private void NavigateToViewModel(object? viewModel)
/// <param name="contract">The contract for view resolution.</param>
private void NavigateToViewModel(object? viewModel, string? contract)
{
if (Router == null)
{
@ -112,17 +131,33 @@ namespace Avalonia.ReactiveUI
Content = DefaultContent;
return;
}
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
var viewInstance = viewLocator.ResolveView(viewModel);
var viewInstance = viewLocator.ResolveView(viewModel, contract);
if (viewInstance == null)
{
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
if (contract == null)
{
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
}
else
{
this.Log().Warn($"Couldn't find view with contract '{contract}' for '{viewModel}'. Is it registered? Falling back to default content.");
}
Content = DefaultContent;
return;
}
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
if (contract == null)
{
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
}
else
{
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel} and contract '{contract}'.");
}
viewInstance.ViewModel = viewModel;
if (viewInstance is IDataContextProvider provider)
provider.DataContext = viewModel;

52
src/Avalonia.ReactiveUI/ViewModelViewHost.cs

@ -3,7 +3,7 @@ using System.Reactive.Disposables;
using ReactiveUI;
using Splat;
namespace Avalonia.ReactiveUI
namespace Avalonia.ReactiveUI
{
/// <summary>
/// This content control will automatically load the View associated with
@ -18,6 +18,12 @@ namespace Avalonia.ReactiveUI
public static readonly AvaloniaProperty<object?> ViewModelProperty =
AvaloniaProperty.Register<ViewModelViewHost, object?>(nameof(ViewModel));
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewContract"/> property.
/// </summary>
public static readonly StyledProperty<string?> ViewContractProperty =
AvaloniaProperty.Register<ViewModelViewHost, string?>(nameof(ViewContract));
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelViewHost"/> class.
/// </summary>
@ -25,8 +31,8 @@ namespace Avalonia.ReactiveUI
{
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.ViewModel)
.Subscribe(NavigateToViewModel)
this.WhenAnyValue(x => x.ViewModel, x => x.ViewContract)
.Subscribe(tuple => NavigateToViewModel(tuple.Item1, tuple.Item2))
.DisposeWith(disposables);
});
}
@ -39,7 +45,16 @@ namespace Avalonia.ReactiveUI
get => GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
/// <summary>
/// Gets or sets the view contract.
/// </summary>
public string? ViewContract
{
get => GetValue(ViewContractProperty);
set => SetValue(ViewContractProperty, value);
}
/// <summary>
/// Gets or sets the view locator.
/// </summary>
@ -49,7 +64,8 @@ namespace Avalonia.ReactiveUI
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>
/// <param name="viewModel">ViewModel to which the user navigates.</param>
private void NavigateToViewModel(object? viewModel)
/// <param name="contract">The contract for view resolution.</param>
private void NavigateToViewModel(object? viewModel, string? contract)
{
if (viewModel == null)
{
@ -57,17 +73,33 @@ namespace Avalonia.ReactiveUI
Content = DefaultContent;
return;
}
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
var viewInstance = viewLocator.ResolveView(viewModel);
var viewInstance = viewLocator.ResolveView(viewModel, contract);
if (viewInstance == null)
{
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
if (contract == null)
{
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
}
else
{
this.Log().Warn($"Couldn't find view with contract '{contract}' for '{viewModel}'. Is it registered? Falling back to default content.");
}
Content = DefaultContent;
return;
}
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
if (contract == null)
{
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
}
else
{
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel} and contract '{contract}'.");
}
viewInstance.ViewModel = viewModel;
if (viewInstance is IStyledElement styled)
styled.DataContext = viewModel;

2
src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<DefineConstants>AVALONIA_REMOTE_PROTOCOL;$(DefineConstants)</DefineConstants>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Key.snk</AssemblyOriginatorKeyFile>

2
src/Avalonia.Styling/Avalonia.Styling.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AssemblyName>Avalonia.Styling</AssemblyName>
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>

2
src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />

1
src/Avalonia.Themes.Default/ButtonSpinner.xaml

@ -5,6 +5,7 @@
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Focusable" Value="True"/>
</Style>
<Style Selector="ButtonSpinner /template/ RepeatButton">
<Setter Property="Background" Value="Transparent"/>

2
src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />

1
src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml

@ -64,6 +64,7 @@
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Focusable" Value="True"/>
<Setter Property="Template">
<ControlTemplate>
<DataValidationErrors>

96
src/Avalonia.Themes.Fluent/FluentTheme.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
#nullable enable
@ -17,11 +18,14 @@ namespace Avalonia.Themes.Fluent
/// <summary>
/// Includes the fluent theme in an application.
/// </summary>
public class FluentTheme : IStyle, IResourceProvider
public class FluentTheme : AvaloniaObject, IStyle, IResourceProvider
{
private readonly Uri _baseUri;
private IStyle[]? _loaded;
private Styles _fluentDark = new();
private Styles _fluentLight = new();
private Styles _sharedStyles = new();
private bool _isLoading;
private IStyle? _loaded;
/// <summary>
/// Initializes a new instance of the <see cref="FluentTheme"/> class.
@ -30,6 +34,7 @@ namespace Avalonia.Themes.Fluent
public FluentTheme(Uri baseUri)
{
_baseUri = baseUri;
InitStyles(baseUri);
}
/// <summary>
@ -39,12 +44,37 @@ namespace Avalonia.Themes.Fluent
public FluentTheme(IServiceProvider serviceProvider)
{
_baseUri = ((IUriContext)serviceProvider.GetService(typeof(IUriContext))).BaseUri;
InitStyles(_baseUri);
}
public static readonly StyledProperty<FluentThemeMode> ModeProperty =
AvaloniaProperty.Register<FluentTheme, FluentThemeMode>(nameof(Mode));
/// <summary>
/// Gets or sets the mode of the fluent theme (light, dark).
/// </summary>
public FluentThemeMode Mode { get; set; }
public FluentThemeMode Mode
{
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == ModeProperty)
{
if (Mode == FluentThemeMode.Dark)
{
(Loaded as Styles)![1] = _fluentDark[0];
(Loaded as Styles)![2] = _fluentDark[1];
}
else
{
(Loaded as Styles)![1] = _fluentLight[0];
(Loaded as Styles)![2] = _fluentLight[1];
}
}
}
public IResourceHost? Owner => (Loaded as IResourceProvider)?.Owner;
@ -58,18 +88,25 @@ namespace Avalonia.Themes.Fluent
if (_loaded == null)
{
_isLoading = true;
var loaded = (IStyle)AvaloniaXamlLoader.Load(GetUri(), _baseUri);
_loaded = new[] { loaded };
if (Mode == FluentThemeMode.Light)
{
_loaded = new Styles() { _sharedStyles , _fluentLight[0], _fluentLight[1] };
}
else if (Mode == FluentThemeMode.Dark)
{
_loaded = new Styles() { _sharedStyles, _fluentDark[0], _fluentDark[1] };
}
_isLoading = false;
}
return _loaded?[0]!;
return _loaded!;
}
}
bool IResourceNode.HasResources => (Loaded as IResourceProvider)?.HasResources ?? false;
IReadOnlyList<IStyle> IStyle.Children => _loaded ?? Array.Empty<IStyle>();
IReadOnlyList<IStyle> IStyle.Children => _loaded?.Children ?? Array.Empty<IStyle>();
public event EventHandler OwnerChanged
{
@ -105,10 +142,47 @@ namespace Avalonia.Themes.Fluent
void IResourceProvider.AddOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.AddOwner(owner);
void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner);
private Uri GetUri() => Mode switch
private void InitStyles(Uri baseUri)
{
FluentThemeMode.Dark => new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml", UriKind.Absolute),
_ => new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml", UriKind.Absolute),
};
_sharedStyles = new Styles
{
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
},
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
},
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Controls/FluentControls.xaml")
}
};
_fluentLight = new Styles
{
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml")
},
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml")
}
};
_fluentDark = new Styles
{
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml")
},
new StyleInclude(baseUri)
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml")
}
};
}
}
}

6
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -79,4 +79,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
Total Issues: 79
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime.get()' is present in the implementation but not in the contract.
Total Issues: 84

2
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<RootNamespace>Avalonia</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

4
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs

@ -80,12 +80,12 @@ namespace Avalonia.Media.TextFormatting.Unicode
}
}
var shouldBreak = GetSimpleBreak() ?? (bool?)GetPairTableBreak(lastClass);
var shouldBreak = GetSimpleBreak() ?? GetPairTableBreak(lastClass);
// Rule LB8a
_lb8a = _nextClass == LineBreakClass.ZWJ;
if (shouldBreak.Value)
if (shouldBreak)
{
Current = new LineBreak(FindPriorNonWhitespace(_lastPosition), _lastPosition);
return true;

10
src/Avalonia.Visuals/Platform/IPlatformSettings.cs

@ -7,5 +7,15 @@ namespace Avalonia.Platform
Size DoubleClickSize { get; }
TimeSpan DoubleClickTime { get; }
/// <summary>
/// Determines the size of the area within that you should click twice in order for a double click to be counted.
/// </summary>
Size TouchDoubleClickSize { get; }
/// <summary>
/// Determines the time span that what will be used to determine the double-click.
/// </summary>
TimeSpan TouchDoubleClickTime { get; }
}
}

2
src/Avalonia.X11/Avalonia.X11.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Avalonia.X11</PackageId>
</PropertyGroup>

12
src/Avalonia.X11/Stubs.cs

@ -1,10 +1,4 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
namespace Avalonia.X11
@ -13,5 +7,11 @@ namespace Avalonia.X11
{
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(16, 16);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
}

1
src/Avalonia.X11/X11Platform.cs

@ -68,6 +68,7 @@ namespace Avalonia.X11
//TODO: log
if (options.UseDBusMenu)
DBusHelper.TryInitialize();
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IPlatformThreadingInterface>().ToConstant(new X11PlatformThreading(this))

2
src/Avalonia.X11/X11Window.cs

@ -1157,7 +1157,7 @@ namespace Avalonia.X11
public ITextInputMethodImpl TextInputMethod => _ime;
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) =>
_transparencyHelper.SetTransparencyRequest(transparencyLevel);
_transparencyHelper?.SetTransparencyRequest(transparencyLevel);
public void SetWindowManagerAddShadowHint(bool enabled)
{

6
src/Directory.Build.props

@ -2,4 +2,10 @@
<Import Project="..\Directory.Build.props" />
<Import Project="..\build\SharedVersion.props" />
<Import Project="..\build\SourceLink.props" Condition="'$(DisableSourceLink)' == ''" />
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)\Shared\ModuleInitializer.cs" >
<Link>Shared\_ModuleInitializer.cs</Link>
<Visible>false</Visible>
</Compile>
</ItemGroup>
</Project>

2
src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Avalonia.LinuxFramebuffer</PackageId>
</PropertyGroup>

4
src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs

@ -18,5 +18,9 @@ namespace Avalonia.LinuxFramebuffer
{
public Size DoubleClickSize { get; } = new Size(4, 4);
public TimeSpan DoubleClickTime { get; } = new TimeSpan(0, 0, 0, 0, 500);
public Size TouchDoubleClickSize => new Size(16,16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
}

2
src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<IsPackable>true</IsPackable>
<PackageId>Avalonia.Markup.Xaml.Loader</PackageId>
<DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>

9
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
@ -15,7 +16,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
IXamlType startType = null;
var sourceProperty = binding.Children.OfType<XamlPropertyAssignmentNode>().FirstOrDefault(c => c.Property.Name == "Source");
if ((sourceProperty?.Values.Count ?? 0) == 1)
var dataTypeProperty = binding.Children.OfType<XamlPropertyAssignmentNode>().FirstOrDefault(c => c.Property.Name == "DataType");
if (sourceProperty?.Values.Count is 1)
{
var sourceValue = sourceProperty.Values[0];
switch (sourceValue)
@ -99,6 +101,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
if (dataTypeProperty?.Values.Count is 1 && dataTypeProperty.Values[0] is XamlAstTextNode text)
{
startType = TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type;
}
Func<IXamlType> startTypeResolver = startType is not null ? () => startType : () =>
{
var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();

6
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@ -155,6 +155,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
Func<IXamlType> startTypeResolver = () =>
{
var dataTypeProperty = obj.Children.OfType<XamlPropertyAssignmentNode>().FirstOrDefault(c => c.Property.Name == "DataType");
if (dataTypeProperty?.Values.Count is 1 && dataTypeProperty.Values[0] is XamlAstTextNode text)
{
return TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type;
}
var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentDataContextNode is null)
{

2
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<DefineConstants>$(DefineConstants);PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER;XAMLX_INTERNAL</DefineConstants>
<UseCecil>false</UseCecil>
<DefineConstants Condition="$(UseCecil) == true">$(DefineConstants);RUNTIME_XAML_CECIL</DefineConstants>

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs

@ -71,5 +71,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public CompiledBindingPath Path { get; set; }
public object Source { get; set; }
public Type DataType { get; set; }
}
}

2
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>
<ItemGroup>

10
src/Shared/ModuleInitializer.cs

@ -0,0 +1,10 @@
namespace System.Runtime.CompilerServices
{
#if !NET5_0_OR_GREATER
internal class ModuleInitializerAttribute : Attribute
{
}
#endif
}

2
src/Skia/Avalonia.Skia/Avalonia.Skia.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<RootNamespace>Avalonia.Skia</RootNamespace>
<AssemblyName>Avalonia.Skia</AssemblyName>
<PackageId>Avalonia.Skia</PackageId>

2
src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs

@ -142,7 +142,7 @@ namespace Avalonia.Web.Blazor
public Size ClientSize => _clientSize;
public Size? FrameSize => null;
public double RenderScaling => 1;
public double RenderScaling => _currentSurface?.Scaling ?? 1;
public IEnumerable<object> Surfaces => new object[] { _currentSurface! };

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save