Browse Source

Merge branch 'master' into refactor/bindings

pull/13970/head
Steven Kirk 3 years ago
parent
commit
de52fcaa12
  1. 6
      .editorconfig
  2. 6
      api/Avalonia.Themes.Fluent.nupkg.xml
  3. 6
      api/Avalonia.Themes.Simple.nupkg.xml
  4. 66
      api/Avalonia.nupkg.xml
  5. 20
      azure-pipelines-integrationtests.yml
  6. 30
      azure-pipelines.yml
  7. 1
      build/BuildTargets.targets
  8. 6
      build/DevAnalyzers.props
  9. 9
      build/TrimmingEnable.props
  10. 3
      build/XUnit.props
  11. 2
      global.json
  12. 9
      nukebuild/.editorconfig
  13. 3
      nukebuild/ApiDiffValidation.cs
  14. 8
      nukebuild/Build.cs
  15. 28
      nukebuild/RefAssemblyGenerator.cs
  16. 36
      nukebuild/Shims.cs
  17. 5
      packages/Avalonia/AvaloniaBuildTasks.targets
  18. 18
      packages/Avalonia/AvaloniaRules.Project.xml
  19. 2
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  20. 0
      samples/ControlCatalog.Browser/AppBundle/Logo.svg
  21. 74
      samples/ControlCatalog.Browser/AppBundle/app.css
  22. 0
      samples/ControlCatalog.Browser/AppBundle/embed.js
  23. 0
      samples/ControlCatalog.Browser/AppBundle/favicon.ico
  24. 28
      samples/ControlCatalog.Browser/AppBundle/index.html
  25. 5
      samples/ControlCatalog.Browser/AppBundle/main.js
  26. 36
      samples/ControlCatalog.Browser/ControlCatalog.Browser.csproj
  27. 3
      samples/ControlCatalog.Browser/EmbedSample.Browser.cs
  28. 6
      samples/ControlCatalog.Browser/Program.cs
  29. 7
      samples/ControlCatalog.Browser/Roots.xml
  30. 56
      samples/ControlCatalog.Browser/app.css
  31. 31
      samples/ControlCatalog.Browser/index.html
  32. 3
      samples/ControlCatalog/MainView.xaml
  33. 2
      samples/ControlCatalog/MainView.xaml.cs
  34. 42
      samples/ControlCatalog/Pages/DataValidationPage.axaml
  35. 14
      samples/ControlCatalog/Pages/DataValidationPage.axaml.cs
  36. 6
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  37. 45
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  38. 19
      samples/ControlCatalog/Pages/GesturePage.cs
  39. 263
      samples/ControlCatalog/Pages/GesturePage.xaml
  40. 3
      samples/ControlCatalog/Pages/ScrollViewerPage.xaml
  41. 4
      samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs
  42. 45
      samples/ControlCatalog/ViewModels/DataValidationViewModel.cs
  43. 2
      samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
  44. 8
      samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs
  45. 16
      samples/Directory.Build.props
  46. 7
      samples/Generators.Sandbox/Controls/CustomTextBox.cs
  47. 50
      samples/GpuInterop/D3DDemo/D3D11DemoControl.cs
  48. 9
      samples/GpuInterop/DrawingSurfaceDemoBase.cs
  49. 12
      samples/GpuInterop/GpuDemo.axaml.cs
  50. 4
      samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs
  51. 14
      samples/GpuInterop/VulkanDemo/VulkanContent.cs
  52. 38
      samples/GpuInterop/VulkanDemo/VulkanContext.cs
  53. 30
      samples/GpuInterop/VulkanDemo/VulkanImage.cs
  54. 2
      samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs
  55. 37
      samples/IntegrationTestApp/MainWindow.axaml.cs
  56. 2
      samples/MobileSandbox.Android/MobileSandbox.Android.csproj
  57. 2
      samples/MobileSandbox/MainView.xaml
  58. 4
      samples/SafeAreaDemo.iOS/Info.plist
  59. 6
      samples/SafeAreaDemo/App.xaml
  60. 12
      samples/SafeAreaDemo/App.xaml.cs
  61. 47
      samples/SafeAreaDemo/ViewModels/MainViewModel.cs
  62. 9
      samples/SafeAreaDemo/Views/MainView.xaml
  63. 7
      samples/SafeAreaDemo/Views/MainView.xaml.cs
  64. 1
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  65. 6
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  66. 12
      src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
  67. 15
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  68. 2
      src/Android/Avalonia.Android/AvaloniaView.cs
  69. 37
      src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
  70. 8
      src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
  71. 22
      src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
  72. 10
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  73. 8
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  74. 45
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
  75. 2
      src/Avalonia.Base/Animation/PageSlide.cs
  76. 5
      src/Avalonia.Base/Collections/AvaloniaList.cs
  77. 203
      src/Avalonia.Base/Input/FocusManager.cs
  78. 9
      src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs
  79. 39
      src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
  80. 1
      src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
  81. 2
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  82. 2
      src/Avalonia.Base/Input/InputElement.cs
  83. 2
      src/Avalonia.Base/Input/InputMethod.cs
  84. 4
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  85. 28
      src/Avalonia.Base/Input/PinchEventArgs.cs
  86. 5
      src/Avalonia.Base/Input/Pointer.cs
  87. 5
      src/Avalonia.Base/Logging/LogArea.cs
  88. 12
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  89. 7
      src/Avalonia.Base/Media/MediaContext.Compositor.cs
  90. 2
      src/Avalonia.Base/Media/PolyLineSegment.cs
  91. 11
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  92. 60
      src/Avalonia.Base/Media/TransformGroup.cs
  93. 4
      src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs
  94. 9
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
  95. 32
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
  96. 2
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  97. 3
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  98. 2
      src/Avalonia.Base/Rendering/RenderLoop.cs
  99. 6
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  100. 18
      src/Avalonia.Base/Threading/Dispatcher.Queue.cs

6
.editorconfig

@ -208,6 +208,12 @@ dotnet_diagnostic.AVA2001.severity = error
# Xaml files
[*.{xaml,axaml}]
indent_size = 2
# DuplicateSetterError
avalonia_xaml_diagnostic.AVLN2203.severity = error
# StyleInMergedDictionaries
avalonia_xaml_diagnostic.AVLN2204.severity = error
# Obsolete
avalonia_xaml_diagnostic.AVLN5001.severity = error
# Xml project files
[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]

6
api/Avalonia.Themes.Fluent.nupkg.xml

@ -403,6 +403,12 @@
<Left>baseline/netstandard2.0/Avalonia.Themes.Fluent.dll</Left>
<Right>target/netstandard2.0/Avalonia.Themes.Fluent.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/UserControl.xaml</Target>
<Left>baseline/netstandard2.0/Avalonia.Themes.Fluent.dll</Left>
<Right>target/netstandard2.0/Avalonia.Themes.Fluent.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Window.xaml</Target>

6
api/Avalonia.Themes.Simple.nupkg.xml

@ -391,6 +391,12 @@
<Left>baseline/netstandard2.0/Avalonia.Themes.Simple.dll</Left>
<Right>target/netstandard2.0/Avalonia.Themes.Simple.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/UserControl.xaml</Target>
<Left>baseline/netstandard2.0/Avalonia.Themes.Simple.dll</Left>
<Right>target/netstandard2.0/Avalonia.Themes.Simple.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:CompiledAvaloniaXaml.!AvaloniaResources.NamespaceInfo:/Controls/Window.xaml</Target>

66
api/Avalonia.nupkg.xml

@ -1003,6 +1003,72 @@
<Left>baseline/designer/Avalonia.Designer.HostApp.dll</Left>
<Right>target/designer/Avalonia.Designer.HostApp.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.AvaloniaLocator.RegistrationHelper`1</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.AvaloniaLocator.ResolverDisposable</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Input.FocusManager.&lt;GetFocusScopeAncestors&gt;d__18</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Layout.LayoutManager.&lt;&gt;c</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Layout.LayoutManager.ArrangeResult</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Layout.LayoutManager.EffectiveViewportChangedListener</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Rendering.DefaultRenderTimer.&lt;&gt;c__DisplayClass13_0</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Rendering.UiThreadRenderTimer.&lt;&gt;c__DisplayClass3_0</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Controls.Primitives.PopupPositioning.ManagedPopupPositioner.&lt;&gt;c__DisplayClass5_0</Target>
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left>
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Controls.Primitives.PopupPositioning.ManagedPopupPositionerPopupImplHelper.&lt;&gt;c</Target>
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left>
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Controls.Primitives.PopupPositioning.ManagedPopupPositionerPopupImplHelper.MoveResizeDelegate</Target>
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left>
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.ImportCompleted</Target>

20
azure-pipelines-integrationtests.yml

@ -5,14 +5,16 @@ jobs:
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.415'
displayName: 'Use .NET 6.0 Runtime'
inputs:
version: 6.0.415
packageType: runtime
version: 6.0.x
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.402'
displayName: 'Use .NET 7.0 SDK'
inputs:
version: 7.0.402
packageType: sdk
useGlobalJson: true
- script: system_profiler SPDisplaysDataType |grep Resolution
displayName: 'Get Resolution'
@ -57,14 +59,16 @@ jobs:
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.404'
displayName: 'Use .NET 6.0 Runtime'
inputs:
version: 6.0.404
packageType: runtime
version: 6.0.x
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.101'
displayName: 'Use .NET 7.0 SDK'
inputs:
version: 7.0.101
packageType: sdk
useGlobalJson: true
- task: Windows Application Driver@0
inputs:

30
azure-pipelines.yml

@ -30,14 +30,16 @@ jobs:
vmImage: 'ubuntu-20.04'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.415'
displayName: 'Use .NET 6.0 Runtime'
inputs:
version: 6.0.415
packageType: runtime
version: 6.0.x
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.402'
displayName: 'Use .NET 7.0 SDK'
inputs:
version: 7.0.402
packageType: sdk
useGlobalJson: true
- task: CmdLine@2
displayName: 'Install Workloads'
@ -74,14 +76,16 @@ jobs:
vmImage: 'macos-12'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.404'
displayName: 'Use .NET 6.0 Runtime'
inputs:
version: 6.0.404
packageType: runtime
version: 6.0.x
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.101'
displayName: 'Use .NET 7.0 SDK'
inputs:
version: 7.0.101
packageType: sdk
useGlobalJson: true
- task: CmdLine@2
displayName: 'Install Workloads'
@ -152,14 +156,16 @@ jobs:
SolutionDir: '$(Build.SourcesDirectory)'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.404'
displayName: 'Use .NET 6.0 Runtime'
inputs:
version: 6.0.404
packageType: runtime
version: 6.0.x
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.101'
displayName: 'Use .NET 7.0 SDK'
inputs:
version: 7.0.101
packageType: sdk
useGlobalJson: true
- task: CmdLine@2
displayName: 'Install Workloads'

1
build/BuildTargets.targets

@ -4,6 +4,7 @@
<AvaloniaUseExternalMSBuild>true</AvaloniaUseExternalMSBuild>
<AvaloniaXamlIlVerifyIl>true</AvaloniaXamlIlVerifyIl>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">true</AvaloniaXamlVerboseExceptions>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.props"/>
<Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.targets"/>

6
build/DevAnalyzers.props

@ -3,12 +3,10 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)..\src\tools\DevAnalyzers\DevAnalyzers.csproj"
PrivateAssets="all"
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
SetTargetFramework="TargetFramework=netstandard2.0"/>
OutputItemType="Analyzer" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\src\tools\Avalonia.Analyzers\Avalonia.Analyzers.csproj"
PrivateAssets="all"
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
SetTargetFramework="TargetFramework=netstandard2.0"/>
OutputItemType="Analyzer" />
</ItemGroup>
</Project>

9
build/TrimmingEnable.props

@ -1,10 +1,12 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<!-- Remove check for the AOT when we get rid of dependencies with reflection -->
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0' and '$(PublishAot)' != 'true'">
<ILLinkTreatWarningsAsErrors>true</ILLinkTreatWarningsAsErrors>
@ -13,4 +15,11 @@
<!-- NativeAOT warnings -->
<WarningsAsErrors>$(WarningsAsErrors);IL3050;IL3051;IL3052;IL3053;IL3054;IL3055;IL3056</WarningsAsErrors>
</PropertyGroup>
<!-- UnconditionalSuppressMessage doesn't work correctly with .NET Standard, keep trim warnings in .NET only -->
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
<EnableTrimAnalyzer>false</EnableTrimAnalyzer>
</PropertyGroup>
</Project>

3
build/XUnit.props

@ -6,12 +6,13 @@
<PackageReference Include="xunit.extensibility.core" Version="2.4.2" />
<PackageReference Include="xunit.extensibility.execution" Version="2.4.2" />
<PackageReference Include="xunit.runner.console" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" Condition="'$(TargetFramework)' != 'netstandard2.0'" />
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
</ItemGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\avalonia.snk</AssemblyOriginatorKeyFile>
<SignAssembly>False</SignAssembly>
<NoWarn>$(NoWarn);CS8002</NoWarn> <!-- ignore signing warnings for unit tests -->
</PropertyGroup>
</Project>

2
global.json

@ -1,6 +1,6 @@
{
"sdk": {
"version": "7.0.101",
"version": "7.0.404",
"rollForward": "latestFeature"
},
"msbuild-sdks": {

9
nukebuild/.editorconfig

@ -6,3 +6,12 @@ root = false
# C# files
[*.cs]
dotnet_style_require_accessibility_modifiers = never
[{il-repack,Numerge}/**/*.cs]
dotnet_diagnostic.CA1304.severity = none
dotnet_diagnostic.CA1815.severity = none
dotnet_diagnostic.CA1820.severity = none
dotnet_diagnostic.CA1825.severity = none
dotnet_diagnostic.CA1829.severity = none
dotnet_diagnostic.CA1847.severity = none
dotnet_diagnostic.SYSLIB0017.severity = none

3
nukebuild/ApiDiffValidation.cs

@ -8,6 +8,7 @@ using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Nuke.Common.Tooling;
using static Serilog.Log;
public static class ApiDiffValidation
{
@ -142,7 +143,7 @@ public static class ApiDiffValidation
Avalonia.11.0.0
*/
var packageId = GetPackageId(packagePath);
Build.Information("Downloading {0} {1} baseline package", packageId, baselineVersion);
Information("Downloading {0} {1} baseline package", packageId, baselineVersion);
try
{

8
nukebuild/Build.cs

@ -1,21 +1,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Nuke.Common;
using Nuke.Common.Git;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet;
using Nuke.Common.Tools.MSBuild;
using Nuke.Common.Tools.Npm;
using Nuke.Common.Utilities;
using Nuke.Common.Utilities.Collections;
using static Nuke.Common.EnvironmentInfo;
using static Nuke.Common.IO.FileSystemTasks;
using static Nuke.Common.IO.PathConstruction;
@ -23,6 +16,7 @@ using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
using static Nuke.Common.Tools.Xunit.XunitTasks;
using static Nuke.Common.Tools.VSWhere.VSWhereTasks;
using static Serilog.Log;
using MicroCom.CodeGenerator;
/*

28
nukebuild/RefAssemblyGenerator.cs

@ -70,9 +70,6 @@ public class RefAssemblyGenerator
static void ProcessType(TypeDefinition type, MethodReference obsoleteCtor)
{
foreach (var nested in type.NestedTypes)
ProcessType(nested, obsoleteCtor);
var hideMembers = (type.IsInterface && type.Name.EndsWith("Impl"))
|| HasPrivateApi(type.CustomAttributes);
@ -97,7 +94,7 @@ public class RefAssemblyGenerator
foreach (var m in type.Methods)
{
if (hideMembers || HasPrivateApi(m.CustomAttributes))
if (!m.IsPrivate && (hideMembers || HasPrivateApi(m.CustomAttributes)))
{
HideMethod(m);
}
@ -108,30 +105,27 @@ public class RefAssemblyGenerator
{
if (HasPrivateApi(p.CustomAttributes))
{
if (p.SetMethod != null)
HideMethod(p.SetMethod);
if (p.GetMethod != null)
HideMethod(p.GetMethod);
if (p.SetMethod is { IsPrivate: false } setMethod)
HideMethod(setMethod);
if (p.GetMethod is { IsPrivate: false } getMethod)
HideMethod(getMethod);
}
}
foreach (var f in type.Fields)
{
if (hideMembers || HasPrivateApi(f.CustomAttributes))
if (!f.IsPrivate && (hideMembers || HasPrivateApi(f.CustomAttributes)))
{
var dflags = FieldAttributes.Public | FieldAttributes.Family | FieldAttributes.FamORAssem |
FieldAttributes.FamANDAssem | FieldAttributes.Assembly;
f.Attributes = ((f.Attributes | dflags) ^ dflags) | FieldAttributes.Assembly;
f.IsAssembly = true;
}
}
foreach (var cl in type.NestedTypes)
{
ProcessType(cl, obsoleteCtor);
if (hideMembers)
if (hideMembers && cl.IsNestedPublic)
{
var dflags = TypeAttributes.Public;
cl.Attributes = ((cl.Attributes | dflags) ^ dflags) | TypeAttributes.NotPublic;
cl.IsNestedAssembly = true;
}
}
@ -143,9 +137,7 @@ public class RefAssemblyGenerator
static void HideMethod(MethodDefinition m)
{
var dflags = MethodAttributes.Public | MethodAttributes.Family | MethodAttributes.FamORAssem |
MethodAttributes.FamANDAssem | MethodAttributes.Assembly;
m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly;
m.IsAssembly = true;
}
static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, ICustomAttribute? unstableAttribute)

36
nukebuild/Shims.cs

@ -1,24 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using Nuke.Common;
using Nuke.Common.IO;
using Numerge;
using static Serilog.Log;
public partial class Build
{
internal static void Information(string info)
{
Logger.Info(info);
}
internal static void Information(string info, params object[] args)
{
Logger.Info(info, args);
}
private void Zip(AbsolutePath target, params string[] paths) => Zip(target, paths.AsEnumerable());
private void Zip(AbsolutePath target, IEnumerable<string> paths)
@ -44,16 +33,12 @@ public partial class Build
if (Directory.Exists(path))
{
var dirInfo = new DirectoryInfo(path);
var rootPath = Path.GetDirectoryName(dirInfo.FullName);
var rootPath = Path.GetDirectoryName(dirInfo.FullName)!;
foreach(var fsEntry in dirInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
{
if (fsEntry is FileInfo)
{
#if NET6
var relPath = Path.GetRelativePath(rootPath, fsEntry.FullName);
#else
var relPath = GetRelativePath(rootPath, fsEntry.FullName);
#endif
AddFile(fsEntry.FullName, relPath);
}
}
@ -82,27 +67,16 @@ public partial class Build
}
}
private static string GetRelativePath(string relativeTo, string path)
{
var uri = new Uri(relativeTo);
var rel = Uri.UnescapeDataString(uri.MakeRelativeUri(new Uri(path)).ToString()).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
if (rel.Contains(Path.DirectorySeparatorChar.ToString()) == false)
{
rel = $".{Path.DirectorySeparatorChar}{rel}";
}
return rel;
}
class NumergeNukeLogger : INumergeLogger
{
public void Log(NumergeLogLevel level, string message)
{
if(level == NumergeLogLevel.Error)
Logger.Error(message);
Error(message);
else if (level == NumergeLogLevel.Warning)
Logger.Warn(message);
Warning(message);
else
Logger.Info(message);
Information(message);
}
}
}

5
packages/Avalonia/AvaloniaBuildTasks.targets

@ -130,6 +130,7 @@
<AvaloniaXamlOriginalCopyFilePath Condition="'$(AvaloniaXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/original.dll</AvaloniaXamlOriginalCopyFilePath>
<AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
<AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
<AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">false</AvaloniaXamlVerboseExceptions>
</PropertyGroup>
<WriteLinesToFile
Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'"
@ -153,7 +154,9 @@
DelaySign="$(DelaySign)"
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)">
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
VerboseExceptions="$(AvaloniaXamlVerboseExceptions)"
AnalyzerConfigFiles="@(EditorConfigFiles)">
<Output TaskParameter="WrittenFilePaths" ItemName="FileWrites" />
</CompileAvaloniaXamlTask>
<Exec

18
packages/Avalonia/AvaloniaRules.Project.xml

@ -18,11 +18,6 @@
Description="Enable/Disable XAML Compiling"
Category="Compile" />
<BoolProperty Name="AvaloniaXamlIlVerifyIl"
DisplayName="Verify IL"
Description="Enable/Disable Verify IL after XAML Compiling"
Category="Compile" />
<BoolProperty Name="AvaloniaUseCompiledBindingsByDefault"
DisplayName="Use CompiledBindings"
Description="Use compiled bindings by default"
@ -32,10 +27,15 @@
<!-- Debug -->
<BoolProperty Name="AvaloniaXamlIlDebuggerLaunch"
DisplayName="Debug XAML Compiler"
Description="Allow debug XAML compilation"
Category="Debug" />
DisplayName="Debug XAML Compiler"
Description="Allow debug XAML compilation"
Category="Debug" />
<BoolProperty Name="AvaloniaXamlVerboseExceptions"
DisplayName="Report verbose internal exceptions with stack traces"
Description="Also includes inner exceptions"
Category="Debug" />
<EnumProperty Name="AvaloniaXamlReportImportance"
DisplayName="XAML Report Importance"
Description="Provides levels of importance for XAML Compiler messages"

2
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -27,7 +27,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1" />
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.4" />
</ItemGroup>
<ItemGroup>

0
samples/ControlCatalog.Browser/Logo.svg → samples/ControlCatalog.Browser/AppBundle/Logo.svg

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

74
samples/ControlCatalog.Browser/AppBundle/app.css

@ -0,0 +1,74 @@
:root {
--sat: env(safe-area-inset-top);
--sar: env(safe-area-inset-right);
--sab: env(safe-area-inset-bottom);
--sal: env(safe-area-inset-left);
}
/* HTML styles for the splash screen */
.highlight {
color: white;
font-size: 2.5rem;
display: block;
}
.purple {
color: #8b44ac;
}
.icon {
opacity: 0.05;
height: 35%;
width: 35%;
position: absolute;
background-repeat: no-repeat;
right: 0px;
bottom: 0px;
margin-right: 3%;
margin-bottom: 5%;
z-index: 5000;
background-position: right bottom;
pointer-events: none;
}
#avalonia-splash a {
color: whitesmoke;
text-decoration: none;
}
.center {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
#avalonia-splash {
position: relative;
height: 100%;
width: 100%;
color: whitesmoke;
background: #1b2a4e;
font-family: 'Nunito', sans-serif;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
justify-content: center;
align-items: center;
}
.splash-close {
animation: fadeout 0.25s linear forwards;
}
@keyframes fadeout {
0% {
opacity: 100%;
}
100% {
opacity: 0;
visibility: collapse;
}
}

0
samples/ControlCatalog.Browser/embed.js → samples/ControlCatalog.Browser/AppBundle/embed.js

0
samples/ControlCatalog.Browser/favicon.ico → samples/ControlCatalog.Browser/AppBundle/favicon.ico

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

28
samples/ControlCatalog.Browser/AppBundle/index.html

@ -0,0 +1,28 @@
<!DOCTYPE html>
<!-- Licensed to the .NET Foundation under one or more agreements. -->
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
<html>
<head>
<title>AvaloniaUI - ControlCatalog</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./app.css" />
</head>
<body style="margin: 0px; overflow: hidden">
<div id="out">
<div id="avalonia-splash">
<div class="center">
<h2 class="purple">
Powered by
<a class="highlight" href="https://www.avaloniaui.net/" target="_blank">Avalonia UI</a>
</h2>
</div>
<img class="icon" src="Logo.svg" alt="Avalonia Logo" />
</div>
</div>
<script type='module' src="./main.js"></script>
</body>
</html>

5
samples/ControlCatalog.Browser/main.js → samples/ControlCatalog.Browser/AppBundle/main.js

@ -1,7 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
import { dotnet } from './dotnet.js'
import { dotnet } from './dotnet.js' // NET 7
//import { dotnet } from './_framework/dotnet.js' // NET 8+
const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
@ -13,4 +14,4 @@ const dotnetRuntime = await dotnet
const config = dotnetRuntime.getConfig();
await dotnetRuntime.runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]);
await dotnetRuntime.runMainAndExit(config.mainAssemblyName, [globalThis.location.href]);

36
samples/ControlCatalog.Browser/ControlCatalog.Browser.csproj

@ -1,44 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../src/Browser/Avalonia.Browser/Avalonia.Browser.props" />
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<WasmMainJSPath>main.js</WasmMainJSPath>
<WasmMainJSPath>AppBundle/main.js</WasmMainJSPath>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
<WasmBuildNative>true</WasmBuildNative>
<EmccFlags>-sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0</EmccFlags>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<RunAOTCompilation>true</RunAOTCompilation>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<WasmBuildNative>true</WasmBuildNative>
<InvariantGlobalization>true</InvariantGlobalization>
<EmccCompileOptimizationFlag>-O2</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-O2</EmccLinkOptimizationFlag>
</PropertyGroup>
<ItemGroup>
<TrimmerRootDescriptor Include="Roots.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\..\src\Browser\Avalonia.Browser\Avalonia.Browser.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="../../src/Skia/Avalonia.Skia/Avalonia.Skia.csproj" />
<ProjectReference Include="../../src/Browser/Avalonia.Browser/Avalonia.Browser.csproj" />
<ProjectReference Include="../ControlCatalog/ControlCatalog.csproj" />
</ItemGroup>
<ItemGroup>
<WasmExtraFilesToDeploy Include="index.html" />
<WasmExtraFilesToDeploy Include="main.js" />
<WasmExtraFilesToDeploy Include="embed.js" />
<WasmExtraFilesToDeploy Include="favicon.ico" />
<WasmExtraFilesToDeploy Include="Logo.svg" />
<WasmExtraFilesToDeploy Include="app.css" />
<WasmExtraFilesToDeploy Include="AppBundle/**" />
</ItemGroup>
<Import Project="..\..\src\Browser\Avalonia.Browser\Avalonia.Browser.props" />
<Import Project="..\..\src\Browser\Avalonia.Browser\Avalonia.Browser.targets" />
<Import Project="../../src/Browser/Avalonia.Browser/Avalonia.Browser.targets" />
</Project>

3
samples/ControlCatalog.Browser/EmbedSample.Browser.cs

@ -4,6 +4,7 @@ using Avalonia.Platform;
using Avalonia.Browser;
using ControlCatalog.Pages;
using System.Threading.Tasks;
namespace ControlCatalog.Browser;
@ -25,7 +26,7 @@ public class EmbedSampleWeb : INativeDemoControl
_ = JSHost.ImportAsync("embed.js", "./embed.js").ContinueWith(_ =>
{
EmbedInterop.AddAppButton(defaultHandle.Object);
});
}, TaskScheduler.FromCurrentSynchronizationContext());
return defaultHandle;
}

6
samples/ControlCatalog.Browser/Program.cs

@ -1,8 +1,9 @@
using System.Diagnostics;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Browser;
using Avalonia.Controls;
using Avalonia.Logging;
using ControlCatalog;
using ControlCatalog.Browser;
@ -12,7 +13,10 @@ internal partial class Program
{
public static async Task Main(string[] args)
{
Trace.Listeners.Add(new ConsoleTraceListener());
await BuildAvaloniaApp()
.LogToTrace(LogEventLevel.Warning)
.AfterSetup(_ =>
{
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();

7
samples/ControlCatalog.Browser/Roots.xml

@ -1,7 +0,0 @@
<linker>
<assembly fullname="ControlCatalog" preserve="All" />
<assembly fullname="ControlCatalog.Web" preserve="All" />
<assembly fullname="Avalonia.Themes.Fluent" preserve="All" />
<assembly fullname="Avalonia.Themes.Simple" preserve="All" />
<assembly fullname="Avalonia.Controls.ColorPicker" preserve="All" />
</linker>

56
samples/ControlCatalog.Browser/app.css

@ -1,56 +0,0 @@
:root {
--sat: env(safe-area-inset-top);
--sar: env(safe-area-inset-right);
--sab: env(safe-area-inset-bottom);
--sal: env(safe-area-inset-left);
}
#out {
height: 100vh;
width: 100vw
}
#avalonia-splash {
position: relative;
height: 100%;
width: 100%;
color: whitesmoke;
background: #171C2C;
font-family: 'Nunito', sans-serif;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
}
#avalonia-splash a{
color: whitesmoke;
text-decoration: none;
}
.center {
display: flex;
justify-content: center;
height: 250px;
}
.splash-close {
animation: slide 0.5s linear 1s forwards;
}
@keyframes slide {
0% {
top: 0%;
}
50% {
opacity: 80%;
}
100% {
top: 100%;
overflow: hidden;
opacity: 0;
display: none;
visibility: collapse;
}
}

31
samples/ControlCatalog.Browser/index.html

@ -1,31 +0,0 @@
<!DOCTYPE html>
<!-- Licensed to the .NET Foundation under one or more agreements. -->
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
<html>
<head>
<title>AvaloniaUI - ControlCatalog</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="modulepreload" href="./main.js" />
<link rel="modulepreload" href="./dotnet.js" />
<link rel="modulepreload" href="./avalonia.js" />
<link rel="stylesheet" href="./app.css" />
</head>
<body style="margin: 0px">
<div id="out">
<div id="avalonia-splash">
<div class="center">
<h2>Powered by</h2>
<a class="navbar-brand" href="https://www.avaloniaui.net/" target="_blank">
<img src="Logo.svg" alt="Avalonia Logo" width="30" height="24" />
Avalonia
</a>
</div>
</div>
</div>
<script type='module' src="./main.js"></script>
</body>
</html>

3
samples/ControlCatalog/MainView.xaml

@ -74,6 +74,9 @@
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:DataGridPage />
</TabItem>
<TabItem Header="Data Validation">
<pages:DataValidationPage />
</TabItem>
<TabItem Header="Date/Time Picker">
<pages:DateTimePickerPage />
</TabItem>

2
samples/ControlCatalog/MainView.xaml.cs

@ -59,7 +59,7 @@ namespace ControlCatalog
{
if (flowDirections.SelectedItem is FlowDirection flowDirection)
{
TopLevel.GetTopLevel(this).FlowDirection = flowDirection;
TopLevel.GetTopLevel(this)!.FlowDirection = flowDirection;
}
};

42
samples/ControlCatalog/Pages/DataValidationPage.axaml

@ -0,0 +1,42 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:ControlCatalog.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="viewModels:DataValidationViewModel"
x:Class="ControlCatalog.Pages.DataValidationPage">
<UserControl.DataContext>
<viewModels:DataValidationViewModel />
</UserControl.DataContext>
<StackPanel TextBlock.TextWrapping="Wrap">
<Label Target="Email1" Content="Email validation" />
<TextBox x:Name="Email1"
MaxWidth="400"
HorizontalAlignment="Left"
Text="{Binding DataAnnotationsSample}" />
<Label Target="Email2" Content="Email validation with custom error converter"
Margin="0, 10, 0, 0" />
<TextBox x:Name="Email2"
Text="{Binding DataAnnotationsSample}"
MaxWidth="400"
HorizontalAlignment="Left"
DataValidationErrors.ErrorConverter="{Binding Converter}" />
<Label Target="SetterException" Content="Setter exception with custom error converter and tooltip error"
Margin="0, 10, 0, 0" />
<TextBox x:Name="SetterException"
Text="{Binding ExceptionInsideSetterSample}"
MaxWidth="400"
HorizontalAlignment="Left"
DataValidationErrors.ErrorConverter="{Binding ExceptionConverter}">
<TextBox.Styles>
<Style Selector="DataValidationErrors">
<Setter Property="Theme" Value="{DynamicResource TooltipDataValidationErrors}" />
</Style>
</TextBox.Styles>
</TextBox>
</StackPanel>
</UserControl>

14
samples/ControlCatalog/Pages/DataValidationPage.axaml.cs

@ -0,0 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages;
public partial class DataValidationPage : UserControl
{
public DataValidationPage()
{
InitializeComponent();
}
}

6
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -160,7 +160,7 @@ namespace ControlCatalog.Pages
}
else
{
SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result));
SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result!));
results.ItemsSource = new[] { result };
resultsVisible.IsVisible = true;
}
@ -238,7 +238,7 @@ namespace ControlCatalog.Pages
SuggestedStartLocation = lastSelectedDirectory,
SuggestedFileName = "FileName",
DefaultExtension = fileTypes?.Any() == true ? "txt" : null,
ShowOverwritePrompt = false
ShowOverwritePrompt = true
});
if (file is not null)
@ -436,7 +436,7 @@ CanPickFolder: {storageProvider.CanPickFolder}";
{
var forceManaged = this.Get<CheckBox>("ForceManaged").IsChecked ?? false;
return forceManaged
? new ManagedStorageProvider<Window>(GetWindow(), null)
? new ManagedStorageProvider(GetWindow())
: GetTopLevel().StorageProvider;
}

45
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
@ -14,26 +15,54 @@ namespace ControlCatalog.Pages
private const string CustomFormat = "application/xxx-avalonia-controlcatalog-custom";
public DragAndDropPage()
{
this.InitializeComponent();
InitializeComponent();
_dropState = this.Get<TextBlock>("DropState");
int textCount = 0;
SetupDnd("Text", d => d.Set(DataFormats.Text,
$"Text was dragged {++textCount} times"), DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link);
SetupDnd("Custom", d => d.Set(CustomFormat, "Test123"), DragDropEffects.Move);
SetupDnd("Files", async d => d.Set(DataFormats.Files, new[] { await (VisualRoot as TopLevel)!.StorageProvider.TryGetFileFromPathAsync(Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName) }), DragDropEffects.Copy);
SetupDnd(
"Text",
d => d.Set(DataFormats.Text, $"Text was dragged {++textCount} times"),
DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link);
SetupDnd(
"Custom",
d => d.Set(CustomFormat, "Test123"),
DragDropEffects.Move);
SetupDnd(
"Files",
async d =>
{
if (Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName is { } name &&
TopLevel.GetTopLevel(this) is { } topLevel &&
await topLevel.StorageProvider.TryGetFileFromPathAsync(name) is { } storageFile)
{
d.Set(DataFormats.Files, new[] { storageFile });
}
},
DragDropEffects.Copy);
}
void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects)
private void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects) =>
SetupDnd(
suffix,
o =>
{
factory(o);
return Task.CompletedTask;
},
effects);
private void SetupDnd(string suffix, Func<DataObject, Task> factory, DragDropEffects effects)
{
var dragMe = this.Get<Border>("DragMe" + suffix);
var dragState = this.Get<TextBlock>("DragState" + suffix);
async void DoDrag(object? sender, Avalonia.Input.PointerPressedEventArgs e)
async void DoDrag(object? sender, PointerPressedEventArgs e)
{
var dragData = new DataObject();
factory(dragData);
await factory(dragData);
var result = await DragDrop.DoDragDrop(e, dragData, effects);
switch (result)

19
samples/ControlCatalog/Pages/GesturePage.cs

@ -41,12 +41,12 @@ namespace ControlCatalog.Pages
SetPullHandlers(this.Find<Border>("RightPullZone"), true);
SetPullHandlers(this.Find<Border>("LeftPullZone"), false);
var image = this.Find<Image>("PinchImage");
var image = this.Get<Image>("PinchImage");
SetPinchHandlers(image);
var reset = this.Find<Button>("ResetButton");
var reset = this.Get<Button>("ResetButton");
reset!.Click += (s, e) =>
reset.Click += (_, _) =>
{
var compositionVisual = ElementComposition.GetElementVisual(image);
@ -58,7 +58,18 @@ namespace ControlCatalog.Pages
image.InvalidateMeasure();
}
};
if (this.Find<Slider>("AngleSlider") is { } slider &&
this.Find<Panel>("RotationGesture") is { } rotationGesture
)
{
rotationGesture.AddHandler(Gestures.PinchEvent, (s, e) =>
{
slider.Value = e.Angle;
});
}
}
private void SetPinchHandlers(Control? control)

263
samples/ControlCatalog/Pages/GesturePage.xaml

@ -4,114 +4,167 @@
d:DesignHeight="800"
d:DesignWidth="400"
x:Class="ControlCatalog.Pages.GesturePage">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pull Gexture (Touch / Pen)</TextBlock>
<TextBlock Margin="5">Pull from colored rectangles</TextBlock>
<Border>
<DockPanel HorizontalAlignment="Stretch"
ClipToBounds="True"
Margin="5"
Height="200">
<Border DockPanel.Dock="Top"
Margin="2"
Name="TopPullZone"
Background="Transparent"
BorderBrush="Red"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="TopToBottom"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Name="TopBall"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Bottom"
BorderBrush="Green"
Margin="2"
Background="Transparent"
Name="BottomPullZone"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="BottomToTop"/>
</Border.GestureRecognizers>
<Border Width="10"
Name="BottomBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="10"
CornerRadius="5"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Right"
Margin="2"
Background="Transparent"
Name="RightPullZone"
BorderBrush="Blue"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="RightToLeft"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="RightBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
<TabControl>
<TabItem>
<TabItem.Header>
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pull Gexture (Touch / Pen)</TextBlock>
</TabItem.Header>
<StackPanel>
<TextBlock Margin="5">Pull from colored rectangles</TextBlock>
<Border>
<DockPanel HorizontalAlignment="Stretch"
ClipToBounds="True"
Margin="5"
Height="200">
<Border DockPanel.Dock="Top"
Margin="2"
Name="TopPullZone"
Background="Transparent"
BorderBrush="Red"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="TopToBottom"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Name="TopBall"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Bottom"
BorderBrush="Green"
Margin="2"
Background="Transparent"
Name="BottomPullZone"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="BottomToTop"/>
</Border.GestureRecognizers>
<Border Width="10"
Name="BottomBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="10"
CornerRadius="5"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Right"
Margin="2"
Background="Transparent"
Name="RightPullZone"
BorderBrush="Blue"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="RightToLeft"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="RightBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Left"
Margin="2"
Background="Transparent"
Name="LeftPullZone"
BorderBrush="Orange"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="LeftToRight"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="LeftBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
</Border>
</DockPanel>
</Border>
<Border DockPanel.Dock="Left"
Margin="2"
Background="Transparent"
Name="LeftPullZone"
BorderBrush="Orange"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="LeftToRight"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="LeftBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
</StackPanel>
</TabItem>
<TabItem>
<TabItem.Header>
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pinch/Zoom Gexture (Multi Touch)</TextBlock>
</TabItem.Header>
<StackPanel>
<Border ClipToBounds="True">
<Image Stretch="UniformToFill"
Margin="5"
Name="PinchImage"
Source="/Assets/delicate-arch-896885_640.jpg">
<Image.GestureRecognizers>
<PinchGestureRecognizer/>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True"/>
</Image.GestureRecognizers>
</Image>
</Border>
</DockPanel>
</Border>
<Button HorizontalAlignment="Center" Name="ResetButton">Reset</Button>
</StackPanel>
</TabItem>
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pinch/Zoom Gexture (Multi Touch)</TextBlock>
<Border ClipToBounds="True">
<Image Stretch="UniformToFill"
Margin="5"
Name="PinchImage"
Source="/Assets/delicate-arch-896885_640.jpg">
<Image.GestureRecognizers>
<PinchGestureRecognizer/>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True"/>
</Image.GestureRecognizers>
</Image>
</Border>
<Button HorizontalAlignment="Center" Name="ResetButton">Reset</Button>
</StackPanel>
<TabItem>
<TabItem.Header>
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pinch/Rotation Gexture (Multi Touch)</TextBlock>
</TabItem.Header>
<DockPanel>
<Slider Minimum="0"
Maximum="360"
DockPanel.Dock="Bottom"
x:Name="AngleSlider"
/>
<Panel x:Name="RotationGesture">
<Panel.GestureRecognizers>
<PinchGestureRecognizer/>
</Panel.GestureRecognizers>
<Border BorderThickness="1.5" BorderBrush="LawnGreen"/>
<Panel HorizontalAlignment="Center"
Width="100"
Height="100">
<Panel.RenderTransform>
<RotateTransform Angle="{Binding #AngleSlider.Value}"/>
</Panel.RenderTransform>
<Rectangle Fill="SkyBlue"/>
<Rectangle HorizontalAlignment="Center"
VerticalAlignment="Top"
Fill="Yellow"
Width="5"
Height="35"/>
</Panel>
<TextBlock Text="{Binding #AngleSlider.Value, StringFormat=0°}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="DemiBold"
FontSize="20"
/>
</Panel>
</DockPanel>
</TabItem>
</TabControl>
</UserControl>

3
samples/ControlCatalog/Pages/ScrollViewerPage.xaml

@ -17,6 +17,9 @@
Content="Allow auto hide" />
<ToggleSwitch IsChecked="{Binding EnableInertia}"
Content="Enable Inertia" />
<ToggleSwitch IsChecked="{Binding #ScrollViewer.IsDeferredScrollingEnabled}"
ToolTip.Tip="When enabled, dragging a scroll bar thumb won't affect the scrolling content until the pointer is released."
Content="Enable Deferred Scrolling" />
<StackPanel Orientation="Vertical"
Spacing="4">

4
samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs

@ -29,7 +29,7 @@ namespace ControlCatalog.ViewModels
public class IdAndName
{
public string Id { get; set; }
public string Name { get; set; }
public string? Id { get; set; }
public string? Name { get; set; }
}
}

45
samples/ControlCatalog/ViewModels/DataValidationViewModel.cs

@ -0,0 +1,45 @@
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using MiniMvvm;
namespace ControlCatalog.ViewModels;
public class DataValidationViewModel : ViewModelBase
{
private string? _DataAnnotationsSample;
[Required]
[EmailAddress]
[MinLength(5)]
public string? DataAnnotationsSample
{
get => _DataAnnotationsSample;
set => RaiseAndSetIfChanged(ref _DataAnnotationsSample, value);
}
public Func<object, object> Converter { get; } = new Func<object, object>(o =>
{
return $"Error: {o}";
});
private string? _ExceptionInsideSetterSample;
public string? ExceptionInsideSetterSample
{
get => _ExceptionInsideSetterSample;
set
{
if (value is null || value.Length < 5)
throw new ArgumentOutOfRangeException(nameof(value), "Give me 5 or more letter please :-)");
RaiseAndSetIfChanged(ref _ExceptionInsideSetterSample, value);
}
}
public Func<object, object> ExceptionConverter { get; } = new Func<object, object>(o =>
{
return o is Exception ex ? $"Huh, there was an Exception: {ex.Message}" : "Something went really wrong!";
});
}

2
samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs

@ -48,7 +48,7 @@ namespace ControlCatalog.ViewModels
foreach (var item in items)
{
Items.Remove(item);
Items.Remove(item!);
}
});

8
samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs

@ -1,11 +1,9 @@
using System;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Platform;
using MiniMvvm;
namespace ControlCatalog.ViewModels;
#nullable enable
public class PlatformInformationViewModel : ViewModelBase
{
@ -46,11 +44,7 @@ public class PlatformInformationViewModel : ViewModelBase
}
}
}
else
{
}
}
public string PlatformInfo { get; }
public string? PlatformInfo { get; }
}

16
samples/Directory.Build.props

@ -1,10 +1,14 @@
<Project>
<PropertyGroup>
<IsPackable>false</IsPackable>
<AvaloniaPreviewerNetCoreToolPath>$(MSBuildThisFileDirectory)..\src\tools\Avalonia.Designer.HostApp\bin\Debug\netcoreapp2.0\Avalonia.Designer.HostApp.dll</AvaloniaPreviewerNetCoreToolPath>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<LangVersion>11</LangVersion>
<IsPackable>false</IsPackable>
<AvaloniaPreviewerNetCoreToolPath>$(MSBuildThisFileDirectory)..\src\tools\Avalonia.Designer.HostApp\bin\Debug\netcoreapp2.0\Avalonia.Designer.HostApp.dll</AvaloniaPreviewerNetCoreToolPath>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<LangVersion>11</LangVersion>
<NoWarn>$(NoWarn);CS8002</NoWarn> <!-- ignore signing warnings for samples -->
</PropertyGroup>
<Import Project="..\build\SharedVersion.props" />
<Import Project="..\build\DevAnalyzers.props" />
<Import Project="..\build\SharedVersion.props"/>
<Import Project="..\build\DevAnalyzers.props"/>
</Project>

7
samples/Generators.Sandbox/Controls/CustomTextBox.cs

@ -1,10 +1,9 @@
using System;
using Avalonia.Controls;
using Avalonia.Styling;
namespace Generators.Sandbox.Controls;
public class CustomTextBox : TextBox, IStyleable
public class CustomTextBox : TextBox
{
Type IStyleable.StyleKey => typeof(TextBox);
}
protected override Type StyleKeyOverride => typeof(TextBox);
}

50
samples/GpuInterop/D3DDemo/D3D11DemoControl.cs

@ -20,21 +20,21 @@ namespace GpuInterop.D3DDemo;
public class D3D11DemoControl : DrawingSurfaceDemoBase
{
private D3DDevice _device;
private D3D11Swapchain _swapchain;
private SharpDX.Direct3D11.DeviceContext _context;
private D3DDevice? _device;
private D3D11Swapchain? _swapchain;
private DeviceContext? _context;
private Matrix _view;
private PixelSize _lastSize;
private Texture2D _depthBuffer;
private DepthStencilView _depthView;
private Texture2D? _depthBuffer;
private DepthStencilView? _depthView;
private Matrix _proj;
private Buffer _constantBuffer;
private Stopwatch _st = Stopwatch.StartNew();
private Buffer? _constantBuffer;
private readonly Stopwatch _st = Stopwatch.StartNew();
protected override (bool success, string info) InitializeGraphicsResources(Compositor compositor,
CompositionDrawingSurface surface, ICompositionGpuInterop interop)
{
if (interop?.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
if (interop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
.D3D11TextureGlobalSharedHandle) != true)
return (false, "DXGI shared handle import is not supported by the current graphics backend");
@ -60,8 +60,12 @@ public class D3D11DemoControl : DrawingSurfaceDemoBase
protected override void FreeGraphicsResources()
{
_swapchain.DisposeAsync();
_swapchain = null!;
if (_swapchain is not null)
{
_swapchain.DisposeAsync().GetAwaiter().GetResult();
_swapchain = null;
}
Utilities.Dispose(ref _depthView);
Utilities.Dispose(ref _depthBuffer);
Utilities.Dispose(ref _constantBuffer);
@ -80,10 +84,10 @@ public class D3D11DemoControl : DrawingSurfaceDemoBase
_lastSize = pixelSize;
Resize(pixelSize);
}
using (_swapchain.BeginDraw(pixelSize, out var renderView))
using (_swapchain!.BeginDraw(pixelSize, out var renderView))
{
_device.ImmediateContext.OutputMerger.SetTargets(_depthView, renderView);
_device!.ImmediateContext.OutputMerger.SetTargets(_depthView, renderView);
var viewProj = Matrix.Multiply(_view, _proj);
var context = _device.ImmediateContext;
@ -101,10 +105,10 @@ public class D3D11DemoControl : DrawingSurfaceDemoBase
var ypr = Matrix4x4.CreateFromYawPitchRoll(Yaw, Pitch, Roll);
// Update WorldViewProj Matrix
var worldViewProj = Matrix.RotationX((float)Yaw) * Matrix.RotationY((float)Pitch)
* Matrix.RotationZ((float)Roll)
* Matrix.Scaling(new Vector3(scaleX, scaleY, 1))
* viewProj;
var worldViewProj = Matrix.RotationX(Yaw) * Matrix.RotationY(Pitch)
* Matrix.RotationZ(Roll)
* Matrix.Scaling(new Vector3(scaleX, scaleY, 1))
* viewProj;
worldViewProj.Transpose();
context.UpdateSubresource(ref worldViewProj, _constantBuffer);
@ -112,21 +116,25 @@ public class D3D11DemoControl : DrawingSurfaceDemoBase
context.Draw(36, 0);
_context.Flush();
_context!.Flush();
}
}
private void Resize(PixelSize size)
{
Utilities.Dispose(ref _depthBuffer);
if (_device is null)
return;
_depthBuffer = new Texture2D(_device,
new Texture2DDescription()
{
Format = Format.D32_Float_S8X24_UInt,
ArraySize = 1,
MipLevels = 1,
Width = (int)size.Width,
Height = (int)size.Height,
Width = size.Width,
Height = size.Height,
SampleDescription = new SampleDescription(1, 0),
Usage = ResourceUsage.Default,
BindFlags = BindFlags.DepthStencil,
@ -138,9 +146,9 @@ public class D3D11DemoControl : DrawingSurfaceDemoBase
_depthView = new DepthStencilView(_device, _depthBuffer);
// Setup targets and viewport for rendering
_device.ImmediateContext.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f));
_device.ImmediateContext.Rasterizer.SetViewport(new Viewport(0, 0, size.Width, size.Height, 0.0f, 1.0f));
// Setup new projection matrix with correct aspect ratio
_proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f);
_proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, size.Width / (float) size.Height, 0.1f, 100.0f);
}
}

9
samples/GpuInterop/DrawingSurfaceDemoBase.cs

@ -1,5 +1,4 @@
using System;
using System.Numerics;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
@ -13,12 +12,12 @@ public abstract class DrawingSurfaceDemoBase : Control, IGpuDemo
{
private CompositionSurfaceVisual? _visual;
private Compositor? _compositor;
private Action _update;
private string _info;
private readonly Action _update;
private string _info = string.Empty;
private bool _updateQueued;
private bool _initialized;
protected CompositionDrawingSurface Surface { get; private set; }
protected CompositionDrawingSurface? Surface { get; private set; }
public DrawingSurfaceDemoBase()
{
@ -113,7 +112,7 @@ public abstract class DrawingSurfaceDemoBase : Control, IGpuDemo
protected abstract void RenderFrame(PixelSize pixelSize);
protected virtual bool SupportsDisco => false;
public void Update(GpuDemo parent, float yaw, float pitch, float roll, float disco)
public void Update(GpuDemo? parent, float yaw, float pitch, float roll, float disco)
{
ParentControl = parent;
if (ParentControl != null)

12
samples/GpuInterop/GpuDemo.axaml.cs

@ -80,13 +80,13 @@ public class GpuDemo : UserControl
set => SetAndRaise(DiscoVisibleProperty, ref _discoVisible, value);
}
private IGpuDemo _demo;
private IGpuDemo? _demo;
public static readonly DirectProperty<GpuDemo, IGpuDemo> DemoProperty =
AvaloniaProperty.RegisterDirect<GpuDemo, IGpuDemo>("Demo", o => o.Demo,
public static readonly DirectProperty<GpuDemo, IGpuDemo?> DemoProperty =
AvaloniaProperty.RegisterDirect<GpuDemo, IGpuDemo?>("Demo", o => o.Demo,
(o, v) => o._demo = v);
public IGpuDemo Demo
public IGpuDemo? Demo
{
get => _demo;
set => SetAndRaise(DemoProperty, ref _demo, value);
@ -102,7 +102,7 @@ public class GpuDemo : UserControl
)
{
if (change.Property == DemoProperty)
((IGpuDemo)change.OldValue)?.Update(null, 0, 0, 0, 0);
((IGpuDemo?)change.OldValue)?.Update(null, 0, 0, 0, 0);
_demo?.Update(this, Yaw, Pitch, Roll, Disco);
}
@ -112,5 +112,5 @@ public class GpuDemo : UserControl
public interface IGpuDemo
{
void Update(GpuDemo parent, float yaw, float pitch, float roll, float disco);
void Update(GpuDemo? parent, float yaw, float pitch, float roll, float disco);
}

4
samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs

@ -13,7 +13,7 @@ namespace Avalonia.Vulkan
private readonly CommandPool _commandPool;
private readonly List<VulkanCommandBuffer> _usedCommandBuffers = new();
private object _lock = new object();
private readonly object _lock = new();
public unsafe VulkanCommandBufferPool(Vk api, Device device, Queue queue, uint queueFamilyIndex)
{
@ -167,7 +167,7 @@ namespace Avalonia.Vulkan
ReadOnlySpan<PipelineStageFlags> waitDstStageMask = default,
ReadOnlySpan<Semaphore> signalSemaphores = default,
Fence? fence = null,
KeyedMutexSubmitInfo keyedMutex = null)
KeyedMutexSubmitInfo? keyedMutex = null)
{
EndRecording();

14
samples/GpuInterop/VulkanDemo/VulkanContent.cs

@ -39,7 +39,7 @@ unsafe class VulkanContent : IDisposable
{
_context = context;
var name = typeof(VulkanContent).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin"));
using (var sr = new BinaryReader(typeof(VulkanContent).Assembly.GetManifestResourceStream(name)))
using (var sr = new BinaryReader(typeof(VulkanContent).Assembly.GetManifestResourceStream(name)!))
{
var buf = new byte[sr.ReadInt32()];
sr.Read(buf, 0, buf.Length);
@ -115,7 +115,7 @@ unsafe class VulkanContent : IDisposable
{
var name = typeof(VulkanContent).Assembly.GetManifestResourceNames()
.First(x => x.Contains((fragment ? "frag" : "vert") + ".spirv"));
using (var sr = typeof(VulkanContent).Assembly.GetManifestResourceStream(name))
using (var sr = typeof(VulkanContent).Assembly.GetManifestResourceStream(name)!)
{
using (var mem = new MemoryStream())
{
@ -158,7 +158,7 @@ unsafe class VulkanContent : IDisposable
var commandBuffer = _context.Pool.CreateCommandBuffer();
commandBuffer.BeginRecording();
_colorAttachment.TransitionLayout(commandBuffer.InternalHandle,
_colorAttachment!.TransitionLayout(commandBuffer.InternalHandle,
ImageLayout.Undefined, AccessFlags.None,
ImageLayout.ColorAttachmentOptimal, AccessFlags.ColorAttachmentWriteBit);
@ -251,9 +251,9 @@ unsafe class VulkanContent : IDisposable
}
};
api.CmdBlitImage(commandBuffer.InternalHandle, _colorAttachment.InternalHandle.Value,
api.CmdBlitImage(commandBuffer.InternalHandle, _colorAttachment.InternalHandle,
ImageLayout.TransferSrcOptimal,
image.InternalHandle.Value, ImageLayout.TransferDstOptimal, 1, srcBlitRegion, Filter.Linear);
image.InternalHandle, ImageLayout.TransferDstOptimal, 1, srcBlitRegion, Filter.Linear);
commandBuffer.Submit();
}
@ -393,7 +393,7 @@ unsafe class VulkanContent : IDisposable
var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0));
var projection =
Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)((float)size.Width / size.Height),
Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)size.Width / size.Height,
0.01f, 1000);
_colorAttachment = new VulkanImage(_context, (uint)Format.R8G8B8A8Unorm, size, false);
@ -808,7 +808,7 @@ unsafe class VulkanContent : IDisposable
static Stopwatch St = Stopwatch.StartNew();
private bool _isInit;
private VulkanImage _colorAttachment;
private VulkanImage? _colorAttachment;
private DescriptorSet _descriptorSet;
[StructLayout(LayoutKind.Sequential, Pack = 4)]

38
samples/GpuInterop/VulkanDemo/VulkanContext.cs

@ -2,34 +2,31 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Vulkan;
using Silk.NET.Core;
using Silk.NET.Core.Native;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT;
using Silk.NET.Vulkan.Extensions.KHR;
using SilkNetDemo;
using SkiaSharp;
using D3DDevice = SharpDX.Direct3D11.Device;
using DxgiDevice = SharpDX.DXGI.Device;
namespace GpuInterop.VulkanDemo;
public unsafe class VulkanContext : IDisposable
{
public Vk Api { get; init; }
public Instance Instance { get; init; }
public PhysicalDevice PhysicalDevice { get; init; }
public Device Device { get; init; }
public Queue Queue { get; init; }
public uint QueueFamilyIndex { get; init; }
public VulkanCommandBufferPool Pool { get; init; }
public GRContext GrContext { get; init; }
public DescriptorPool DescriptorPool { get; init; }
public D3DDevice? D3DDevice { get; init; }
public required Vk Api { get; init; }
public required Instance Instance { get; init; }
public required PhysicalDevice PhysicalDevice { get; init; }
public required Device Device { get; init; }
public required Queue Queue { get; init; }
public required uint QueueFamilyIndex { get; init; }
public required VulkanCommandBufferPool Pool { get; init; }
public required GRContext GrContext { get; init; }
public required DescriptorPool DescriptorPool { get; init; }
public required D3DDevice? D3DDevice { get; init; }
public static (VulkanContext? result, string info) TryCreate(ICompositionGpuInterop gpuInterop)
{
@ -58,10 +55,8 @@ public unsafe class VulkanContext : IDisposable
enabledExtensions.Add("VK_EXT_debug_utils");
if (IsLayerAvailable(api, "VK_LAYER_KHRONOS_validation"))
enabledLayers.Add("VK_LAYER_KHRONOS_validation");
Instance vkInstance = default;
Silk.NET.Vulkan.PhysicalDevice physicalDevice = default;
Device device = default;
DescriptorPool descriptorPool = default;
VulkanCommandBufferPool? pool = null;
@ -78,7 +73,7 @@ public unsafe class VulkanContext : IDisposable
EnabledExtensionCount = pRequiredExtensions.UCount,
PpEnabledLayerNames = pEnabledLayers,
EnabledLayerCount = pEnabledLayers.UCount
}, null, out vkInstance).ThrowOnError();
}, null, out var vkInstance).ThrowOnError();
if (api.TryGetInstanceExtension(vkInstance, out ExtDebugUtils debugUtils))
@ -95,7 +90,7 @@ public unsafe class VulkanContext : IDisposable
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(LogCallback),
};
debugUtils.CreateDebugUtilsMessenger(vkInstance, debugCreateInfo, null, out var messenger);
debugUtils.CreateDebugUtilsMessenger(vkInstance, debugCreateInfo, null, out _);
}
var requireDeviceExtensions = new List<string>
@ -158,11 +153,11 @@ public unsafe class VulkanContext : IDisposable
else if (gpuInterop.DeviceUuid != null)
{
if (!new Span<byte>(physicalDeviceIDProperties.DeviceUuid, 16)
.SequenceEqual(gpuInterop?.DeviceUuid))
.SequenceEqual(gpuInterop.DeviceUuid))
continue;
}
physicalDevice = physicalDevices[c];
var physicalDevice = physicalDevices[c];
var name = Marshal.PtrToStringAnsi(new IntPtr(physicalDeviceProperties2.Properties.DeviceName))!;
@ -251,8 +246,7 @@ public unsafe class VulkanContext : IDisposable
RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
d3dDevice = D3DMemoryHelper.CreateDeviceByLuid(
new Span<byte>(physicalDeviceIDProperties.DeviceLuid, 8));
var dxgiDevice = d3dDevice?.QueryInterface<DxgiDevice>();
return (new VulkanContext
{
Api = api,

30
samples/GpuInterop/VulkanDemo/VulkanImage.cs

@ -24,23 +24,23 @@ public unsafe class VulkanImage : IDisposable
private ImageLayout _currentLayout;
private AccessFlags _currentAccessFlags;
private ImageUsageFlags _imageUsageFlags { get; }
private ImageView? _imageView { get; set; }
private ImageView _imageView { get; set; }
private DeviceMemory _imageMemory { get; set; }
private SharpDX.Direct3D11.Texture2D? _d3dTexture2D;
private readonly SharpDX.Direct3D11.Texture2D? _d3dTexture2D;
internal Image? InternalHandle { get; private set; }
internal Image InternalHandle { get; private set; }
internal Format Format { get; }
internal ImageAspectFlags AspectFlags { get; private set; }
internal ImageAspectFlags AspectFlags { get; }
public ulong Handle => InternalHandle?.Handle ?? 0;
public ulong ViewHandle => _imageView?.Handle ?? 0;
public ulong Handle => InternalHandle.Handle;
public ulong ViewHandle => _imageView.Handle;
public uint UsageFlags => (uint) _imageUsageFlags;
public ulong MemoryHandle => _imageMemory.Handle;
public DeviceMemory DeviceMemory => _imageMemory;
public uint MipLevels { get; private set; }
public uint MipLevels { get; }
public Vk Api { get; }
public PixelSize Size { get; }
public ulong MemorySize { get; private set; }
public ulong MemorySize { get; }
public uint CurrentLayout => (uint) _currentLayout;
public VulkanImage(VulkanContext vk, uint format, PixelSize size,
@ -93,7 +93,7 @@ public unsafe class VulkanImage : IDisposable
.CreateImage(_device, imageCreateInfo, null, out var image).ThrowOnError();
InternalHandle = image;
Api.GetImageMemoryRequirements(_device, InternalHandle.Value,
Api.GetImageMemoryRequirements(_device, InternalHandle,
out var memoryRequirements);
@ -109,7 +109,7 @@ public unsafe class VulkanImage : IDisposable
ImportMemoryWin32HandleInfoKHR handleImport = default;
if (exportable && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(vk.D3DDevice, size, Format);
_d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(vk.D3DDevice!, size, Format);
using var dxgi = _d3dTexture2D.QueryInterface<SharpDX.DXGI.Resource1>();
handleImport = new ImportMemoryWin32HandleInfoKHR
@ -141,7 +141,7 @@ public unsafe class VulkanImage : IDisposable
MemorySize = memoryRequirements.Size;
Api.BindImageMemory(_device, InternalHandle.Value, _imageMemory, 0).ThrowOnError();
Api.BindImageMemory(_device, InternalHandle, _imageMemory, 0).ThrowOnError();
var componentMapping = new ComponentMapping(
ComponentSwizzle.Identity,
ComponentSwizzle.Identity,
@ -155,7 +155,7 @@ public unsafe class VulkanImage : IDisposable
var imageViewCreateInfo = new ImageViewCreateInfo
{
SType = StructureType.ImageViewCreateInfo,
Image = InternalHandle.Value,
Image = InternalHandle,
ViewType = ImageViewType.Type2D,
Format = Format,
Components = componentMapping,
@ -209,7 +209,7 @@ public unsafe class VulkanImage : IDisposable
ImageLayout fromLayout, AccessFlags fromAccessFlags,
ImageLayout destinationLayout, AccessFlags destinationAccessFlags)
{
VulkanMemoryHelper.TransitionLayout(Api, commandBuffer, InternalHandle.Value,
VulkanMemoryHelper.TransitionLayout(Api, commandBuffer, InternalHandle,
fromLayout,
fromAccessFlags,
destinationLayout, destinationAccessFlags,
@ -241,8 +241,8 @@ public unsafe class VulkanImage : IDisposable
public unsafe void Dispose()
{
Api.DestroyImageView(_device, _imageView.Value, null);
Api.DestroyImage(_device, InternalHandle.Value, null);
Api.DestroyImageView(_device, _imageView, null);
Api.DestroyImage(_device, InternalHandle, null);
Api.FreeMemory(_device, _imageMemory, null);
_imageView = default;

2
samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs

@ -146,6 +146,6 @@ class VulkanSwapchainImage : ISwapchainImage
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
_lastPresent = _target.UpdateWithKeyedMutexAsync(_importedImage, 1, 0);
else
_lastPresent = _target.UpdateWithSemaphoresAsync(_importedImage, _renderCompletedSemaphore, _availableSemaphore);
_lastPresent = _target.UpdateWithSemaphoresAsync(_importedImage, _renderCompletedSemaphore!, _availableSemaphore!);
}
}

37
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -42,23 +42,20 @@ namespace IntegrationTestApp
private void InitializeViewMenu()
{
var mainTabs = this.Get<TabControl>("MainTabs");
var viewMenu = (NativeMenuItem)NativeMenu.GetMenu(this).Items[1];
var viewMenu = (NativeMenuItem?)NativeMenu.GetMenu(this)?.Items[1];
if (mainTabs.Items is not null)
foreach (var tabItem in mainTabs.Items.Cast<TabItem>())
{
foreach (TabItem tabItem in mainTabs.Items)
var menuItem = new NativeMenuItem
{
var menuItem = new NativeMenuItem
{
Header = (string)tabItem.Header!,
ToolTip = (string)tabItem.Header!,
IsChecked = tabItem.IsSelected,
ToggleType = NativeMenuItemToggleType.Radio,
};
menuItem.Click += (s, e) => tabItem.IsSelected = true;
viewMenu?.Menu?.Items.Add(menuItem);
}
Header = (string?)tabItem.Header,
ToolTip = (string?)tabItem.Header,
IsChecked = tabItem.IsSelected,
ToggleType = NativeMenuItemToggleType.Radio,
};
menuItem.Click += (_, _) => tabItem.IsSelected = true;
viewMenu?.Menu?.Items.Add(menuItem);
}
}
@ -77,7 +74,7 @@ namespace IntegrationTestApp
var window = new ShowWindowTest
{
WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex,
CanResize = canResizeCheckBox.IsChecked.Value,
CanResize = canResizeCheckBox.IsChecked ?? false,
};
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
@ -227,9 +224,9 @@ namespace IntegrationTestApp
var gestureBorder2 = this.GetControl<Border>("GestureBorder2");
var lastGesture = this.GetControl<TextBlock>("LastGesture");
var resetGestures = this.GetControl<Button>("ResetGestures");
gestureBorder.Tapped += (s, e) => lastGesture.Text = "Tapped";
gestureBorder.Tapped += (_, _) => lastGesture.Text = "Tapped";
gestureBorder.DoubleTapped += (s, e) =>
gestureBorder.DoubleTapped += (_, _) =>
{
lastGesture.Text = "DoubleTapped";
@ -238,14 +235,14 @@ namespace IntegrationTestApp
gestureBorder2.IsVisible = true;
};
gestureBorder2.DoubleTapped += (s, e) =>
gestureBorder2.DoubleTapped += (_, _) =>
{
lastGesture.Text = "DoubleTapped2";
};
Gestures.AddRightTappedHandler(gestureBorder, (s, e) => lastGesture.Text = "RightTapped");
Gestures.AddRightTappedHandler(gestureBorder, (_, _) => lastGesture.Text = "RightTapped");
resetGestures.Click += (s, e) =>
resetGestures.Click += (_, _) =>
{
lastGesture.Text = string.Empty;
gestureBorder.IsVisible = true;

2
samples/MobileSandbox.Android/MobileSandbox.Android.csproj

@ -27,7 +27,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1" />
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.4" />
</ItemGroup>
<ItemGroup>

2
samples/MobileSandbox/MainView.xaml

@ -8,7 +8,7 @@
<TextBox TextInputOptions.Multiline="True" AcceptsReturn="True" Watermark="Text" Height="200" TextWrapping="Wrap"/>
<TextBox Watermark="Username" TextInputOptions.ContentType="Email" TextInputOptions.ReturnKeyType="Done" />
<TextBox Watermark="Password" PasswordChar="*" TextInputOptions.ContentType="Password" />
<TextBox Watermark="Pin" PasswordChar="*" TextInputOptions.ContentType="Digits" />
<TextBox Watermark="Pin" PasswordChar="*" TextInputOptions.ContentType="Digits" TextInputOptions.ReturnKeyType="Next" />
<Button Content="Login" Command="{Binding ButtonCommand}" />
</StackPanel>
</UserControl>

4
samples/SafeAreaDemo.iOS/Info.plist

@ -39,9 +39,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

6
samples/SafeAreaDemo/App.xaml

@ -2,9 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SafeAreaDemo"
x:Class="SafeAreaDemo.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
RequestedThemeVariant="Light">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
@ -12,4 +10,4 @@
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
</Application>

12
samples/SafeAreaDemo/App.xaml.cs

@ -17,20 +17,14 @@ namespace SafeAreaDemo
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainViewModel()
};
desktop.MainWindow = new MainWindow();
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = new MainView
{
DataContext = new MainViewModel()
};
singleViewPlatform.MainView = new MainView();
}
base.OnFrameworkInitializationCompleted();
}
}
}
}

47
samples/SafeAreaDemo/ViewModels/MainViewModel.cs

@ -1,4 +1,5 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using MiniMvvm;
@ -7,15 +8,16 @@ namespace SafeAreaDemo.ViewModels
public class MainViewModel : ViewModelBase
{
private bool _useSafeArea = true;
private bool _fullscreen;
private bool _displayEdgeToEdge;
private IInsetsManager? _insetsManager;
private bool _hideSystemBars;
private bool _autoSafeAreaPadding;
public Thickness SafeAreaPadding
{
get
{
return _insetsManager?.SafeAreaPadding ?? default;
return !_autoSafeAreaPadding ? _insetsManager?.SafeAreaPadding ?? default : default;
}
}
@ -40,12 +42,12 @@ namespace SafeAreaDemo.ViewModels
}
}
public bool Fullscreen
public bool DisplayEdgeToEdge
{
get => _fullscreen;
get => _displayEdgeToEdge;
set
{
_fullscreen = value;
_displayEdgeToEdge = value;
if (_insetsManager != null)
{
@ -76,25 +78,34 @@ namespace SafeAreaDemo.ViewModels
}
}
internal IInsetsManager? InsetsManager
public bool AutoSafeAreaPadding
{
get => _insetsManager;
get => _autoSafeAreaPadding;
set
{
if (_insetsManager != null)
{
_insetsManager.SafeAreaChanged -= InsetsManager_SafeAreaChanged;
}
_autoSafeAreaPadding = value;
RaisePropertyChanged();
RaiseSafeAreaChanged();
}
}
internal void Initialize(Control mainView, IInsetsManager? InsetsManager)
{
if (_insetsManager != null)
{
_insetsManager.SafeAreaChanged -= InsetsManager_SafeAreaChanged;
}
_insetsManager = value;
_autoSafeAreaPadding = mainView.GetValue(TopLevel.AutoSafeAreaPaddingProperty);
_insetsManager = InsetsManager;
if (_insetsManager != null)
{
_insetsManager.SafeAreaChanged += InsetsManager_SafeAreaChanged;
if (_insetsManager != null)
{
_insetsManager.SafeAreaChanged += InsetsManager_SafeAreaChanged;
_insetsManager.DisplayEdgeToEdge = _fullscreen;
_insetsManager.IsSystemBarVisible = !_hideSystemBars;
}
_displayEdgeToEdge = _insetsManager.DisplayEdgeToEdge;
_hideSystemBars = !(_insetsManager.IsSystemBarVisible ?? false);
}
}

9
samples/SafeAreaDemo/Views/MainView.xaml

@ -7,7 +7,9 @@
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="SafeAreaDemo.Views.MainView"
x:DataType="vm:MainViewModel">
x:DataType="vm:MainViewModel"
Background="#ccc"
TopLevel.AutoSafeAreaPadding="{Binding AutoSafeAreaPadding, Mode=TwoWay}">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Border BorderBrush="Red"
@ -40,8 +42,9 @@
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Label HorizontalAlignment="Left">Options:</Label>
<CheckBox IsChecked="{Binding Fullscreen}">Fullscreen</CheckBox>
<CheckBox IsChecked="{Binding UseSafeArea}">Use Safe Area</CheckBox>
<CheckBox IsChecked="{Binding DisplayEdgeToEdge}">Display Edge To Edge</CheckBox>
<CheckBox IsChecked="{Binding UseSafeArea}" IsEnabled="{Binding !AutoSafeAreaPadding}">Use Safe Area</CheckBox>
<CheckBox IsChecked="{Binding AutoSafeAreaPadding}">Automatic Paddings</CheckBox>
<CheckBox IsChecked="{Binding HideSystemBars}">Hide System Bars</CheckBox>
<TextBox Width="200" Watermark="Tap to Show Keyboard"/>
</StackPanel>

7
samples/SafeAreaDemo/Views/MainView.xaml.cs

@ -18,10 +18,9 @@ namespace SafeAreaDemo.Views
base.OnLoaded(e);
var insetsManager = TopLevel.GetTopLevel(this)?.InsetsManager;
if (insetsManager != null && DataContext is MainViewModel viewModel)
{
viewModel.InsetsManager = insetsManager;
}
var viewModel = new MainViewModel();
viewModel.Initialize(this, insetsManager);
DataContext = viewModel;
}
}
}

1
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@ -4,6 +4,7 @@
<TargetFrameworks>net461;net6.0-windows</TargetFrameworks>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
</PropertyGroup>
<ItemGroup>

6
src/Android/Avalonia.Android/Avalonia.Android.csproj

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" />
<PackageReference Include="Xamarin.AndroidX.DocumentFile" Version="1.0.1.16" />
<PackageReference Include="Xamarin.AndroidX.Lifecycle.ViewModel" Version="2.3.1.3" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.6.1.5" />
<PackageReference Include="Xamarin.AndroidX.DocumentFile" Version="1.0.1.21" />
<PackageReference Include="Xamarin.AndroidX.Lifecycle.ViewModel" Version="2.6.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />

12
src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
#nullable enable
namespace Avalonia.Android
{
@ -11,9 +7,9 @@ namespace Avalonia.Android
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
private static AppBuilder? s_appBuilder;
internal static object ViewContent;
internal static object? ViewContent;
public object Content
public object? Content
{
get
{
@ -51,7 +47,7 @@ namespace Avalonia.Android
View.Content = ViewContent;
}
if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime)
if (Avalonia.Application.Current?.ApplicationLifetime is SingleViewLifetime lifetime)
{
lifetime.View = View;
}

15
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -1,9 +1,8 @@
using System;
using System.Diagnostics;
using System.Runtime.Versioning;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using Android.Views;
@ -37,6 +36,7 @@ namespace Avalonia.Android
ActivityResult?.Invoke(requestCode, resultCode, data);
}
[SupportedOSPlatform("android23.0")]
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
@ -47,7 +47,8 @@ namespace Avalonia.Android
public abstract partial class AvaloniaMainActivity<TApp> : AvaloniaMainActivity where TApp : Application, new()
{
internal AvaloniaView View;
internal AvaloniaView View { get; set; }
private GlobalLayoutListener _listener;
protected override void OnCreate(Bundle savedInstanceState)
@ -68,9 +69,9 @@ namespace Avalonia.Android
base.OnResume();
// Android only respects LayoutInDisplayCutoutMode value if it has been set once before window becomes visible.
if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
if (OperatingSystem.IsAndroidVersionAtLeast(28) && Window is { Attributes: { } attributes })
{
Window.Attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
}
}
@ -83,9 +84,9 @@ namespace Avalonia.Android
base.OnDestroy();
}
class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
private class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
{
private AvaloniaView _view;
private readonly AvaloniaView _view;
public GlobalLayoutListener(AvaloniaView view)
{

2
src/Android/Avalonia.Android/AvaloniaView.cs

@ -1,4 +1,5 @@
using System;
using System.Runtime.Versioning;
using Android.Content;
using Android.Content.Res;
using Android.Runtime;
@ -46,6 +47,7 @@ namespace Avalonia.Android
return _view.View.DispatchKeyEvent(e);
}
[SupportedOSPlatform("android24.0")]
public override void OnVisibilityAggregated(bool isVisible)
{
base.OnVisibilityAggregated(isVisible);

37
src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs

@ -30,17 +30,25 @@ namespace Avalonia.Android.Platform
{
_displayEdgeToEdge = value;
if(Build.VERSION.SdkInt >= BuildVersionCodes.P)
var window = _activity.Window;
if (OperatingSystem.IsAndroidVersionAtLeast(28) && window?.Attributes is { } attributes)
{
_activity.Window.Attributes.LayoutInDisplayCutoutMode = value ? LayoutInDisplayCutoutMode.ShortEdges : LayoutInDisplayCutoutMode.Default;
attributes.LayoutInDisplayCutoutMode = value ? LayoutInDisplayCutoutMode.ShortEdges : LayoutInDisplayCutoutMode.Default;
}
WindowCompat.SetDecorFitsSystemWindows(_activity.Window, !value);
if (window is not null)
{
WindowCompat.SetDecorFitsSystemWindows(_activity.Window, !value);
}
if(value)
{
_activity.Window.AddFlags(WindowManagerFlags.TranslucentStatus);
_activity.Window.AddFlags(WindowManagerFlags.TranslucentNavigation);
if (window is not null)
{
window.AddFlags(WindowManagerFlags.TranslucentStatus);
window.AddFlags(WindowManagerFlags.TranslucentNavigation);
}
}
else
{
@ -57,14 +65,17 @@ namespace Avalonia.Android.Platform
_callback.InsetsManager = this;
ViewCompat.SetOnApplyWindowInsetsListener(_activity.Window.DecorView, this);
if (_activity.Window is { } window)
{
ViewCompat.SetOnApplyWindowInsetsListener(window.DecorView, this);
ViewCompat.SetWindowInsetsAnimationCallback(_activity.Window.DecorView, _callback);
ViewCompat.SetWindowInsetsAnimationCallback(window.DecorView, _callback);
}
if(Build.VERSION.SdkInt < BuildVersionCodes.R)
if (Build.VERSION.SdkInt < BuildVersionCodes.R)
{
_usesLegacyLayouts = true;
_activity.Window.DecorView.ViewTreeObserver.AddOnGlobalLayoutListener(this);
_activity.Window?.DecorView.ViewTreeObserver?.AddOnGlobalLayoutListener(this);
}
DisplayEdgeToEdge = false;
@ -74,7 +85,7 @@ namespace Avalonia.Android.Platform
{
get
{
var insets = ViewCompat.GetRootWindowInsets(_activity.Window.DecorView);
var insets = _activity.Window is { } window ? ViewCompat.GetRootWindowInsets(window.DecorView) : null;
if (insets != null)
{
@ -127,7 +138,7 @@ namespace Avalonia.Android.Platform
return compat.AppearanceLightStatusBars ? Controls.Platform.SystemBarTheme.Light : Controls.Platform.SystemBarTheme.Dark;
}
catch (Exception _)
catch (Exception)
{
return Controls.Platform.SystemBarTheme.Light;
}
@ -136,8 +147,6 @@ namespace Avalonia.Android.Platform
{
_statusBarTheme = value;
var isDefault = _statusBarTheme == null;
if (!_topLevel.View.IsShown)
{
return;
@ -150,7 +159,7 @@ namespace Avalonia.Android.Platform
_isDefaultSystemBarLightTheme = compat.AppearanceLightStatusBars;
}
if (value == null && _isDefaultSystemBarLightTheme != null)
if (value == null)
{
value = (bool)_isDefaultSystemBarLightTheme ? Controls.Platform.SystemBarTheme.Light : Controls.Platform.SystemBarTheme.Dark;
}

8
src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs

@ -1,4 +1,6 @@
using System;
#nullable enable
using System;
using Avalonia.Interactivity;
using Avalonia.Platform;
@ -6,7 +8,7 @@ namespace Avalonia.Android.Platform
{
internal class AndroidSystemNavigationManagerImpl : ISystemNavigationManagerImpl
{
public event EventHandler<RoutedEventArgs> BackRequested;
public event EventHandler<RoutedEventArgs>? BackRequested;
public AndroidSystemNavigationManagerImpl(IActivityNavigationService? navigationService)
{
@ -16,7 +18,7 @@ namespace Avalonia.Android.Platform
}
}
private void OnBackRequested(object sender, AndroidBackRequestedEventArgs e)
private void OnBackRequested(object? sender, AndroidBackRequestedEventArgs e)
{
var routedEventArgs = new RoutedEventArgs();

22
src/Android/Avalonia.Android/Platform/ClipboardImpl.cs

@ -1,8 +1,8 @@
#nullable enable
using System;
using System.Threading.Tasks;
using Android.Content;
using Avalonia.Input;
using Avalonia.Input.Platform;
@ -10,34 +10,34 @@ namespace Avalonia.Android.Platform
{
internal class ClipboardImpl : IClipboard
{
private ClipboardManager? _clipboardManager;
private readonly ClipboardManager? _clipboardManager;
internal ClipboardImpl(ClipboardManager? value)
{
_clipboardManager = value;
}
public Task<string> GetTextAsync()
public Task<string?> GetTextAsync()
{
if (_clipboardManager?.HasPrimaryClip == true)
{
return Task.FromResult<string>(_clipboardManager.PrimaryClip.GetItemAt(0).Text);
return Task.FromResult(_clipboardManager.PrimaryClip?.GetItemAt(0)?.Text);
}
return Task.FromResult<string>(null);
return Task.FromResult<string?>(null);
}
public Task SetTextAsync(string text)
public Task SetTextAsync(string? text)
{
if(_clipboardManager == null)
{
return Task.CompletedTask;
}
ClipData clip = ClipData.NewPlainText("text", text);
var clip = ClipData.NewPlainText("text", text);
_clipboardManager.PrimaryClip = clip;
return Task.FromResult<object>(null);
return Task.CompletedTask;
}
public Task ClearAsync()
@ -49,13 +49,13 @@ namespace Avalonia.Android.Platform
_clipboardManager.PrimaryClip = null;
return Task.FromResult<object>(null);
return Task.CompletedTask;
}
public Task SetDataObjectAsync(IDataObject data) => throw new PlatformNotSupportedException();
public Task<string[]> GetFormatsAsync() => throw new PlatformNotSupportedException();
public Task<object> GetDataAsync(string format) => throw new PlatformNotSupportedException();
public Task<object?> GetDataAsync(string format) => throw new PlatformNotSupportedException();
}
}

10
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -347,7 +347,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
activity.SetTranslucent(true);
SetBlurBehind(activity, 0);
activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent));
activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.Transparent));
}
}
else if (level == WindowTransparencyLevel.Blur)
@ -448,8 +448,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
private readonly AvaloniaInputConnection _inputConnection;
public event EventHandler<int> SelectionChanged;
public EditableWrapper(AvaloniaInputConnection inputConnection)
{
_inputConnection = inputConnection;
@ -624,6 +622,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_inputMethod.IMM.HideSoftInputFromWindow(_inputMethod.View.WindowToken, HideSoftInputFlags.ImplicitOnly);
break;
}
case ImeAction.Next:
{
FocusManager.GetFocusManager(_toplevel.InputRoot)?
.TryMoveFocus(NavigationDirection.Next);
break;
}
}
return base.PerformEditorAction(actionCode);

8
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -55,7 +55,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
var keySymbol = GetKeySymbol(e.UnicodeChar, physicalKey);
var rawKeyEvent = new RawKeyEventArgs(
AndroidKeyboardDevice.Instance,
AndroidKeyboardDevice.Instance!,
Convert.ToUInt64(e.EventTime),
_view.InputRoot,
e.Action == KeyEventActions.Down ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
@ -64,18 +64,18 @@ namespace Avalonia.Android.Platform.Specific.Helpers
physicalKey,
keySymbol);
_view.Input(rawKeyEvent);
_view.Input?.Invoke(rawKeyEvent);
if ((e.Action == KeyEventActions.Down && e.UnicodeChar >= 32)
|| unicodeTextInput != null)
{
var rawTextEvent = new RawTextInputEventArgs(
AndroidKeyboardDevice.Instance,
AndroidKeyboardDevice.Instance!,
Convert.ToUInt64(e.EventTime),
_view.InputRoot,
unicodeTextInput ?? Convert.ToChar(e.UnicodeChar).ToString()
);
_view.Input(rawTextEvent);
_view.Input?.Invoke(rawTextEvent);
}
if (e.Action == KeyEventActions.Up)

45
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

@ -172,29 +172,29 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
{
}
public async Task<IStorageFile?> CreateFileAsync(string name)
public Task<IStorageFile?> CreateFileAsync(string name)
{
var mimeType = MimeTypeMap.Singleton?.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(name)) ?? "application/octet-stream";
var newFile = Document.CreateFile(mimeType, name);
var newFile = Document?.CreateFile(mimeType, name);
if(newFile == null)
{
return null;
return Task.FromResult<IStorageFile?>(null);
}
return new AndroidStorageFile(Activity, newFile.Uri, this);
return Task.FromResult<IStorageFile?>(new AndroidStorageFile(Activity, newFile.Uri, this));
}
public async Task<IStorageFolder?> CreateFolderAsync(string name)
public Task<IStorageFolder?> CreateFolderAsync(string name)
{
var newFolder = Document?.CreateDirectory(name);
if (newFolder == null)
{
return null;
return Task.FromResult<IStorageFolder?>(null);
}
return new AndroidStorageFolder(Activity, newFolder.Uri, false, this, PermissionRoot);
return Task.FromResult<IStorageFolder?>(new AndroidStorageFolder(Activity, newFolder.Uri, false, this, PermissionRoot));
}
public override async Task DeleteAsync()
@ -286,15 +286,15 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
return null;
async Task<AndroidStorageFolder?> MoveRecursively(AndroidStorageFolder storageFolder, AndroidStorageFolder destination)
static async Task<AndroidStorageFolder?> MoveRecursively(AndroidStorageFolder storageFolder, AndroidStorageFolder destination)
{
destination = await destination.CreateFolderAsync(storageFolder.Name) as AndroidStorageFolder;
if (destination == null)
if (await destination.CreateFolderAsync(storageFolder.Name) is not AndroidStorageFolder newDestination)
{
return null;
}
destination = newDestination;
await foreach (var file in storageFolder.GetItemsAsync())
{
if (file is AndroidStorageFolder folder)
@ -477,25 +477,32 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
if (Activity != null && destination is AndroidStorageFolder storageFolder)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.N)
AndroidUri? movedUri = null;
if (OperatingSystem.IsAndroidVersionAtLeast(24))
{
try
{
var uri = DocumentsContract.MoveDocument(Activity.ContentResolver!, Uri, ((await GetParentAsync()) as AndroidStorageFolder)!.Uri, storageFolder.Document!.Uri);
return new AndroidStorageFile(Activity, uri, storageFolder);
if (Activity.ContentResolver is { } contentResolver &&
storageFolder.Document?.Uri is { } targetParentUri &&
await GetParentAsync() is AndroidStorageFolder parentFolder)
{
movedUri = DocumentsContract.MoveDocument(contentResolver, Uri, parentFolder.Uri, targetParentUri);
}
}
catch (Exception ex)
catch (Exception)
{
// There are many reason why DocumentContract will fail to move a file. We fallback to copying.
return await MoveFileByCopy();
}
}
else
if (movedUri is not null)
{
return await MoveFileByCopy();
return new AndroidStorageFile(Activity, movedUri, storageFolder);
}
return await MoveFileByCopy();
}
async Task<AndroidStorageFile?> MoveFileByCopy()

2
src/Avalonia.Base/Animation/PageSlide.cs

@ -47,7 +47,7 @@ namespace Avalonia.Animation
public TimeSpan Duration { get; set; }
/// <summary>
/// Gets the duration of the animation.
/// Gets the orientation of the animation.
/// </summary>
public SlideAxis Orientation { get; set; }

5
src/Avalonia.Base/Collections/AvaloniaList.cs

@ -3,7 +3,6 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Avalonia.Diagnostics;
namespace Avalonia.Collections
@ -14,13 +13,13 @@ namespace Avalonia.Collections
public enum ResetBehavior
{
/// <summary>
/// Clearing the list notifies with the <see cref="CollectionChanged"/> event with a
/// Clearing the list notifies with the <see cref="INotifyCollectionChanged.CollectionChanged"/> event with a
/// <see cref="NotifyCollectionChangedAction.Reset"/> action.
/// </summary>
Reset,
/// <summary>
/// Clearing the list notifies with the <see cref="CollectionChanged"/> event with a
/// Clearing the list notifies with the <see cref="INotifyCollectionChanged.CollectionChanged"/> event with a
/// <see cref="NotifyCollectionChangedAction.Remove"/> action.
/// </summary>
Remove,

203
src/Avalonia.Base/Input/FocusManager.cs

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.VisualTree;
@ -15,10 +12,16 @@ namespace Avalonia.Input
public class FocusManager : IFocusManager
{
/// <summary>
/// The focus scopes in which the focus is currently defined.
/// Private attached property for storing the currently focused element in a focus scope.
/// </summary>
private readonly ConditionalWeakTable<IFocusScope, IInputElement?> _focusScopes =
new ConditionalWeakTable<IFocusScope, IInputElement?>();
/// <remarks>
/// This property is set on the control which defines a focus scope and tracks the currently
/// focused element within that scope.
/// </remarks>
private static readonly AttachedProperty<IInputElement> FocusedElementProperty =
AvaloniaProperty.RegisterAttached<FocusManager, StyledElement, IInputElement>("FocusedElement");
private StyledElement? _focusRoot;
/// <summary>
/// Initializes a new instance of the <see cref="FocusManager"/> class.
@ -38,15 +41,6 @@ namespace Avalonia.Input
/// </summary>
public IInputElement? GetFocusedElement() => Current;
/// <summary>
/// Gets the current focus scope.
/// </summary>
public IFocusScope? Scope
{
get;
private set;
}
/// <summary>
/// Focuses a control.
/// </summary>
@ -58,38 +52,35 @@ namespace Avalonia.Input
NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None)
{
if (control != null)
if (KeyboardDevice.Instance is not { } keyboardDevice)
return false;
if (control is not null)
{
var scope = GetFocusScopeAncestors(control)
.FirstOrDefault();
if (!CanFocus(control))
return false;
if (scope != null)
if (GetFocusScope(control) is StyledElement scope)
{
Scope = scope;
return SetFocusedElement(scope, control, method, keyModifiers);
scope.SetValue(FocusedElementProperty, control);
_focusRoot = GetFocusRoot(scope);
}
keyboardDevice.SetFocusedElement(control, method, keyModifiers);
return true;
}
else if (Current != null)
else if (_focusRoot?.GetValue(FocusedElementProperty) is { } restore &&
restore != Current &&
Focus(restore))
{
// If control is null, set focus to the topmost focus scope.
foreach (var scope in GetFocusScopeAncestors(Current).Reverse().ToArray())
{
if (scope != Scope &&
_focusScopes.TryGetValue(scope, out var element) &&
element != null)
{
return Focus(element, method);
}
}
if (Scope is object)
{
// Couldn't find a focus scope, clear focus.
return SetFocusedElement(Scope, null);
}
return true;
}
else
{
_focusRoot = null;
keyboardDevice.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None);
return false;
}
return false;
}
public void ClearFocus()
@ -97,55 +88,23 @@ namespace Avalonia.Input
Focus(null);
}
public IInputElement? GetFocusedElement(IFocusScope scope)
{
_focusScopes.TryGetValue(scope, out var result);
return result;
}
/// <summary>
/// Sets the currently focused element in the specified scope.
/// </summary>
/// <param name="scope">The focus scope.</param>
/// <param name="element">The element to focus. May be null.</param>
/// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
/// <remarks>
/// If the specified scope is the current <see cref="Scope"/> then the keyboard focus
/// will change.
/// </remarks>
public bool SetFocusedElement(
IFocusScope scope,
IInputElement? element,
NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None)
public void ClearFocusOnElementRemoved(IInputElement removedElement, Visual oldParent)
{
scope = scope ?? throw new ArgumentNullException(nameof(scope));
if (element is not null && !CanFocus(element))
{
return false;
}
if (_focusScopes.TryGetValue(scope, out var existingElement))
{
if (element != existingElement)
{
_focusScopes.Remove(scope);
_focusScopes.Add(scope, element);
}
}
else
if (oldParent is IInputElement parentElement &&
GetFocusScope(parentElement) is StyledElement scope &&
scope.GetValue(FocusedElementProperty) is IInputElement focused &&
focused == removedElement)
{
_focusScopes.Add(scope, element);
scope.ClearValue(FocusedElementProperty);
}
if (Scope == scope)
{
KeyboardDevice.Instance?.SetFocusedElement(element, method, keyModifiers);
}
if (Current == removedElement)
Focus(null);
}
return true;
public IInputElement? GetFocusedElement(IFocusScope scope)
{
return (scope as StyledElement)?.GetValue(FocusedElementProperty);
}
/// <summary>
@ -154,35 +113,23 @@ namespace Avalonia.Input
/// <param name="scope">The new focus scope.</param>
public void SetFocusScope(IFocusScope scope)
{
scope = scope ?? throw new ArgumentNullException(nameof(scope));
if (!_focusScopes.TryGetValue(scope, out var e))
if (GetFocusedElement(scope) is { } focused)
{
Focus(focused);
}
else if (scope is IInputElement scopeElement && CanFocus(scopeElement))
{
// TODO: Make this do something useful, i.e. select the first focusable
// control, select a control that the user has specified to have default
// focus etc.
e = scope as IInputElement;
_focusScopes.Add(scope, e);
Focus(scopeElement);
}
Scope = scope;
Focus(e);
}
public void RemoveFocusScope(IFocusScope scope)
public void RemoveFocusRoot(IFocusScope scope)
{
scope = scope ?? throw new ArgumentNullException(nameof(scope));
if (_focusScopes.TryGetValue(scope, out _))
{
SetFocusedElement(scope, null);
_focusScopes.Remove(scope);
}
if (Scope == scope)
{
Scope = null;
}
if (scope == _focusRoot)
ClearFocus();
}
public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope;
@ -200,6 +147,17 @@ namespace Avalonia.Input
// In our unit tests some elements might not have a root. Remove when we migrate to headless tests.
?? (FocusManager?)AvaloniaLocator.Current.GetService<IFocusManager>();
}
internal bool TryMoveFocus(NavigationDirection direction)
{
if (GetFocusedElement() is {} focusedElement
&& KeyboardNavigationHandler.GetNext(focusedElement, direction) is {} newElement)
{
return newElement.Focus();
}
return false;
}
/// <summary>
/// Checks if the specified element can be focused.
@ -209,27 +167,45 @@ namespace Avalonia.Input
private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && IsVisible(e);
/// <summary>
/// Gets the focus scope ancestors of the specified control, traversing popups.
/// Gets the focus scope of the specified control, traversing popups.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The focus scopes.</returns>
private static IEnumerable<IFocusScope> GetFocusScopeAncestors(IInputElement control)
/// <returns>The focus scope.</returns>
private static StyledElement? GetFocusScope(IInputElement control)
{
IInputElement? c = control;
while (c != null)
{
if (c is IFocusScope scope &&
if (c is IFocusScope &&
c is Visual v &&
v.VisualRoot is Visual root &&
root.IsVisible)
{
yield return scope;
return v;
}
c = (c as Visual)?.GetVisualParent<IInputElement>() ??
((c as IHostedVisualTreeRoot)?.Host as IInputElement);
}
return null;
}
private static StyledElement? GetFocusRoot(StyledElement scope)
{
if (scope is not Visual v)
return null;
var root = v.VisualRoot as Visual;
while (root is IHostedVisualTreeRoot hosted &&
hosted.Host?.VisualRoot is Visual parentRoot)
{
root = parentRoot;
}
return root;
}
/// <summary>
@ -263,6 +239,11 @@ namespace Avalonia.Input
}
}
private static bool IsVisible(IInputElement e) => (e as Visual)?.IsEffectivelyVisible ?? true;
private static bool IsVisible(IInputElement e)
{
if (e is Visual v)
return v.IsAttachedToVisualTree && e.IsEffectivelyVisible;
return true;
}
}
}

9
src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs

@ -2,6 +2,7 @@
{
public abstract class GestureRecognizer : StyledElement
{
private PointerEventArgs? _currentPointerEventArgs;
protected internal IInputElement? Target { get; internal set; }
protected abstract void PointerPressed(PointerPressedEventArgs e);
@ -11,17 +12,23 @@
internal void PointerPressedInternal(PointerPressedEventArgs e)
{
_currentPointerEventArgs = e;
PointerPressed(e);
_currentPointerEventArgs = null;
}
internal void PointerReleasedInternal(PointerReleasedEventArgs e)
{
_currentPointerEventArgs = e;
PointerReleased(e);
_currentPointerEventArgs = null;
}
internal void PointerMovedInternal(PointerEventArgs e)
{
_currentPointerEventArgs = e;
PointerMoved(e);
_currentPointerEventArgs = null;
}
internal void PointerCaptureLostInternal(IPointer pointer)
@ -32,6 +39,8 @@
protected void Capture(IPointer pointer)
{
(pointer as Pointer)?.CaptureGestureRecognizer(this);
_currentPointerEventArgs?.PreventGestureRecognition();
}
}
}

39
src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs

@ -10,6 +10,7 @@ namespace Avalonia.Input
private IPointer? _secondContact;
private Point _secondPoint;
private Point _origin;
private double _previousAngle;
protected override void PointerCaptureLost(IPointer pointer)
{
@ -20,7 +21,7 @@ namespace Avalonia.Input
{
if (Target is Visual visual)
{
if(_firstContact == e.Pointer)
if (_firstContact == e.Pointer)
{
_firstPoint = e.GetPosition(visual);
}
@ -39,10 +40,13 @@ namespace Avalonia.Input
var scale = distance / _initialDistance;
var pinchEventArgs = new PinchEventArgs(scale, _origin);
Target?.RaiseEvent(pinchEventArgs);
var degree = GetAngleDegreeFromPoints(_firstPoint, _secondPoint);
var pinchEventArgs = new PinchEventArgs(scale, _origin, degree, _previousAngle - degree);
_previousAngle = degree;
Target?.RaiseEvent(pinchEventArgs);
e.Handled = pinchEventArgs.Handled;
e.PreventGestureRecognition();
}
}
}
@ -74,18 +78,24 @@ namespace Avalonia.Input
_origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f);
_previousAngle = GetAngleDegreeFromPoints(_firstPoint, _secondPoint);
Capture(_firstContact);
Capture(_secondContact);
e.PreventGestureRecognition();
}
}
}
protected override void PointerReleased(PointerReleasedEventArgs e)
{
RemoveContact(e.Pointer);
if(RemoveContact(e.Pointer))
{
e.PreventGestureRecognition();
}
}
private void RemoveContact(IPointer pointer)
private bool RemoveContact(IPointer pointer)
{
if (_firstContact == pointer || _secondContact == pointer)
{
@ -102,13 +112,28 @@ namespace Avalonia.Input
}
Target?.RaiseEvent(new PinchEndedEventArgs());
return true;
}
return false;
}
private float GetDistance(Point a, Point b)
private static float GetDistance(Point a, Point b)
{
var length = _secondPoint - _firstPoint;
var length = b - a;
return (float)new Vector(length.X, length.Y).Length;
}
private static double GetAngleDegreeFromPoints(Point a, Point b)
{
// https://stackoverflow.com/a/15994225/20894223
var deltaX = a.X - b.X;
var deltaY = -(a.Y - b.Y); // I reverse the sign, because on the screen the Y axes
// are reversed with respect to the Cartesian plane.
var rad = System.Math.Atan2(deltaX, deltaY); // radians from -π to +π
var degree = ((rad * (180 / System.Math.PI))) + 180; // Atan2 returns a radian value between -π to +π, in degrees -180 to +180.
// To get the angle between 0 and 360 degrees you need to add 180 degrees.
return degree;
}
}
}

1
src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs

@ -44,7 +44,6 @@ namespace Avalonia.Input
{
var currentPosition = e.GetPosition(visual);
Capture(e.Pointer);
e.PreventGestureRecognition();
Vector delta = default;
switch (PullDirection)

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

@ -124,8 +124,6 @@ namespace Avalonia.Input.GestureRecognizers
_trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance));
Capture(e.Pointer);
e.PreventGestureRecognition();
}
}

2
src/Avalonia.Base/Input/InputElement.cs

@ -511,7 +511,7 @@ namespace Avalonia.Input
if (IsFocused)
{
FocusManager.GetFocusManager(this)?.ClearFocus();
FocusManager.GetFocusManager(this)?.ClearFocusOnElementRemoved(this, e.Parent);
}
}

2
src/Avalonia.Base/Input/InputMethod.cs

@ -29,7 +29,7 @@ namespace Avalonia.Input
}
/// <summary>
/// Defines the <see cref="TextInputMethodClientRequeryRequested"/> event.
/// Defines the TextInputMethodClientRequeryRequested event.
/// </summary>
public static readonly RoutedEvent<TextInputMethodClientRequeryRequestedEventArgs> TextInputMethodClientRequeryRequestedEvent =
RoutedEvent.Register<InputElement, TextInputMethodClientRequeryRequestedEventArgs>(

4
src/Avalonia.Base/Input/Navigation/TabNavigation.cs

@ -649,12 +649,12 @@ namespace Avalonia.Input.Navigation
private static bool IsTabStop(IInputElement e)
{
if (e is InputElement ie)
return ie.Focusable && KeyboardNavigation.GetIsTabStop(ie) && ie.IsVisible && ie.IsEnabled;
return ie.Focusable && KeyboardNavigation.GetIsTabStop(ie) && ie.IsVisible && ie.IsEffectivelyEnabled;
return false;
}
private static bool IsTabStopOrGroup(IInputElement e) => IsTabStop(e) || IsGroup(e);
private static bool IsVisible(IInputElement e) => (e as Visual)?.IsVisible ?? true;
private static bool IsVisibleAndEnabled(IInputElement e) => IsVisible(e) && e.IsEnabled;
private static bool IsVisibleAndEnabled(IInputElement e) => IsVisible(e) && e.IsEffectivelyEnabled;
}
}

28
src/Avalonia.Base/Input/PinchEventArgs.cs

@ -4,20 +4,44 @@ namespace Avalonia.Input
{
public class PinchEventArgs : RoutedEventArgs
{
public PinchEventArgs(double scale, Point scaleOrigin) : base(Gestures.PinchEvent)
public PinchEventArgs(double scale, Point scaleOrigin) : base(Gestures.PinchEvent)
{
Scale = scale;
ScaleOrigin = scaleOrigin;
}
public PinchEventArgs(double scale, Point scaleOrigin, double angle, double angleDelta) : base(Gestures.PinchEvent)
{
Scale = scale;
ScaleOrigin = scaleOrigin;
Angle = angle;
AngleDelta = angleDelta;
}
public double Scale { get; } = 1;
public Point ScaleOrigin { get; }
/// <summary>
/// Gets the angle of the pinch gesture, in degrees.
/// </summary>
/// <remarks>
/// A pinch gesture is the movement of two pressed points closer together. This property is the measured angle of the line between those two points. Remember zero degrees is a line pointing up.
/// </remarks>
public double Angle { get; }
/// <summary>
/// Gets the difference from the previous and current pinch angle.
/// </summary>
/// <remarks>
/// The AngleDelta value includes the sign of rotation. Positive for clockwise, negative counterclockwise.
/// </remarks>
public double AngleDelta { get; }
}
public class PinchEndedEventArgs : RoutedEventArgs
{
public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent)
public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent)
{
}
}

5
src/Avalonia.Base/Input/Pointer.cs

@ -88,7 +88,10 @@ namespace Avalonia.Input
public void Dispose()
{
Capture(null);
if (Captured != null)
{
Capture(null);
}
}
/// <summary>

5
src/Avalonia.Base/Logging/LogArea.cs

@ -74,5 +74,10 @@ namespace Avalonia.Logging
/// The log event comes from macOS Platform
/// </summary>
public const string macOSPlatform = nameof(macOSPlatform);
/// <summary>
/// The log event comes from Browser Platform
/// </summary>
public static string BrowserPlatform => nameof(BrowserPlatform);
}
}

12
src/Avalonia.Base/Media/Imaging/Bitmap.cs

@ -284,6 +284,16 @@ namespace Avalonia.Media.Imaging
return AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
}
IRef<IBitmapImpl> IImageBrushSource.Bitmap => PlatformImpl;
IRef<IBitmapImpl>? IImageBrushSource.Bitmap
{
get
{
// TODO12: We should probably make PlatformImpl to be nullable or make it possible to check
// and fix IRef<T> in general (right now Item is not nullable while it internally is)
if (PlatformImpl.Item == null!)
return null;
return PlatformImpl;
}
}
}
}

7
src/Avalonia.Base/Media/MediaContext.Compositor.cs

@ -1,4 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Transport;
@ -20,7 +22,8 @@ partial class MediaContext
_requestedCommits.Remove(compositor);
_pendingCompositionBatches[compositor] = commit;
commit.Processed.ContinueWith(_ =>
_dispatcher.Post(() => CompositionBatchFinished(compositor, commit), DispatcherPriority.Send));
_dispatcher.Post(() => CompositionBatchFinished(compositor, commit), DispatcherPriority.Send),
TaskContinuationOptions.ExecuteSynchronously);
return commit;
}
@ -93,7 +96,7 @@ partial class MediaContext
// Unit tests are assuming that they can call any API without setting up platforms
if (AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() == null)
return;
if (compositor is
{
UseUiThreadForSynchronousCommits: false,

2
src/Avalonia.Base/Media/PolyLineSegment.cs

@ -39,6 +39,8 @@ namespace Avalonia.Media
/// Initializes a new instance of the <see cref="PolyLineSegment"/> class.
/// </summary>
/// <param name="points">The points.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
Justification = "Collection properties shouldn't be set with SetCurrentValue.")]
public PolyLineSegment(IEnumerable<Point> points)
{
Points = new Points(points);

11
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -1299,8 +1299,6 @@ namespace Avalonia.Media.TextFormatting
}
}
var overhangAfter = Math.Max(0, bounds.Bottom - height);
var width = widthIncludingWhitespace;
for (var i = _textRuns.Length - 1; i >= 0; i--)
@ -1327,11 +1325,12 @@ namespace Avalonia.Media.TextFormatting
}
}
var start = GetParagraphOffsetX(width, widthIncludingWhitespace);
//The width of overhanging pixels at the bottom
var overhangAfter = Math.Max(0, bounds.Bottom - height);
//The width of overhanging pixels at the origin
var overhangLeading = Math.Max(0, start - bounds.Left);
var overhangLeading = Math.Abs(Math.Min(bounds.Left, 0));
//The width of overhanging pixels at the end
var overhangTrailing = Math.Max(0, bounds.Width - widthIncludingWhitespace);
var overhangTrailing = Math.Max(0, bounds.Right - widthIncludingWhitespace);
var hasOverflowed = width > _paragraphWidth;
if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight))
@ -1339,6 +1338,8 @@ namespace Avalonia.Media.TextFormatting
height = lineHeight;
}
var start = GetParagraphOffsetX(width, widthIncludingWhitespace);
return new TextLineMetrics
{
HasOverflowed = hasOverflowed,

60
src/Avalonia.Base/Media/TransformGroup.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.Collections;
using Avalonia.Metadata;
@ -11,24 +12,20 @@ namespace Avalonia.Media
public static readonly StyledProperty<Transforms> ChildrenProperty =
AvaloniaProperty.Register<TransformGroup, Transforms>(nameof(Children));
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
private IDisposable? _childrenNotificationSubscription;
private readonly EventHandler _childTransformChangedHandler;
private Matrix? _lastMatrix;
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
Justification = "Collection properties shouldn't be set with SetCurrentValue.")]
public TransformGroup()
{
Children = new Transforms();
Children.ResetBehavior = ResetBehavior.Remove;
Children.CollectionChanged += delegate
_childTransformChangedHandler = (_, _) =>
{
Children.ForEachItem(
(tr) => tr.Changed += ChildTransform_Changed,
(tr) => tr.Changed -= ChildTransform_Changed,
() => { });
_lastMatrix = null;
RaiseChanged();
};
}
private void ChildTransform_Changed(object? sender, System.EventArgs e)
{
this.RaiseChanged();
Children = new Transforms();
}
/// <summary>
@ -51,14 +48,41 @@ namespace Avalonia.Media
{
get
{
Matrix result = Matrix.Identity;
foreach (var t in Children)
if (_lastMatrix is null)
{
result *= t.Value;
var matrix = Matrix.Identity;
foreach (var t in Children)
{
matrix *= t.Value;
}
_lastMatrix = matrix;
}
return _lastMatrix.Value;
}
}
return result;
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ChildrenProperty)
{
_childrenNotificationSubscription?.Dispose();
if (change.OldValue is Transforms oldTransforms)
{
foreach (var item in oldTransforms)
{
item.Changed -= _childTransformChangedHandler;
}
}
if (change.NewValue is Transforms newTransforms)
{
// Ensure reset behavior is Remove
newTransforms.ResetBehavior = ResetBehavior.Remove;
_childrenNotificationSubscription = newTransforms.ForEachItem(
(tr) => tr.Changed += _childTransformChangedHandler,
(tr) => tr.Changed -= _childTransformChangedHandler,
() => { });
}
}
}
}

4
src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs

@ -1,4 +1,6 @@
using System;
#nullable enable
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Text;

9
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs

@ -94,21 +94,22 @@ internal class BclStorageFile : IStorageBookmarkFile
GC.SuppressFinalize(this);
}
public async Task DeleteAsync()
public Task DeleteAsync()
{
FileInfo.Delete();
return Task.CompletedTask;
}
public async Task<IStorageItem?> MoveAsync(IStorageFolder destination)
public Task<IStorageItem?> MoveAsync(IStorageFolder destination)
{
if (destination is BclStorageFolder storageFolder)
{
var newPath = System.IO.Path.Combine(storageFolder.DirectoryInfo.FullName, FileInfo.Name);
FileInfo.MoveTo(newPath);
return new BclStorageFile(new FileInfo(newPath));
return Task.FromResult<IStorageItem?>(new BclStorageFile(new FileInfo(newPath)));
}
return null;
return Task.FromResult<IStorageItem?>(null);
}
}

32
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs

@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Security;
using System.Threading.Tasks;
using Avalonia.Utilities;
namespace Avalonia.Platform.Storage.FileIO;
@ -57,17 +58,11 @@ internal class BclStorageFolder : IStorageBookmarkFolder
return Task.FromResult<IStorageFolder?>(null);
}
public async IAsyncEnumerable<IStorageItem> GetItemsAsync()
{
var items = DirectoryInfo.EnumerateDirectories()
public IAsyncEnumerable<IStorageItem> GetItemsAsync()
=> DirectoryInfo.EnumerateDirectories()
.Select(d => (IStorageItem)new BclStorageFolder(d))
.Concat(DirectoryInfo.EnumerateFiles().Select(f => new BclStorageFile(f)));
foreach (var item in items)
{
yield return item;
}
}
.Concat(DirectoryInfo.EnumerateFiles().Select(f => new BclStorageFile(f)))
.AsAsyncEnumerable();
public virtual Task<string?> SaveBookmarkAsync()
{
@ -95,38 +90,39 @@ internal class BclStorageFolder : IStorageBookmarkFolder
GC.SuppressFinalize(this);
}
public async Task DeleteAsync()
public Task DeleteAsync()
{
DirectoryInfo.Delete(true);
return Task.CompletedTask;
}
public async Task<IStorageItem?> MoveAsync(IStorageFolder destination)
public Task<IStorageItem?> MoveAsync(IStorageFolder destination)
{
if (destination is BclStorageFolder storageFolder)
{
var newPath = System.IO.Path.Combine(storageFolder.DirectoryInfo.FullName, DirectoryInfo.Name);
DirectoryInfo.MoveTo(newPath);
return new BclStorageFolder(new DirectoryInfo(newPath));
return Task.FromResult<IStorageItem?>(new BclStorageFolder(new DirectoryInfo(newPath)));
}
return null;
return Task.FromResult<IStorageItem?>(null);
}
public async Task<IStorageFile?> CreateFileAsync(string name)
public Task<IStorageFile?> CreateFileAsync(string name)
{
var fileName = System.IO.Path.Combine(DirectoryInfo.FullName, name);
var newFile = new FileInfo(fileName);
using var stream = newFile.Create();
return new BclStorageFile(newFile);
return Task.FromResult<IStorageFile?>(new BclStorageFile(newFile));
}
public async Task<IStorageFolder?> CreateFolderAsync(string name)
public Task<IStorageFolder?> CreateFolderAsync(string name)
{
var newFolder = DirectoryInfo.CreateSubdirectory(name);
return new BclStorageFolder(newFolder);
return Task.FromResult<IStorageFolder?>(new BclStorageFolder(newFolder));
}
}

2
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -183,7 +183,7 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
{
_queuedSceneInvalidation = false;
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
}, DispatcherPriority.Input));
}, DispatcherPriority.Input), TaskContinuationOptions.ExecuteSynchronously);
}
}

3
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@ -108,7 +108,8 @@ namespace Avalonia.Rendering.Composition
var pending = _pendingBatch;
if (pending != null)
pending.Processed.ContinueWith(
_ => Dispatcher.Post(_triggerCommitRequested, DispatcherPriority.Send));
_ => Dispatcher.Post(_triggerCommitRequested, DispatcherPriority.Send),
TaskContinuationOptions.ExecuteSynchronously);
else
_triggerCommitRequested();
}

2
src/Avalonia.Base/Rendering/RenderLoop.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Avalonia.Logging;
using Avalonia.Threading;
@ -20,7 +19,6 @@ namespace Avalonia.Rendering
private readonly List<IRenderLoopTask> _itemsCopy = new List<IRenderLoopTask>();
private IRenderTimer? _timer;
private int _inTick;
private readonly int _inUpdate;
public static IRenderLoop LocatorAutoInstance
{

6
src/Avalonia.Base/Threading/Dispatcher.Invoke.cs

@ -120,6 +120,7 @@ public partial class Dispatcher
/// Executes the specified Func&lt;TResult&gt; synchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <typeparam name="TResult">The type of the <paramref name="callback"/> return value.</typeparam>
/// <param name="callback">
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
@ -138,6 +139,7 @@ public partial class Dispatcher
/// Executes the specified Func&lt;TResult&gt; synchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <typeparam name="TResult">The type of the <paramref name="callback"/> return value.</typeparam>
/// <param name="callback">
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
@ -158,6 +160,7 @@ public partial class Dispatcher
/// Executes the specified Func&lt;TResult&gt; synchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <typeparam name="TResult">The type of the <paramref name="callback"/> return value.</typeparam>
/// <param name="callback">
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
@ -185,6 +188,7 @@ public partial class Dispatcher
/// Executes the specified Func&lt;TResult&gt; synchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <typeparam name="TResult">The type of the <paramref name="callback"/> return value.</typeparam>
/// <param name="callback">
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
@ -319,6 +323,7 @@ public partial class Dispatcher
/// Executes the specified Func&lt;TResult&gt; asynchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <typeparam name="TResult">The type of the <paramref name="callback"/> return value.</typeparam>
/// <param name="callback">
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
@ -357,6 +362,7 @@ public partial class Dispatcher
/// Executes the specified Func&lt;TResult&gt; asynchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <typeparam name="TResult">The type of the <paramref name="callback"/> return value.</typeparam>
/// <param name="callback">
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>

18
src/Avalonia.Base/Threading/Dispatcher.Queue.cs

@ -71,16 +71,28 @@ public partial class Dispatcher
}
}
class DummyShuttingDownUnitTestDispatcherImpl : IDispatcherImpl
private sealed class DummyShuttingDownUnitTestDispatcherImpl : IDispatcherImpl
{
public bool CurrentThreadIsLoopThread => true;
public void Signal()
{
}
public event Action? Signaled;
public event Action? Timer;
public event Action? Signaled
{
add { }
remove { }
}
public event Action? Timer
{
add { }
remove { }
}
public long Now => 0;
public void UpdateTimer(long? dueTimeInMs)
{
}

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

Loading…
Cancel
Save