Browse Source

Merge branch 'master' into SplitView-StyledProperty

pull/10138/head
robloo 3 years ago
committed by GitHub
parent
commit
448fcb4bdf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      .editorconfig
  2. 5
      .ncrunch/Avalonia.UnitTests.v3.ncrunchproject
  3. 5
      .ncrunch/GpuInterop.v3.ncrunchproject
  4. 5
      Avalonia.Desktop.slnf
  5. 27
      Avalonia.sln
  6. 5
      build/DevAnalyzers.props
  7. 6
      build/HarfBuzzSharp.props
  8. 2
      build/ImageSharp.props
  9. 2
      build/Moq.props
  10. 1
      build/SharedVersion.props
  11. 6
      build/SkiaSharp.props
  12. 15
      build/XUnit.props
  13. 4
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  14. 17
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  15. 39
      native/Avalonia.Native/src/OSX/PlatformBehaviorInhibition.mm
  16. 2
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  17. 5
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  18. 29
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  19. 6
      native/Avalonia.Native/src/OSX/WindowImpl.h
  20. 103
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  21. 1
      native/Avalonia.Native/src/OSX/common.h
  22. 11
      native/Avalonia.Native/src/OSX/main.mm
  23. 2
      readme.md
  24. 7
      samples/BindingDemo/App.xaml
  25. 11
      samples/ControlCatalog.Browser.Blazor/App.razor.cs
  26. 4
      samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj
  27. 12
      samples/ControlCatalog.Browser.Blazor/Program.cs
  28. 20
      samples/ControlCatalog.Browser/Program.cs
  29. 3
      samples/ControlCatalog.Browser/main.js
  30. 1
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  31. 24
      samples/ControlCatalog/App.xaml
  32. 48
      samples/ControlCatalog/App.xaml.cs
  33. 1
      samples/ControlCatalog/ControlCatalog.csproj
  34. 23
      samples/ControlCatalog/MainView.xaml
  35. 39
      samples/ControlCatalog/MainView.xaml.cs
  36. 6
      samples/ControlCatalog/Models/CatalogTheme.cs
  37. 10
      samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
  38. 30
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml
  39. 51
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  40. 22
      samples/ControlCatalog/Pages/FlyoutsPage.axaml
  41. 1
      samples/ControlCatalog/Pages/GesturePage.cs
  42. 2
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  43. 16
      samples/ControlCatalog/Pages/SplitViewPage.xaml
  44. 2
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  45. 79
      samples/ControlCatalog/Pages/ThemePage.axaml
  46. 37
      samples/ControlCatalog/Pages/ThemePage.axaml.cs
  47. 1
      samples/Directory.Build.props
  48. 5
      samples/GpuInterop/MainWindow.axaml.cs
  49. 2
      samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs
  50. 6
      samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs
  51. 7
      samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs
  52. 50
      samples/GpuInterop/VulkanDemo/VulkanContent.cs
  53. 14
      samples/GpuInterop/VulkanDemo/VulkanContext.cs
  54. 12
      samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs
  55. 50
      samples/GpuInterop/VulkanDemo/VulkanImage.cs
  56. 8
      samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs
  57. 5
      samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs
  58. 2
      samples/IntegrationTestApp/App.axaml
  59. 59
      samples/IntegrationTestApp/MainWindow.axaml
  60. 93
      samples/IntegrationTestApp/MainWindow.axaml.cs
  61. 1
      samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj
  62. 5
      samples/MobileSandbox/App.xaml
  63. 4
      samples/MobileSandbox/MainView.xaml
  64. 2
      samples/PlatformSanityChecks/App.xaml
  65. 2
      samples/Previewer/App.xaml
  66. 14
      samples/RenderDemo/MainWindow.xaml
  67. 23
      samples/RenderDemo/MainWindow.xaml.cs
  68. 45
      samples/RenderDemo/ViewModels/MainWindowViewModel.cs
  69. 12
      samples/SampleControls/HamburgerMenu/HamburgerMenu.cs
  70. 34
      samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
  71. 2
      samples/Sandbox/App.axaml
  72. 11
      samples/interop/WindowsInteropTest/Program.cs
  73. 7
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  74. 42
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  75. 1
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  76. 127
      src/Android/Avalonia.Android/InputEditable.cs
  77. 2
      src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
  78. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  79. 141
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  80. 6
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
  81. 6
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
  82. 4
      src/Android/Avalonia.Android/PlatformIconLoader.cs
  83. 6
      src/Android/Avalonia.Android/Stubs.cs
  84. 39
      src/Avalonia.Base/Animation/Animatable.cs
  85. 11
      src/Avalonia.Base/Animation/KeySpline.cs
  86. 108
      src/Avalonia.Base/AvaloniaObject.cs
  87. 50
      src/Avalonia.Base/AvaloniaProperty.cs
  88. 18
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  89. 112
      src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs
  90. 13
      src/Avalonia.Base/Collections/IAvaloniaDictionary.cs
  91. 14
      src/Avalonia.Base/Collections/IAvaloniaReadOnlyDictionary.cs
  92. 6
      src/Avalonia.Base/Controls/IResourceDictionary.cs
  93. 7
      src/Avalonia.Base/Controls/IResourceNode.cs
  94. 91
      src/Avalonia.Base/Controls/ResourceDictionary.cs
  95. 125
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  96. 2
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  97. 4
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  98. 4
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  99. 2
      src/Avalonia.Base/Data/InstancedBinding.cs
  100. 3
      src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs

25
.editorconfig

@ -55,16 +55,17 @@ dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
# private static fields should have s_ prefix
dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.private_static_fields_should_have_prefix.symbols = private_static_fields
dotnet_naming_rule.private_static_fields_should_have_prefix.style = private_static_prefix_style
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
dotnet_naming_symbols.private_static_fields.required_modifiers = static
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
dotnet_naming_style.static_prefix_style.required_prefix = s_
dotnet_naming_style.static_prefix_style.capitalization = camel_case
dotnet_naming_style.private_static_prefix_style.required_prefix = s_
dotnet_naming_style.private_static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
@ -117,7 +118,7 @@ csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
@ -145,10 +146,14 @@ dotnet_diagnostic.CS1591.severity = suggestion
# CS0162: Remove unreachable code
dotnet_diagnostic.CS0162.severity = error
# CA1018: Mark attributes with AttributeUsageAttribute
dotnet_diagnostic.CA1018.severity = error
# CA1304: Specify CultureInfo
dotnet_diagnostic.CA1304.severity = warning
# CA1802: Use literals where appropriate
dotnet_diagnostic.CA1802.severity = warning
# CA1813: Avoid unsealed attributes
dotnet_diagnostic.CA1813.severity = error
# CA1815: Override equals and operator equals on value types
dotnet_diagnostic.CA1815.severity = warning
# CA1820: Test for empty strings using string length
@ -207,5 +212,5 @@ indent_size = 2
# Shell scripts
[*.sh]
end_of_line = lf
[*.{cmd, bat}]
[*.{cmd,bat}]
end_of_line = crlf

5
.ncrunch/Avalonia.UnitTests.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<XUnit2Enabled>False</XUnit2Enabled>
</Settings>
</ProjectConfiguration>

5
.ncrunch/GpuInterop.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
Avalonia.Desktop.slnf

@ -8,13 +8,14 @@
"samples\\GpuInterop\\GpuInterop.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"src\\Avalonia.Base\\Avalonia.Base.csproj",
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
"src\\Avalonia.Controls.DataGrid\\Avalonia.Controls.DataGrid.csproj",
"src\\Avalonia.Controls.ItemsRepeater\\Avalonia.Controls.ItemsRepeater.csproj",
"src\\Avalonia.Controls\\Avalonia.Controls.csproj",
"src\\Avalonia.DesignerSupport\\Avalonia.DesignerSupport.csproj",
"src\\Avalonia.Desktop\\Avalonia.Desktop.csproj",
@ -41,9 +42,11 @@
"src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj",
"src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
"src\\tools\\DevGenerators\\DevGenerators.csproj",
"src\\tools\\PublicAnalyzers\\Avalonia.Analyzers.csproj",
"tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj",
"tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj",
"tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj",
"tests\\Avalonia.Controls.ItemsRepeater.UnitTests\\Avalonia.Controls.ItemsRepeater.UnitTests.csproj",
"tests\\Avalonia.Controls.UnitTests\\Avalonia.Controls.UnitTests.csproj",
"tests\\Avalonia.DesignerSupport.TestApp\\Avalonia.DesignerSupport.TestApp.csproj",
"tests\\Avalonia.DesignerSupport.Tests\\Avalonia.DesignerSupport.Tests.csproj",

27
Avalonia.sln

@ -231,7 +231,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blaz
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Analyzers", "src\tools\PublicAnalyzers\Avalonia.Analyzers.csproj", "{C692FE73-43DB-49CE-87FC-F03ED61F25C9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{176582E8-46AF-416A-85C1-13A5C6744497}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater", "src\Avalonia.Controls.ItemsRepeater\Avalonia.Controls.ItemsRepeater.csproj", "{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater.UnitTests", "tests\Avalonia.Controls.ItemsRepeater.UnitTests\Avalonia.Controls.ItemsRepeater.UnitTests.csproj", "{F4E36AA8-814E-4704-BC07-291F70F45193}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -548,6 +559,18 @@ Global
{C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.Build.0 = Release|Any CPU
{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Release|Any CPU.Build.0 = Release|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.Build.0 = Release|Any CPU
{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.Build.0 = Release|Any CPU
{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -613,6 +636,8 @@ Global
{90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

5
build/DevAnalyzers.props

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

6
build/HarfBuzzSharp.props

@ -1,7 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="HarfBuzzSharp" Version="2.8.2.1-preview.108" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2.1-preview.108" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2.1-preview.108" />
<PackageReference Include="HarfBuzzSharp" Version="2.8.2.3" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2.3" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2.3" />
</ItemGroup>
</Project>

2
build/ImageSharp.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
</ItemGroup>
</Project>

2
build/Moq.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Moq" Version="4.18.4" />
</ItemGroup>
</Project>

1
build/SharedVersion.props

@ -3,6 +3,7 @@
<PropertyGroup>
<Product>Avalonia</Product>
<Version>11.0.999</Version>
<Authors>Avalonia Team</Authors>
<Copyright>Copyright 2022 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>

6
build/SkiaSharp.props

@ -1,7 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.88.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.1" />
<PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.3" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.3" />
</ItemGroup>
</Project>

15
build/XUnit.props

@ -1,13 +1,12 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.assert" Version="2.4.1" />
<PackageReference Include="xunit.core" Version="2.4.1" />
<PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" />
<PackageReference Include="xunit.runner.console" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.assert" Version="2.4.2" />
<PackageReference Include="xunit.core" Version="2.4.2" />
<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.SkippableFact" Version="1.4.13" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
</ItemGroup>

4
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -43,6 +43,7 @@
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */ = {isa = PBXBuildFile; fileRef = 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; };
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; };
@ -95,6 +96,7 @@
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformBehaviorInhibition.mm; sourceTree = "<group>"; };
AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
@ -140,6 +142,7 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */,
BC11A5BC2608D58F0017BAD0 /* automation.h */,
BC11A5BD2608D58F0017BAD0 /* automation.mm */,
1A1852DB23E05814008F0DED /* deadlock.mm */,
@ -288,6 +291,7 @@
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */,

17
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -223,6 +223,19 @@
}
}
// From chromium:
//
// > The delegate or the window class should implement this method so that
// > -[NSWindow isZoomed] can be then determined by whether or not the current
// > window frame is equal to the zoomed frame.
//
// If we don't implement this, then isZoomed always returns true for a non-
// resizable window ¯\_(ツ)_/¯
- (NSRect)windowWillUseStandardFrame:(NSWindow*)window
defaultFrame:(NSRect)newFrame {
return newFrame;
}
-(BOOL)canBecomeKeyWindow
{
if(_canBecomeKeyWindow)
@ -261,10 +274,6 @@
-(void) setEnabled:(bool)enable
{
_isEnabled = enable;
[[self standardWindowButton:NSWindowCloseButton] setEnabled:enable];
[[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable];
[[self standardWindowButton:NSWindowZoomButton] setEnabled:enable];
}
-(void)becomeKeyWindow

39
native/Avalonia.Native/src/OSX/PlatformBehaviorInhibition.mm

@ -0,0 +1,39 @@
#include "common.h"
namespace
{
id<NSObject> s_inhibitAppSleepHandle{};
}
class PlatformBehaviorInhibition : public ComSingleObject<IAvnPlatformBehaviorInhibition, &IID_IAvnCursorFactory>
{
public:
FORWARD_IUNKNOWN()
virtual void SetInhibitAppSleep(bool inhibitAppSleep, char* reason) override
{
START_COM_CALL;
@autoreleasepool
{
if (inhibitAppSleep && s_inhibitAppSleepHandle == nullptr)
{
NSActivityOptions options = NSActivityUserInitiatedAllowingIdleSystemSleep;
s_inhibitAppSleepHandle = [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:[NSString stringWithUTF8String: reason]];
}
if (!inhibitAppSleep)
{
s_inhibitAppSleepHandle = nullptr;
}
}
}
};
extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition()
{
@autoreleasepool
{
return new PlatformBehaviorInhibition();
}
}

2
native/Avalonia.Native/src/OSX/PopupImpl.mm

@ -29,7 +29,7 @@ private:
[Window setLevel:NSPopUpMenuWindowLevel];
}
protected:
virtual NSWindowStyleMask GetStyle() override
virtual NSWindowStyleMask CalculateStyleMask() override
{
return NSWindowStyleMaskBorderless;
}

5
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@ -105,9 +105,8 @@ BEGIN_INTERFACE_MAP()
virtual void BringToFront ();
protected:
virtual NSWindowStyleMask GetStyle();
void UpdateStyle();
virtual NSWindowStyleMask CalculateStyleMask() = 0;
virtual void UpdateStyle();
private:
void CreateNSWindow (bool isDialog);

29
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -35,18 +35,14 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl,
lastSize = NSSize { 100, 100 };
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 };
lastMenu = nullptr;
CreateNSWindow(usePanel);
[Window setContentView:StandardContainer];
[Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
[Window setOpaque:false];
}
@ -564,12 +560,8 @@ bool WindowBaseImpl::IsModal() {
return false;
}
NSWindowStyleMask WindowBaseImpl::GetStyle() {
return NSWindowStyleMaskBorderless;
}
void WindowBaseImpl::UpdateStyle() {
[Window setStyleMask:GetStyle()];
[Window setStyleMask:CalculateStyleMask()];
}
void WindowBaseImpl::CleanNSWindow() {
@ -580,21 +572,12 @@ void WindowBaseImpl::CleanNSWindow() {
}
}
void WindowBaseImpl::CreateNSWindow(bool isDialog) {
if (isDialog) {
if (![Window isKindOfClass:[AvnPanel class]]) {
CleanNSWindow();
Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
[Window setHidesOnDeactivate:false];
}
void WindowBaseImpl::CreateNSWindow(bool usePanel) {
if (usePanel) {
Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless];
[Window setHidesOnDeactivate:false];
} else {
if (![Window isKindOfClass:[AvnWindow class]]) {
CleanNSWindow();
Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
}
Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless];
}
}

6
native/Avalonia.Native/src/OSX/WindowImpl.h

@ -41,8 +41,6 @@ BEGIN_INTERFACE_MAP()
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl);
void HideOrShowTrafficLights ();
virtual HRESULT Show (bool activate, bool isDialog) override;
virtual HRESULT SetEnabled (bool enable) override;
@ -100,9 +98,11 @@ BEGIN_INTERFACE_MAP()
bool CanBecomeKeyWindow ();
protected:
virtual NSWindowStyleMask GetStyle() override;
virtual NSWindowStyleMask CalculateStyleMask() override;
void UpdateStyle () override;
private:
void ZOrderChildWindows();
void OnInitialiseNSWindow();
NSString *_lastTitle;
};

103
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -30,19 +30,6 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
OnInitialiseNSWindow();
}
void WindowImpl::HideOrShowTrafficLights() {
if (Window == nil) {
return;
}
bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
[[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights];
[[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights];
[[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights];
}
void WindowImpl::OnInitialiseNSWindow(){
[GetWindowProtocol() setCanBecomeKeyWindow:true];
@ -66,9 +53,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
_isModal = isDialog;
WindowBaseImpl::Show(activate, isDialog);
HideOrShowTrafficLights();
GetWindowState(&_actualWindowState);
return SetWindowState(_lastWindowState);
}
}
@ -134,14 +119,19 @@ void WindowImpl::BringToFront()
}
[Window invalidateShadow];
ZOrderChildWindows();
}
}
void WindowImpl::ZOrderChildWindows()
{
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
auto window = (*iterator)->Window;
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
auto window = (*iterator)->Window;
// #9565: Only bring window to front if it's on the currently active space
if ([window isOnActiveSpace])
(*iterator)->BringToFront();
// #9565: Only bring window to front if it's on the currently active space
if ([window isOnActiveSpace]) {
(*iterator)->BringToFront();
}
}
}
@ -161,13 +151,15 @@ bool WindowImpl::CanBecomeKeyWindow()
void WindowImpl::StartStateTransition() {
_transitioningWindowState = true;
UpdateStyle();
}
void WindowImpl::EndStateTransition() {
_transitioningWindowState = false;
UpdateStyle();
// Ensure correct order of child windows after fullscreen transition.
BringToFront();
ZOrderChildWindows();
}
SystemDecorations WindowImpl::Decorations() {
@ -225,16 +217,12 @@ bool WindowImpl::IsZoomed() {
}
void WindowImpl::DoZoom() {
switch (_decorations) {
case SystemDecorationsNone:
case SystemDecorationsBorderOnly:
[Window setFrame:[Window screen].visibleFrame display:true];
break;
case SystemDecorationsFull:
[Window performZoom:Window];
break;
if (_decorations == SystemDecorationsNone ||
_decorations == SystemDecorationsBorderOnly ||
_canResize == false) {
[Window setFrame:[Window screen].visibleFrame display:true];
} else {
[Window performZoom:Window];
}
}
@ -261,8 +249,6 @@ HRESULT WindowImpl::SetDecorations(SystemDecorations value) {
UpdateStyle();
HideOrShowTrafficLights();
switch (_decorations) {
case SystemDecorationsNone:
[Window setHasShadow:NO];
@ -419,9 +405,6 @@ HRESULT WindowImpl::SetExtendClientArea(bool enable) {
}
[GetWindowProtocol() setIsExtended:enable];
HideOrShowTrafficLights();
UpdateStyle();
}
@ -577,14 +560,16 @@ bool WindowImpl::IsOwned() {
return _parent != nullptr;
}
NSWindowStyleMask WindowImpl::GetStyle() {
unsigned long s = NSWindowStyleMaskBorderless;
NSWindowStyleMask WindowImpl::CalculateStyleMask() {
// Use the current style mask and only clear the flags we're going to be modifying.
unsigned long s = [Window styleMask] &
~(NSWindowStyleMaskFullSizeContentView |
NSWindowStyleMaskTitled |
NSWindowStyleMaskClosable |
NSWindowStyleMaskResizable |
NSWindowStyleMaskMiniaturizable |
NSWindowStyleMaskTexturedBackground);
if(_actualWindowState == FullScreen)
{
s |= NSWindowStyleMaskFullScreen;
}
switch (_decorations) {
case SystemDecorationsNone:
s = s | NSWindowStyleMaskFullSizeContentView;
@ -597,7 +582,7 @@ NSWindowStyleMask WindowImpl::GetStyle() {
case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
if (_canResize && _isEnabled) {
if ((_canResize && _isEnabled) || _transitioningWindowState) {
s = s | NSWindowStyleMaskResizable;
}
break;
@ -612,3 +597,25 @@ NSWindowStyleMask WindowImpl::GetStyle() {
}
return s;
}
void WindowImpl::UpdateStyle() {
WindowBaseImpl::UpdateStyle();
if (Window == nil) {
return;
}
bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
NSButton* closeButton = [Window standardWindowButton:NSWindowCloseButton];
NSButton* miniaturizeButton = [Window standardWindowButton:NSWindowMiniaturizeButton];
NSButton* zoomButton = [Window standardWindowButton:NSWindowZoomButton];
[closeButton setHidden:!hasTrafficLights];
[closeButton setEnabled:_isEnabled];
[miniaturizeButton setHidden:!hasTrafficLights];
[miniaturizeButton setEnabled:_isEnabled];
[zoomButton setHidden:!hasTrafficLights];
[zoomButton setEnabled:_isEnabled && _canResize];
}

1
native/Avalonia.Native/src/OSX/common.h

@ -26,6 +26,7 @@ extern IAvnTrayIcon* CreateTrayIcon();
extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnApplicationCommands* CreateApplicationCommands();
extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern IAvnPlatformSettings* CreatePlatformSettings();
extern void SetAppMenu(IAvnMenu *menu);

11
native/Avalonia.Native/src/OSX/main.mm

@ -408,6 +408,17 @@ public:
return S_OK;
}
}
virtual HRESULT CreatePlatformBehaviorInhibition(IAvnPlatformBehaviorInhibition** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
*ppv = ::CreatePlatformBehaviorInhibition();
return S_OK;
}
}
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

2
readme.md

@ -1,3 +1,5 @@
[![GH_Banner](https://user-images.githubusercontent.com/552074/218457976-92e76834-9e22-4e35-acfa-aa50281bc0f9.png)](https://avaloniaui.net/xpf)
[![Telegram](https://raw.githubusercontent.com/Patrolavia/telegram-badge/master/chat.svg)](https://t.me/Avalonia)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![Discord](https://img.shields.io/badge/discord-join%20chat-46BC99)]( https://aka.ms/dotnet-discord) [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) ![License](https://img.shields.io/github/license/avaloniaui/avalonia.svg)
<br />

7
samples/BindingDemo/App.xaml

@ -2,13 +2,6 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BindingDemo.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<FluentTheme />
</Application.Styles>

11
samples/ControlCatalog.Browser.Blazor/App.razor.cs

@ -1,3 +1,5 @@
using System;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Browser.Blazor;
@ -5,13 +7,4 @@ namespace ControlCatalog.Browser.Blazor;
public partial class App
{
protected override void OnParametersSet()
{
AppBuilder.Configure<ControlCatalog.App>()
.UseBlazor()
// .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
.SetupWithSingleViewLifetime();
base.OnParametersSet();
}
}

4
samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj

@ -9,8 +9,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.0-rc.1.22427.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.0-rc.1.22427.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.2" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>

12
samples/ControlCatalog.Browser.Blazor/Program.cs

@ -1,6 +1,8 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Browser.Blazor;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using ControlCatalog.Browser.Blazor;
@ -9,9 +11,17 @@ public class Program
{
public static async Task Main(string[] args)
{
await CreateHostBuilder(args).Build().RunAsync();
var host = CreateHostBuilder(args).Build();
await StartAvaloniaApp();
await host.RunAsync();
}
public static async Task StartAvaloniaApp()
{
await AppBuilder.Configure<ControlCatalog.App>()
.StartBlazorAppAsync();
}
public static WebAssemblyHostBuilder CreateHostBuilder(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);

20
samples/ControlCatalog.Browser/Program.cs

@ -1,6 +1,8 @@
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Browser;
using Avalonia.Controls;
using ControlCatalog;
using ControlCatalog.Browser;
@ -8,15 +10,27 @@ using ControlCatalog.Browser;
internal partial class Program
{
private static void Main(string[] args)
public static async Task Main(string[] args)
{
BuildAvaloniaApp()
await BuildAvaloniaApp()
.AfterSetup(_ =>
{
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
}).SetupBrowserApp("out");
})
.StartBrowserAppAsync("out");
}
// Example without a ISingleViewApplicationLifetime
// private static AvaloniaView _avaloniaView;
// public static async Task Main(string[] args)
// {
// await BuildAvaloniaApp()
// .SetupBrowserApp();
//
// _avaloniaView = new AvaloniaView("out");
// _avaloniaView.Content = new TextBlock { Text = "Hello world" };
// }
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>();
}

3
samples/ControlCatalog.Browser/main.js

@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
import { dotnet } from './dotnet.js'
import { registerAvaloniaModule } from './avalonia.js';
const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
@ -12,8 +11,6 @@ const dotnetRuntime = await dotnet
.withApplicationArgumentsFromQuery()
.create();
await registerAvaloniaModule(dotnetRuntime);
const config = dotnetRuntime.getConfig();
await dotnetRuntime.runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]);

1
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -31,7 +31,6 @@
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<!-- For native controls test -->
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
</ItemGroup>

24
samples/ControlCatalog/App.xaml

@ -6,18 +6,34 @@
x:Class="ControlCatalog.App">
<Application.Resources>
<ResourceDictionary>
<!-- Custom controls defined in other assemblies -->
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Resources used only in the control catalog -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<Color x:Key="CatalogBaseLowColor">#33000000</Color>
<Color x:Key="CatalogBaseMediumColor">#99000000</Color>
<Color x:Key="CatalogChromeMediumColor">#FFE6E6E6</Color>
<Color x:Key="CatalogBaseHighColor">#FF000000</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<Color x:Key="CatalogBaseLowColor">#33FFFFFF</Color>
<Color x:Key="CatalogBaseMediumColor">#99FFFFFF</Color>
<Color x:Key="CatalogChromeMediumColor">#FF1F1F1F</Color>
<Color x:Key="CatalogBaseHighColor">#FFFFFFFF</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<Color x:Key="SystemAccentColor">#FF0078D7</Color>
<Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
<!-- Styles attached dynamically depending on current theme (simple or fluent) -->
<StyleInclude x:Key="DataGridFluent" Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
<StyleInclude x:Key="DataGridSimple" Source="avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml" />
<StyleInclude x:Key="ColorPickerFluent" Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml" />
<StyleInclude x:Key="ColorPickerSimple" Source="avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml" />
<ResourceInclude x:Key="FluentAccentColors" Source="avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml" />
<ResourceInclude x:Key="FluentBaseLightColors" Source="avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml" />
<ResourceInclude x:Key="FluentBaseDarkColors" Source="avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml" />
<ResourceInclude x:Key="FluentBaseColors" Source="avares://Avalonia.Themes.Fluent/Accents/Base.xaml" />
</ResourceDictionary>
</Application.Resources>
<Application.Styles>

48
samples/ControlCatalog/App.xaml.cs

@ -16,7 +16,6 @@ namespace ControlCatalog
private readonly Styles _themeStylesContainer = new();
private FluentTheme? _fluentTheme;
private SimpleTheme? _simpleTheme;
private IResourceDictionary? _fluentBaseLightColors, _fluentBaseDarkColors;
private IStyle? _colorPickerFluent, _colorPickerSimple;
private IStyle? _dataGridFluent, _dataGridSimple;
@ -33,16 +32,12 @@ namespace ControlCatalog
_fluentTheme = new FluentTheme();
_simpleTheme = new SimpleTheme();
_simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentAccentColors"]!);
_simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentBaseColors"]!);
_colorPickerFluent = (IStyle)Resources["ColorPickerFluent"]!;
_colorPickerSimple = (IStyle)Resources["ColorPickerSimple"]!;
_dataGridFluent = (IStyle)Resources["DataGridFluent"]!;
_dataGridSimple = (IStyle)Resources["DataGridSimple"]!;
_fluentBaseLightColors = (IResourceDictionary)Resources["FluentBaseLightColors"]!;
_fluentBaseDarkColors = (IResourceDictionary)Resources["FluentBaseDarkColors"]!;
SetThemeVariant(CatalogTheme.FluentLight);
SetCatalogThemes(CatalogTheme.Fluent);
}
public override void OnFrameworkInitializationCompleted()
@ -61,19 +56,12 @@ namespace ControlCatalog
private CatalogTheme _prevTheme;
public static CatalogTheme CurrentTheme => ((App)Current!)._prevTheme;
public static void SetThemeVariant(CatalogTheme theme)
public static void SetCatalogThemes(CatalogTheme theme)
{
var app = (App)Current!;
var prevTheme = app._prevTheme;
app._prevTheme = theme;
var shouldReopenWindow = theme switch
{
CatalogTheme.FluentLight => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
CatalogTheme.FluentDark => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
CatalogTheme.SimpleLight => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
CatalogTheme.SimpleDark => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
_ => throw new ArgumentOutOfRangeException(nameof(theme), theme, null)
};
var shouldReopenWindow = prevTheme != theme;
if (app._themeStylesContainer.Count == 0)
{
@ -81,36 +69,16 @@ namespace ControlCatalog
app._themeStylesContainer.Add(new Style());
app._themeStylesContainer.Add(new Style());
}
if (theme == CatalogTheme.FluentLight)
{
app._fluentTheme!.Mode = FluentThemeMode.Light;
app._themeStylesContainer[0] = app._fluentTheme;
app._themeStylesContainer[1] = app._colorPickerFluent!;
app._themeStylesContainer[2] = app._dataGridFluent!;
}
else if (theme == CatalogTheme.FluentDark)
if (theme == CatalogTheme.Fluent)
{
app._fluentTheme!.Mode = FluentThemeMode.Dark;
app._themeStylesContainer[0] = app._fluentTheme;
app._themeStylesContainer[0] = app._fluentTheme!;
app._themeStylesContainer[1] = app._colorPickerFluent!;
app._themeStylesContainer[2] = app._dataGridFluent!;
}
else if (theme == CatalogTheme.SimpleLight)
{
app._simpleTheme!.Mode = SimpleThemeMode.Light;
app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseDarkColors!);
app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseLightColors!);
app._themeStylesContainer[0] = app._simpleTheme;
app._themeStylesContainer[1] = app._colorPickerSimple!;
app._themeStylesContainer[2] = app._dataGridSimple!;
}
else if (theme == CatalogTheme.SimpleDark)
else if (theme == CatalogTheme.Simple)
{
app._simpleTheme!.Mode = SimpleThemeMode.Dark;
app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseLightColors!);
app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseDarkColors!);
app._themeStylesContainer[0] = app._simpleTheme;
app._themeStylesContainer[0] = app._simpleTheme!;
app._themeStylesContainer[1] = app._colorPickerSimple!;
app._themeStylesContainer[2] = app._dataGridSimple!;
}

1
samples/ControlCatalog/ControlCatalog.csproj

@ -26,6 +26,7 @@
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ItemsRepeater\Avalonia.Controls.ItemsRepeater.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />

23
samples/ControlCatalog/MainView.xaml

@ -14,8 +14,8 @@
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
</Grid.Styles>
<controls:HamburgerMenu Name="Sidebar">
<TabItem Header="Composition">
<controls:HamburgerMenu Name="Sidebar">
<TabItem Header="Composition">
<pages:CompositionPage/>
</TabItem>
<TabItem Header="Acrylic">
@ -168,6 +168,9 @@
<TabItem Header="TextBlock">
<pages:TextBlockPage />
</TabItem>
<TabItem Header="Theme Variants">
<pages:ThemePage />
</TabItem>
<TabItem Header="ToggleSwitch">
<pages:ToggleSwitchPage />
</TabItem>
@ -201,14 +204,22 @@
<SystemDecorations>Full</SystemDecorations>
</ComboBox.Items>
</ComboBox>
<ComboBox x:Name="ThemeVariants"
HorizontalAlignment="Stretch"
DisplayMemberBinding="{Binding Key, x:DataType=ThemeVariant}"
SelectedIndex="0">
<ComboBox.Items>
<ThemeVariant>Default</ThemeVariant>
<ThemeVariant>Light</ThemeVariant>
<ThemeVariant>Dark</ThemeVariant>
</ComboBox.Items>
</ComboBox>
<ComboBox x:Name="Themes"
HorizontalAlignment="Stretch"
SelectedIndex="0">
<ComboBox.Items>
<models:CatalogTheme>FluentLight</models:CatalogTheme>
<models:CatalogTheme>FluentDark</models:CatalogTheme>
<models:CatalogTheme>SimpleLight</models:CatalogTheme>
<models:CatalogTheme>SimpleDark</models:CatalogTheme>
<models:CatalogTheme>Fluent</models:CatalogTheme>
<models:CatalogTheme>Simple</models:CatalogTheme>
</ComboBox.Items>
</ComboBox>
<ComboBox x:Name="TransparencyLevels"

39
samples/ControlCatalog/MainView.xaml.cs

@ -9,6 +9,7 @@ using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.VisualTree;
using Avalonia.Styling;
using ControlCatalog.Models;
using ControlCatalog.Pages;
@ -42,16 +43,16 @@ namespace ControlCatalog
{
if (themes.SelectedItem is CatalogTheme theme)
{
App.SetThemeVariant(theme);
((TopLevel?)this.GetVisualRoot())?.PlatformImpl?.SetFrameThemeVariant(theme switch
{
CatalogTheme.FluentLight => PlatformThemeVariant.Light,
CatalogTheme.FluentDark => PlatformThemeVariant.Dark,
CatalogTheme.SimpleLight => PlatformThemeVariant.Light,
CatalogTheme.SimpleDark => PlatformThemeVariant.Dark,
_ => throw new ArgumentOutOfRangeException()
});
App.SetCatalogThemes(theme);
}
};
var themeVariants = this.Get<ComboBox>("ThemeVariants");
themeVariants.SelectedItem = Application.Current!.RequestedThemeVariant;
themeVariants.SelectionChanged += (sender, e) =>
{
if (themeVariants.SelectedItem is ThemeVariant themeVariant)
{
Application.Current!.RequestedThemeVariant = themeVariant;
}
};
@ -118,25 +119,13 @@ namespace ControlCatalog
private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e)
{
var themes = this.Get<ComboBox>("Themes");
var currentTheme = (CatalogTheme?)themes.SelectedItem ?? CatalogTheme.FluentLight;
var newTheme = (currentTheme, e.ThemeVariant) switch
{
(CatalogTheme.FluentDark, PlatformThemeVariant.Light) => CatalogTheme.FluentLight,
(CatalogTheme.FluentLight, PlatformThemeVariant.Dark) => CatalogTheme.FluentDark,
(CatalogTheme.SimpleDark, PlatformThemeVariant.Light) => CatalogTheme.SimpleLight,
(CatalogTheme.SimpleLight, PlatformThemeVariant.Dark) => CatalogTheme.SimpleDark,
_ => currentTheme
};
themes.SelectedItem = newTheme;
Application.Current!.Resources["SystemAccentColor"] = e.AccentColor1;
Application.Current.Resources["SystemAccentColorDark1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
Application.Current.Resources["SystemAccentColorDark2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
Application.Current.Resources["SystemAccentColorDark3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, 0.3);
Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, 0.5);
Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, 0.7);
static Color ChangeColorLuminosity(Color color, double luminosityFactor)
{

6
samples/ControlCatalog/Models/CatalogTheme.cs

@ -2,9 +2,7 @@
{
public enum CatalogTheme
{
FluentLight,
FluentDark,
SimpleLight,
SimpleDark
Fluent,
Simple
}
}

10
samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs

@ -59,10 +59,12 @@ namespace ControlCatalog.Pages
};
StreamGeometry sg = new StreamGeometry();
var cntx = sg.Open();
cntx.BeginFigure(new Point(-25.0d, -10.0d), false);
cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise);
cntx.EndFigure(true);
using (var cntx = sg.Open())
{
cntx.BeginFigure(new Point(-25.0d, -10.0d), false);
cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise);
cntx.EndFigure(true);
}
_smileGeometry = sg.Clone();
}

30
samples/ControlCatalog/Pages/DateTimePickerPage.xaml

@ -15,11 +15,11 @@
Spacing="16">
<TextBlock FontSize="18">A simple DatePicker</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<DatePicker />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
@ -31,7 +31,7 @@
</StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<DatePicker >
<DataValidationErrors.Error>
@ -42,12 +42,12 @@
<TextBlock FontSize="18">A DatePicker with day formatted and year hidden.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<DatePicker x:Name="Control2" DayFormat="d (ddd)"
YearVisible="False" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
@ -58,15 +58,15 @@
</Panel>
</StackPanel>
<Border Background="{DynamicResource SystemControlHighlightBaseLowBrush}" BorderThickness="1" Margin="15" />
<Border Background="{DynamicResource CatalogBaseLowColor}" BorderThickness="1" Margin="15" />
<TextBlock FontSize="18">A simple TimePicker.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<TimePicker />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
@ -77,7 +77,7 @@
</Panel>
</StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<TimePicker>
<DataValidationErrors.Error>
@ -88,11 +88,11 @@
<TextBlock FontSize="18">A TimePicker with minute increments specified.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<TimePicker MinuteIncrement="15" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
@ -105,11 +105,11 @@
<TextBlock FontSize="18">A TimePicker using a 12-hour clock.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="12HourClock"/>
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
@ -122,11 +122,11 @@
<TextBlock FontSize="18">A TimePicker using a 24-hour clock.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="24HourClock" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>

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

@ -40,7 +40,7 @@ namespace ControlCatalog.Pages
if (Enum.TryParse<WellKnownFolder>(currentFolderBox.Text, true, out var folderEnum))
{
lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolder(folderEnum);
lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolderAsync(folderEnum);
}
else
{
@ -51,7 +51,7 @@ namespace ControlCatalog.Pages
if (folderLink is not null)
{
lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPath(folderLink);
lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPathAsync(folderLink);
}
}
};
@ -82,7 +82,13 @@ namespace ControlCatalog.Pages
return new List<FilePickerFileType>
{
FilePickerFileTypes.All,
FilePickerFileTypes.TextPlain
FilePickerFileTypes.TextPlain,
new("Binary Log")
{
Patterns = new[] { "*.binlog", "*.buildlog" },
MimeTypes = new[] { "application/binlog", "application/buildlog" },
AppleUniformTypeIdentifiers = new []{ "public.data" }
}
};
}
@ -142,7 +148,7 @@ namespace ControlCatalog.Pages
}
else
{
SetFolder(await GetStorageProvider().TryGetFolderFromPath(result));
SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result));
results.Items = new[] { result };
resultsVisible.IsVisible = true;
}
@ -223,7 +229,7 @@ namespace ControlCatalog.Pages
ShowOverwritePrompt = false
});
if (file is not null && file.CanOpenWrite)
if (file is not null)
{
// Sync disposal of StreamWriter is not supported on WASM
#if NET6_0_OR_GREATER
@ -275,7 +281,7 @@ namespace ControlCatalog.Pages
{
ignoreTextChanged = true;
lastSelectedDirectory = folder;
currentFolderBox.Text = folder?.Path.LocalPath;
currentFolderBox.Text = folder?.Path is { IsAbsoluteUri: true } abs ? abs.LocalPath : folder?.Path?.ToString();
ignoreTextChanged = false;
}
async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items)
@ -298,31 +304,26 @@ namespace ControlCatalog.Pages
if (item is IStorageFile file)
{
resultText += @$"
CanOpenRead: {file.CanOpenRead}
CanOpenWrite: {file.CanOpenWrite}
Content:
";
if (file.CanOpenRead)
{
#if NET6_0_OR_GREATER
await using var stream = await file.OpenReadAsync();
await using var stream = await file.OpenReadAsync();
#else
using var stream = await file.OpenReadAsync();
using var stream = await file.OpenReadAsync();
#endif
using var reader = new System.IO.StreamReader(stream);
using var reader = new System.IO.StreamReader(stream);
// 4GB file test, shouldn't load more than 10000 chars into a memory.
const int length = 10000;
var buffer = ArrayPool<char>.Shared.Rent(length);
try
{
var charsRead = await reader.ReadAsync(buffer, 0, length);
resultText += new string(buffer, 0, charsRead);
}
finally
{
ArrayPool<char>.Shared.Return(buffer);
}
// 4GB file test, shouldn't load more than 10000 chars into a memory.
const int length = 10000;
var buffer = ArrayPool<char>.Shared.Rent(length);
try
{
var charsRead = await reader.ReadAsync(buffer, 0, length);
resultText += new string(buffer, 0, charsRead);
}
finally
{
ArrayPool<char>.Shared.Return(buffer);
}
}

22
samples/ControlCatalog/Pages/FlyoutsPage.axaml

@ -26,31 +26,31 @@
<StackPanel Spacing="10">
<TextBlock FontSize="18" Text="Button with a Flyout" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<Button Content="Click Me!" Flyout="{StaticResource BasicFlyout}" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Name="ButtonFlyoutXamlText" Padding="15" />
</Panel>
</StackPanel>
<TextBlock FontSize="18" Text="MenuFlyout" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<Button Content="Click Me!" Flyout="{StaticResource SharedMenuFlyout}" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Name="MenuFlyoutXamlText" Padding="15" />
</Panel>
</StackPanel>
<TextBlock FontSize="18" Text="Attached Flyouts" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"
<Panel Background="{DynamicResource CatalogBaseLowColor}"
HorizontalAlignment="Left"
Height="100"
Name="AttachedFlyoutPanel">
@ -70,7 +70,7 @@
</Panel>
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Name="AttachedFlyoutXamlText" Padding="15" />
</Panel>
</StackPanel>
@ -78,21 +78,21 @@
<TextBlock FontSize="18" Text="Sharing Flyouts" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<StackPanel Orientation="Horizontal" Spacing="30">
<Button Content="Launch Flyout on this button" Flyout="{StaticResource SharedMenuFlyout}"/>
<Button Content="Launch Flyout on this button" Flyout="{StaticResource SharedMenuFlyout}"/>
</StackPanel>
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Name="SharedFlyoutXamlText" Padding="15" />
</Panel>
</StackPanel>
<TextBlock FontSize="18" Text="Flyout Placements" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<UniformGrid Columns="3">
<UniformGrid.Styles>
@ -215,7 +215,7 @@
<TextBlock FontSize="18" Text="Flyout ShowMode" />
<StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<WrapPanel Orientation="Horizontal">
<WrapPanel.Styles>

1
samples/ControlCatalog/Pages/GesturePage.cs

@ -70,7 +70,6 @@ namespace ControlCatalog.Pages
_currentScale = 1;
Vector3 currentOffset = default;
bool isZooming = false;
CompositionVisual? compositionVisual = null;

2
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@ -62,7 +62,7 @@
<Button x:Name="scrollToRandom">Scroll to Random</Button>
<Button x:Name="scrollToSelected">Scroll to Selected</Button>
</StackPanel>
<Border BorderThickness="1" BorderBrush="{DynamicResource SystemControlHighlightBaseMediumLowBrush}" Margin="0 0 0 16">
<Border BorderThickness="1" BorderBrush="{DynamicResource CatalogBaseMediumColor}" Margin="0 0 0 16">
<ScrollViewer Name="scroller"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">

16
samples/ControlCatalog/Pages/SplitViewPage.xaml

@ -32,7 +32,7 @@
<TextBlock Text="PaneBackground" />
<ComboBox Name="PaneBackgroundSelector" SelectedIndex="0" Width="170" Margin="10">
<ComboBoxItem Tag="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}">SystemControlBackgroundChromeMediumLowBrush</ComboBoxItem>
<ComboBoxItem Tag="{DynamicResource CatalogChromeMediumColor}">CatalogChromeMediumColor</ComboBoxItem>
<ComboBoxItem Tag="Red">Red</ComboBoxItem>
<ComboBoxItem Tag="Blue">Blue</ComboBoxItem>
<ComboBoxItem Tag="Green">Green</ComboBoxItem>
@ -48,7 +48,7 @@
</StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1">
<!--{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}-->
<SplitView Name="SplitView"
@ -77,7 +77,7 @@
<Border Width="48">
<Viewbox Width="24" Height="24" HorizontalAlignment="Left">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}" Data="M16 17V19H2V17S2 13 9 13 16 17 16 17M12.5 7.5A3.5 3.5 0 1 0 9 11A3.5 3.5 0 0 0 12.5 7.5M15.94 13A5.32 5.32 0 0 1 18 17V19H22V17S22 13.37 15.94 13M15 4A3.39 3.39 0 0 0 13.07 4.59A5 5 0 0 1 13.07 10.41A3.39 3.39 0 0 0 15 11A3.5 3.5 0 0 0 15 4Z" />
<Path Fill="{DynamicResource CatalogBaseHighColor}" Data="M16 17V19H2V17S2 13 9 13 16 17 16 17M12.5 7.5A3.5 3.5 0 1 0 9 11A3.5 3.5 0 0 0 12.5 7.5M15.94 13A5.32 5.32 0 0 1 18 17V19H22V17S22 13.37 15.94 13M15 4A3.39 3.39 0 0 0 13.07 4.59A5 5 0 0 1 13.07 10.41A3.39 3.39 0 0 0 15 11A3.5 3.5 0 0 0 15 4Z" />
</Canvas>
</Viewbox>
</Border>
@ -89,11 +89,11 @@
</SplitView.Pane>
<Grid>
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource CatalogBaseHighColor}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" TextAlignment="Left" Foreground="{DynamicResource CatalogBaseHighColor}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource CatalogBaseHighColor}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" TextAlignment="Left" Foreground="{DynamicResource CatalogBaseHighColor}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource CatalogBaseHighColor}" />
</Grid>
</SplitView>

2
samples/ControlCatalog/Pages/TextBlockPage.xaml

@ -9,7 +9,7 @@
<WrapPanel.Styles>
<Style Selector="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightBaseMediumLowBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource CatalogBaseMediumColor}" />
<Setter Property="Padding" Value="2" />
<Setter Property="Margin" Value="10" />
<Setter Property="Width" Value="200" />

79
samples/ControlCatalog/Pages/ThemePage.axaml

@ -0,0 +1,79 @@
<UserControl x:Class="ControlCatalog.Pages.ThemePage"
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:pages="clr-namespace:ControlCatalog.Pages"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="DemoBackground">Black</SolidColorBrush>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="DemoBackground">White</SolidColorBrush>
</ResourceDictionary>
<ResourceDictionary x:Key="{x:Static pages:ThemePage.Pink}">
<SolidColorBrush x:Key="DemoBackground">#ffe5ea</SolidColorBrush>
<SolidColorBrush x:Key="NormalBackgroundBrush" Color="#ffc0cb" />
<SolidColorBrush x:Key="PointerOverBackgroundBrush" Color="#ffb3c0" />
<SolidColorBrush x:Key="PressedBackgroundBrush" Color="#ff4d6c" />
<SolidColorBrush x:Key="NormalBorderBrush" Color="#ff8096" />
<SolidColorBrush x:Key="PointerOverBorderBrush" Color="#ff8096" />
<SolidColorBrush x:Key="PressedBorderBrush" Color="#ff4d6c" />
<!-- Override colors for fluent theme -->
<StaticResource x:Key="ButtonBackground" ResourceKey="NormalBackgroundBrush" />
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="PointerOverBackgroundBrush" />
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="PressedBackgroundBrush" />
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="NormalBorderBrush" />
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="PointerOverBorderBrush" />
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="PressedBorderBrush" />
<StaticResource x:Key="TextControlBackground" ResourceKey="NormalBackgroundBrush" />
<StaticResource x:Key="TextControlBackgroundPointerOver" ResourceKey="PointerOverBackgroundBrush" />
<StaticResource x:Key="TextControlBackgroundFocused" ResourceKey="PointerOverBackgroundBrush" />
<StaticResource x:Key="TextControlBorderBrush" ResourceKey="NormalBorderBrush" />
<StaticResource x:Key="TextControlBorderBrushPointerOver" ResourceKey="PointerOverBorderBrush" />
<StaticResource x:Key="TextControlBorderBrushFocused" ResourceKey="PressedBorderBrush" />
<StaticResource x:Key="ComboBoxBackground" ResourceKey="NormalBackgroundBrush" />
<StaticResource x:Key="ComboBoxBackgroundPointerOver" ResourceKey="PointerOverBackgroundBrush" />
<StaticResource x:Key="ComboBoxBackgroundPressed" ResourceKey="PressedBackgroundBrush" />
<StaticResource x:Key="ComboBoxBorderBrush" ResourceKey="NormalBorderBrush" />
<StaticResource x:Key="ComboBoxBorderBrushPointerOver" ResourceKey="PointerOverBorderBrush" />
<StaticResource x:Key="ComboBoxBorderBrushPressed" ResourceKey="PressedBorderBrush" />
<!-- Override colors for default theme -->
<StaticResource x:Key="ThemeControlMidBrush" ResourceKey="NormalBackgroundBrush" />
<StaticResource x:Key="ThemeControlHighBrush" ResourceKey="PressedBackgroundBrush" />
<StaticResource x:Key="ThemeBorderLowBrush" ResourceKey="NormalBorderBrush" />
<StaticResource x:Key="ThemeBorderMidBrush" ResourceKey="PointerOverBorderBrush" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<ThemeVariantScope x:Name="ThemeVariantScope">
<Border Background="{DynamicResource DemoBackground}"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Padding="4"
CornerRadius="4">
<Grid RowDefinitions="Auto, 4, Auto, 4, Auto, 4, Auto" ColumnDefinitions="150, 150">
<ComboBox Grid.Column="0" Grid.Row="0" x:Name="Selector" HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{Binding TargetNullValue=Unset}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Username:" VerticalAlignment="Center" />
<TextBlock Grid.Column="0" Grid.Row="4" Text="Password:" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="2" Watermark="Input here" HorizontalAlignment="Stretch" />
<TextBox Grid.Column="1" Grid.Row="4" Watermark="Input here" HorizontalAlignment="Stretch" />
<Button Grid.Column="1" Grid.Row="6" Content="Login" HorizontalAlignment="Stretch" />
</Grid>
</Border>
</ThemeVariantScope>
</UserControl>

37
samples/ControlCatalog/Pages/ThemePage.axaml.cs

@ -0,0 +1,37 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
namespace ControlCatalog.Pages
{
public class ThemePage : UserControl
{
public static ThemeVariant Pink { get; } = new("Pink", ThemeVariant.Light);
public ThemePage()
{
AvaloniaXamlLoader.Load(this);
var selector = this.FindControl<ComboBox>("Selector")!;
var themeVariantScope = this.FindControl<ThemeVariantScope>("ThemeVariantScope")!;
selector.Items = new[]
{
ThemeVariant.Default,
ThemeVariant.Dark,
ThemeVariant.Light,
Pink
};
selector.SelectedIndex = 0;
selector.SelectionChanged += (_, _) =>
{
if (selector.SelectedItem is ThemeVariant theme)
{
themeVariantScope.RequestedThemeVariant = theme;
}
};
}
}
}

1
samples/Directory.Build.props

@ -6,4 +6,5 @@
<LangVersion>11</LangVersion>
</PropertyGroup>
<Import Project="..\build\SharedVersion.props" />
<Import Project="..\build\DevAnalyzers.props" />
</Project>

5
samples/GpuInterop/MainWindow.axaml.cs

@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Rendering;
namespace GpuInterop
{
@ -8,9 +9,9 @@ namespace GpuInterop
{
public MainWindow()
{
this.InitializeComponent();
InitializeComponent();
this.AttachDevTools();
this.Renderer.DrawFps = true;
Renderer.Diagnostics.DebugOverlays = RendererDebugOverlays.Fps;
}
private void InitializeComponent()

2
samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs

@ -47,7 +47,7 @@ public class D3DMemoryHelper
MipLevels = 1,
SampleDescription = new SampleDescription { Count = 1, Quality = 0 },
CpuAccessFlags = default,
OptionFlags = ResourceOptionFlags.SharedKeyedmutex,
OptionFlags = ResourceOptionFlags.SharedKeyedmutex|ResourceOptionFlags.SharedNthandle,
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource
});
}

6
samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs

@ -38,8 +38,8 @@ static class VulkanBufferHelper
MemoryTypeIndex = (uint)FindSuitableMemoryTypeIndex(api,
physicalDevice,
memoryRequirements.MemoryTypeBits,
MemoryPropertyFlags.MemoryPropertyHostCoherentBit |
MemoryPropertyFlags.MemoryPropertyHostVisibleBit)
MemoryPropertyFlags.HostCoherentBit |
MemoryPropertyFlags.HostVisibleBit)
};
api.AllocateMemory(device, memoryAllocateInfo, null, out memory).ThrowOnError();
@ -77,4 +77,4 @@ static class VulkanBufferHelper
return -1;
}
}
}

7
samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia.Input;
using Silk.NET.Vulkan;
using SilkNetDemo;
@ -25,7 +24,7 @@ namespace Avalonia.Vulkan
var commandPoolCreateInfo = new CommandPoolCreateInfo
{
SType = StructureType.CommandPoolCreateInfo,
Flags = CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit,
Flags = CommandPoolCreateFlags.ResetCommandBufferBit,
QueueFamilyIndex = queueFamilyIndex
};
@ -109,7 +108,7 @@ namespace Avalonia.Vulkan
var fenceCreateInfo = new FenceCreateInfo()
{
SType = StructureType.FenceCreateInfo,
Flags = FenceCreateFlags.FenceCreateSignaledBit
Flags = FenceCreateFlags.SignaledBit
};
api.CreateFence(device, fenceCreateInfo, null, out _fence);
@ -134,7 +133,7 @@ namespace Avalonia.Vulkan
var beginInfo = new CommandBufferBeginInfo
{
SType = StructureType.CommandBufferBeginInfo,
Flags = CommandBufferUsageFlags.CommandBufferUsageOneTimeSubmitBit
Flags = CommandBufferUsageFlags.OneTimeSubmitBit
};
_api.BeginCommandBuffer(InternalHandle, beginInfo);

50
samples/GpuInterop/VulkanDemo/VulkanContent.cs

@ -208,7 +208,7 @@ unsafe class VulkanContent : IDisposable
api.CmdBindDescriptorSets(commandBufferHandle, PipelineBindPoint.Graphics,
_pipelineLayout,0,1, &dset, null);
api.CmdPushConstants(commandBufferHandle, _pipelineLayout, ShaderStageFlags.ShaderStageVertexBit | ShaderStageFlags.FragmentBit, 0,
api.CmdPushConstants(commandBufferHandle, _pipelineLayout, ShaderStageFlags.VertexBit | ShaderStageFlags.FragmentBit, 0,
(uint)Marshal.SizeOf<VertextPushConstant>(), &vertexConstant);
api.CmdBindVertexBuffers(commandBufferHandle, 0, 1, _vertexBuffer, 0);
api.CmdBindIndexBuffer(commandBufferHandle, _indexBuffer, 0, IndexType.Uint16);
@ -237,14 +237,14 @@ unsafe class VulkanContent : IDisposable
SrcSubresource =
new ImageSubresourceLayers
{
AspectMask = ImageAspectFlags.ImageAspectColorBit,
AspectMask = ImageAspectFlags.ColorBit,
BaseArrayLayer = 0,
LayerCount = 1,
MipLevel = 0
},
DstSubresource = new ImageSubresourceLayers
{
AspectMask = ImageAspectFlags.ImageAspectColorBit,
AspectMask = ImageAspectFlags.ColorBit,
BaseArrayLayer = 0,
LayerCount = 1,
MipLevel = 0
@ -326,19 +326,19 @@ unsafe class VulkanContent : IDisposable
var imageCreateInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
ImageType = ImageType.ImageType2D,
ImageType = ImageType.Type2D,
Format = Format.D32Sfloat,
Extent =
new Extent3D((uint?)size.Width,
(uint?)size.Height, 1),
MipLevels = 1,
ArrayLayers = 1,
Samples = SampleCountFlags.SampleCount1Bit,
Samples = SampleCountFlags.Count1Bit,
Tiling = ImageTiling.Optimal,
Usage = ImageUsageFlags.ImageUsageDepthStencilAttachmentBit,
Usage = ImageUsageFlags.DepthStencilAttachmentBit,
SharingMode = SharingMode.Exclusive,
InitialLayout = ImageLayout.Undefined,
Flags = ImageCreateFlags.ImageCreateMutableFormatBit
Flags = ImageCreateFlags.CreateMutableFormatBit
};
var api = _context.Api;
@ -355,7 +355,7 @@ unsafe class VulkanContent : IDisposable
AllocationSize = memoryRequirements.Size,
MemoryTypeIndex = (uint)FindSuitableMemoryTypeIndex(api,
_context.PhysicalDevice,
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit)
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)
};
api.AllocateMemory(device, memoryAllocateInfo, null,
@ -369,14 +369,14 @@ unsafe class VulkanContent : IDisposable
ComponentSwizzle.B,
ComponentSwizzle.A);
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectDepthBit,
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.DepthBit,
0, 1, 0, 1);
var imageViewCreateInfo = new ImageViewCreateInfo
{
SType = StructureType.ImageViewCreateInfo,
Image = _depthImage,
ViewType = ImageViewType.ImageViewType2D,
ViewType = ImageViewType.Type2D,
Format = Format.D32Sfloat,
Components = componentMapping,
SubresourceRange = subresourceRange
@ -406,7 +406,7 @@ unsafe class VulkanContent : IDisposable
var colorAttachment = new AttachmentDescription()
{
Format = Format.R8G8B8A8Unorm,
Samples = SampleCountFlags.SampleCount1Bit,
Samples = SampleCountFlags.Count1Bit,
LoadOp = AttachmentLoadOp.Clear,
StoreOp = AttachmentStoreOp.Store,
InitialLayout = ImageLayout.Undefined,
@ -418,7 +418,7 @@ unsafe class VulkanContent : IDisposable
var depthAttachment = new AttachmentDescription()
{
Format = Format.D32Sfloat,
Samples = SampleCountFlags.SampleCount1Bit,
Samples = SampleCountFlags.Count1Bit,
LoadOp = AttachmentLoadOp.Clear,
StoreOp = AttachmentStoreOp.DontCare,
InitialLayout = ImageLayout.Undefined,
@ -431,10 +431,10 @@ unsafe class VulkanContent : IDisposable
{
SrcSubpass = Vk.SubpassExternal,
DstSubpass = 0,
SrcStageMask = PipelineStageFlags.PipelineStageColorAttachmentOutputBit,
SrcStageMask = PipelineStageFlags.ColorAttachmentOutputBit,
SrcAccessMask = 0,
DstStageMask = PipelineStageFlags.PipelineStageColorAttachmentOutputBit,
DstAccessMask = AccessFlags.AccessColorAttachmentWriteBit
DstStageMask = PipelineStageFlags.ColorAttachmentOutputBit,
DstAccessMask = AccessFlags.ColorAttachmentWriteBit
};
var colorAttachmentReference = new AttachmentReference()
@ -498,14 +498,14 @@ unsafe class VulkanContent : IDisposable
var vertShaderStageInfo = new PipelineShaderStageCreateInfo()
{
SType = StructureType.PipelineShaderStageCreateInfo,
Stage = ShaderStageFlags.ShaderStageVertexBit,
Stage = ShaderStageFlags.VertexBit,
Module = _vertShader,
PName = (byte*)pname,
};
var fragShaderStageInfo = new PipelineShaderStageCreateInfo()
{
SType = StructureType.PipelineShaderStageCreateInfo,
Stage = ShaderStageFlags.ShaderStageFragmentBit,
Stage = ShaderStageFlags.FragmentBit,
Module = _fragShader,
PName = (byte*)pname,
};
@ -564,7 +564,7 @@ unsafe class VulkanContent : IDisposable
RasterizerDiscardEnable = false,
PolygonMode = PolygonMode.Fill,
LineWidth = 1,
CullMode = CullModeFlags.CullModeNone,
CullMode = CullModeFlags.None,
DepthBiasEnable = false
};
@ -572,7 +572,7 @@ unsafe class VulkanContent : IDisposable
{
SType = StructureType.PipelineMultisampleStateCreateInfo,
SampleShadingEnable = false,
RasterizationSamples = SampleCountFlags.SampleCount1Bit
RasterizationSamples = SampleCountFlags.Count1Bit
};
var depthStencilCreateInfo = new PipelineDepthStencilStateCreateInfo()
@ -587,10 +587,10 @@ unsafe class VulkanContent : IDisposable
var colorBlendAttachmentState = new PipelineColorBlendAttachmentState()
{
ColorWriteMask = ColorComponentFlags.ColorComponentABit |
ColorComponentFlags.ColorComponentRBit |
ColorComponentFlags.ColorComponentGBit |
ColorComponentFlags.ColorComponentBBit,
ColorWriteMask = ColorComponentFlags.ABit |
ColorComponentFlags.RBit |
ColorComponentFlags.GBit |
ColorComponentFlags.BBit,
BlendEnable = false
};
@ -617,14 +617,14 @@ unsafe class VulkanContent : IDisposable
{
Offset = 0,
Size = (uint)Marshal.SizeOf<VertextPushConstant>(),
StageFlags = ShaderStageFlags.ShaderStageVertexBit
StageFlags = ShaderStageFlags.VertexBit
};
var fragPushConstantRange = new PushConstantRange()
{
//Offset = vertexPushConstantRange.Size,
Size = (uint)Marshal.SizeOf<VertextPushConstant>(),
StageFlags = ShaderStageFlags.ShaderStageFragmentBit
StageFlags = ShaderStageFlags.FragmentBit
};
var layoutBindingInfo = new DescriptorSetLayoutBinding

14
samples/GpuInterop/VulkanDemo/VulkanContext.cs

@ -86,12 +86,12 @@ public unsafe class VulkanContext : IDisposable
var debugCreateInfo = new DebugUtilsMessengerCreateInfoEXT
{
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
MessageType = DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeGeneralBitExt |
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt |
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt |
DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
MessageType = DebugUtilsMessageTypeFlagsEXT.GeneralBitExt |
DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(LogCallback),
};
@ -173,7 +173,7 @@ public unsafe class VulkanContext : IDisposable
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, familyProperties);
for (uint queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++)
{
var family = familyProperties[c];
var family = familyProperties[queueFamilyIndex];
if (!family.QueueFlags.HasAllFlags(QueueFlags.GraphicsBit))
continue;

12
samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs

@ -1,24 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Silk.NET.Core;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using SilkNetDemo;
namespace GpuInterop.VulkanDemo;
public class VulkanDemoControl : DrawingSurfaceDemoBase
{
private Instance _vkInstance;
private Vk _api;
class VulkanResources : IAsyncDisposable
{
public VulkanContext Context { get; }

50
samples/GpuInterop/VulkanDemo/VulkanImage.cs

@ -4,10 +4,13 @@ using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Vulkan;
using SharpDX.DXGI;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using SilkNetDemo;
using SkiaSharp;
using Device = Silk.NET.Vulkan.Device;
using Format = Silk.NET.Vulkan.Format;
namespace GpuInterop.VulkanDemo;
@ -24,7 +27,6 @@ public unsafe class VulkanImage : IDisposable
private ImageView? _imageView { get; set; }
private DeviceMemory _imageMemory { get; set; }
private SharpDX.Direct3D11.Texture2D? _d3dTexture2D;
private IntPtr _win32ShareHandle;
internal Image? InternalHandle { get; private set; }
internal Format Format { get; }
@ -54,13 +56,13 @@ public unsafe class VulkanImage : IDisposable
Size = size;
MipLevels = 1;//mipLevels;
_imageUsageFlags =
ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit |
ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageSampledBit;
ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit |
ImageUsageFlags.TransferSrcBit | ImageUsageFlags.SampledBit;
//MipLevels = MipLevels != 0 ? MipLevels : (uint)Math.Floor(Math.Log(Math.Max(Size.Width, Size.Height), 2));
var handleType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit :
ExternalMemoryHandleTypeFlags.D3D11TextureBit :
ExternalMemoryHandleTypeFlags.OpaqueFDBit;
var externalMemoryCreateInfo = new ExternalMemoryImageCreateInfo
{
@ -72,19 +74,19 @@ public unsafe class VulkanImage : IDisposable
{
PNext = exportable ? &externalMemoryCreateInfo : null,
SType = StructureType.ImageCreateInfo,
ImageType = ImageType.ImageType2D,
ImageType = ImageType.Type2D,
Format = Format,
Extent =
new Extent3D((uint?)Size.Width,
(uint?)Size.Height, 1),
MipLevels = MipLevels,
ArrayLayers = 1,
Samples = SampleCountFlags.SampleCount1Bit,
Samples = SampleCountFlags.Count1Bit,
Tiling = Tiling,
Usage = _imageUsageFlags,
SharingMode = SharingMode.Exclusive,
InitialLayout = ImageLayout.Undefined,
Flags = ImageCreateFlags.ImageCreateMutableFormatBit
Flags = ImageCreateFlags.CreateMutableFormatBit
};
Api
@ -108,14 +110,14 @@ public unsafe class VulkanImage : IDisposable
if (exportable && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(vk.D3DDevice, size, Format);
using var dxgi = _d3dTexture2D.QueryInterface<SharpDX.DXGI.Resource>();
_win32ShareHandle = dxgi.SharedHandle;
using var dxgi = _d3dTexture2D.QueryInterface<SharpDX.DXGI.Resource1>();
handleImport = new ImportMemoryWin32HandleInfoKHR
{
PNext = &dedicatedAllocation,
SType = StructureType.ImportMemoryWin32HandleInfoKhr,
HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit,
Handle = _win32ShareHandle,
HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureBit,
Handle = dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write),
};
}
@ -128,7 +130,7 @@ public unsafe class VulkanImage : IDisposable
MemoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex(
Api,
_physicalDevice,
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit)
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)
};
Api.AllocateMemory(_device, memoryAllocateInfo, null,
@ -146,7 +148,7 @@ public unsafe class VulkanImage : IDisposable
ComponentSwizzle.Identity,
ComponentSwizzle.Identity);
AspectFlags = ImageAspectFlags.ImageAspectColorBit;
AspectFlags = ImageAspectFlags.ColorBit;
var subresourceRange = new ImageSubresourceRange(AspectFlags, 0, MipLevels, 0, 1);
@ -154,7 +156,7 @@ public unsafe class VulkanImage : IDisposable
{
SType = StructureType.ImageViewCreateInfo,
Image = InternalHandle.Value,
ViewType = ImageViewType.ImageViewType2D,
ViewType = ImageViewType.Type2D,
Format = Format,
Components = componentMapping,
SubresourceRange = subresourceRange
@ -168,7 +170,7 @@ public unsafe class VulkanImage : IDisposable
_currentLayout = ImageLayout.Undefined;
TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr);
TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.NoneKhr);
}
public int ExportFd()
@ -185,11 +187,19 @@ public unsafe class VulkanImage : IDisposable
return fd;
}
public IPlatformHandle Export() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
new PlatformHandle(_win32ShareHandle,
KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle) :
new PlatformHandle(new IntPtr(ExportFd()),
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
public IPlatformHandle Export()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
using var dxgi = _d3dTexture2D!.QueryInterface<Resource1>();
return new PlatformHandle(
dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write),
KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureNtHandle);
}
else
return new PlatformHandle(new IntPtr(ExportFd()),
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
}
public ImageTiling Tiling => ImageTiling.Optimal;

8
samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs

@ -29,7 +29,7 @@ internal static class VulkanMemoryHelper
AccessFlags destinationAccessMask,
uint mipLevels)
{
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, mipLevels, 0, 1);
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ColorBit, 0, mipLevels, 0, 1);
var barrier = new ImageMemoryBarrier
{
@ -46,8 +46,8 @@ internal static class VulkanMemoryHelper
api.CmdPipelineBarrier(
commandBuffer,
PipelineStageFlags.PipelineStageAllCommandsBit,
PipelineStageFlags.PipelineStageAllCommandsBit,
PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.AllCommandsBit,
0,
0,
null,
@ -56,4 +56,4 @@ internal static class VulkanMemoryHelper
1,
barrier);
}
}
}

5
samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs

@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia;
@ -7,9 +6,7 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Vulkan;
using Metsys.Bson;
using Silk.NET.Vulkan;
using SkiaSharp;
namespace GpuInterop.VulkanDemo;
@ -84,7 +81,7 @@ class VulkanSwapchainImage : ISwapchainImage
_image.TransitionLayout(buffer.InternalHandle,
ImageLayout.Undefined, AccessFlags.None,
ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessColorAttachmentReadBit);
ImageLayout.ColorAttachmentOptimal, AccessFlags.ColorAttachmentReadBit);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
buffer.Submit(null,null,null, null, new VulkanCommandBufferPool.VulkanCommandBuffer.KeyedMutexSubmitInfo

2
samples/IntegrationTestApp/App.axaml

@ -2,6 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="IntegrationTestApp.App">
<Application.Styles>
<FluentTheme Mode="Light"/>
<FluentTheme />
</Application.Styles>
</Application>

59
samples/IntegrationTestApp/MainWindow.axaml

@ -70,6 +70,7 @@
<ComboBoxItem>Item 0</ComboBoxItem>
<ComboBoxItem>Item 1</ComboBoxItem>
</ComboBox>
<CheckBox Name="ComboBoxWrapSelection" IsChecked="{Binding #BasicComboBox.WrapSelection}">Wrap Selection</CheckBox>
<Button Name="ComboBoxSelectionClear">Clear Selection</Button>
<Button Name="ComboBoxSelectFirst">Select First</Button>
</StackPanel>
@ -120,30 +121,40 @@
</TabItem>
<TabItem Header="Window">
<StackPanel>
<TextBox Name="ShowWindowSize" Watermark="Window Size"/>
<ComboBox Name="ShowWindowMode" SelectedIndex="0">
<ComboBoxItem>NonOwned</ComboBoxItem>
<ComboBoxItem>Owned</ComboBoxItem>
<ComboBoxItem>Modal</ComboBoxItem>
</ComboBox>
<ComboBox Name="ShowWindowLocation" SelectedIndex="0">
<ComboBoxItem>Manual</ComboBoxItem>
<ComboBoxItem>CenterScreen</ComboBoxItem>
<ComboBoxItem>CenterOwner</ComboBoxItem>
</ComboBox>
<ComboBox Name="ShowWindowState" SelectedIndex="0">
<ComboBoxItem Name="ShowWindowStateNormal">Normal</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateMinimized">Minimized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<Button Name="ShowWindow">Show Window</Button>
<Button Name="SendToBack">Send to Back</Button>
<Button Name="EnterFullscreen">Enter Fullscreen</Button>
<Button Name="ExitFullscreen">Exit Fullscreen</Button>
<Button Name="RestoreAll">Restore All</Button>
</StackPanel>
<Grid ColumnDefinitions="*,8,*">
<StackPanel Grid.Column="0">
<TextBox Name="ShowWindowSize" Watermark="Window Size"/>
<ComboBox Name="ShowWindowMode" SelectedIndex="0">
<ComboBoxItem>NonOwned</ComboBoxItem>
<ComboBoxItem>Owned</ComboBoxItem>
<ComboBoxItem>Modal</ComboBoxItem>
</ComboBox>
<ComboBox Name="ShowWindowLocation" SelectedIndex="0">
<ComboBoxItem>Manual</ComboBoxItem>
<ComboBoxItem>CenterScreen</ComboBoxItem>
<ComboBoxItem>CenterOwner</ComboBoxItem>
</ComboBox>
<ComboBox Name="ShowWindowState" SelectedIndex="0">
<ComboBoxItem Name="ShowWindowStateNormal">Normal</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateMinimized">Minimized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<CheckBox Name="ShowWindowCanResize" IsChecked="True">Can Resize</CheckBox>
<Button Name="ShowWindow">Show Window</Button>
<Button Name="SendToBack">Send to Back</Button>
<Button Name="EnterFullscreen">Enter Fullscreen</Button>
<Button Name="ExitFullscreen">Exit Fullscreen</Button>
<Button Name="RestoreAll">Restore All</Button>
</StackPanel>
<StackPanel Grid.Column="2">
<Button Name="ShowTransparentWindow">Transparent Window</Button>
<Button Name="ShowTransparentPopup">Transparent Popup</Button>
</StackPanel>
</Grid>
</TabItem>
<TabItem Header="SliderTab">
<Slider VerticalAlignment="Top" Name="Slider" Value="30"/>
</TabItem>
</TabControl>
</DockPanel>

93
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -7,9 +7,13 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
using Microsoft.CodeAnalysis;
using Avalonia.Controls.Primitives;
using Avalonia.Threading;
using Avalonia.Controls.Primitives.PopupPositioning;
namespace IntegrationTestApp
{
@ -62,11 +66,13 @@ namespace IntegrationTestApp
var locationComboBox = this.GetControl<ComboBox>("ShowWindowLocation");
var stateComboBox = this.GetControl<ComboBox>("ShowWindowState");
var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
var canResizeCheckBox = this.GetControl<CheckBox>("ShowWindowCanResize");
var owner = (Window)this.GetVisualRoot()!;
var window = new ShowWindowTest
{
WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex,
CanResize = canResizeCheckBox.IsChecked.Value,
};
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
@ -103,6 +109,89 @@ namespace IntegrationTestApp
}
}
private void ShowTransparentWindow()
{
// Show a background window to make sure the color behind the transparent window is
// a known color (green).
var backgroundWindow = new Window
{
Title = "Transparent Window Background",
Name = "TransparentWindowBackground",
Width = 300,
Height = 300,
Background = Brushes.Green,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
// This is the transparent window with a red circle.
var window = new Window
{
Title = "Transparent Window",
Name = "TransparentWindow",
SystemDecorations = SystemDecorations.None,
Background = Brushes.Transparent,
TransparencyLevelHint = WindowTransparencyLevel.Transparent,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Width = 200,
Height = 200,
Content = new Border
{
Background = Brushes.Red,
CornerRadius = new CornerRadius(100),
}
};
window.PointerPressed += (_, _) =>
{
window.Close();
backgroundWindow.Close();
};
backgroundWindow.Show(this);
window.Show(backgroundWindow);
}
private void ShowTransparentPopup()
{
var popup = new Popup
{
WindowManagerAddShadowHint = false,
PlacementMode = PlacementMode.AnchorAndGravity,
PlacementAnchor = PopupAnchor.Top,
PlacementGravity = PopupGravity.Bottom,
Width= 200,
Height= 200,
Child = new Border
{
Background = Brushes.Red,
CornerRadius = new CornerRadius(100),
}
};
// Show a background window to make sure the color behind the transparent window is
// a known color (green).
var backgroundWindow = new Window
{
Title = "Transparent Popup Background",
Name = "TransparentPopupBackground",
Width = 200,
Height = 200,
Background = Brushes.Green,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Content = new Border
{
Name = "PopupContainer",
Child = popup,
[AutomationProperties.AccessibilityViewProperty] = AccessibilityView.Content,
}
};
backgroundWindow.PointerPressed += (_, _) => backgroundWindow.Close();
backgroundWindow.Show(this);
popup.Open();
}
private void SendToBack()
{
var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
@ -175,6 +264,10 @@ namespace IntegrationTestApp
this.Get<ListBox>("BasicListBox").SelectedIndex = -1;
if (source?.Name == "MenuClickedMenuItemReset")
this.Get<TextBlock>("ClickedMenuItem").Text = "None";
if (source?.Name == "ShowTransparentWindow")
ShowTransparentWindow();
if (source?.Name == "ShowTransparentPopup")
ShowTransparentPopup();
if (source?.Name == "ShowWindow")
ShowWindow();
if (source?.Name == "SendToBack")

1
samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj

@ -24,7 +24,6 @@
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MobileSandbox\MobileSandbox.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<!-- For native controls test -->
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
</ItemGroup>

5
samples/MobileSandbox/App.xaml

@ -1,8 +1,9 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Mobile Sandbox"
x:Class="MobileSandbox.App">
x:Class="MobileSandbox.App"
RequestedThemeVariant="Dark">
<Application.Styles>
<FluentTheme Mode="Dark" />
<FluentTheme />
</Application.Styles>
</Application>

4
samples/MobileSandbox/MainView.xaml

@ -5,8 +5,8 @@
x:DataType="mobileSandbox:MainView">
<StackPanel Margin="100 50" Spacing="50">
<TextBlock Text="Login" Foreground="White" />
<TextBox Watermark="Text" />
<TextBox Watermark="Username" TextInputOptions.ContentType="Email" AcceptsReturn="True" TextInputOptions.ReturnKeyType="Search" />
<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" />
<Button Content="Login" Command="{Binding ButtonCommand}" />

2
samples/PlatformSanityChecks/App.xaml

@ -1,5 +1,5 @@
<Application xmlns="https://github.com/avaloniaui">
<Application.Styles>
<SimpleTheme Mode="Light" />
<SimpleTheme />
</Application.Styles>
</Application>

2
samples/Previewer/App.xaml

@ -1,5 +1,5 @@
<Application xmlns="https://github.com/avaloniaui">
<Application.Styles>
<SimpleTheme Mode="Light" />
<SimpleTheme />
</Application.Styles>
</Application>

14
samples/RenderDemo/MainWindow.xaml

@ -26,6 +26,20 @@
IsHitTestVisible="False" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="{Binding ToggleDrawLayoutTimeGraph}" Header="Draw layout time graph">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsChecked="{Binding DrawLayoutTimeGraph}"
IsHitTestVisible="False" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="{Binding ToggleDrawRenderTimeGraph}" Header="Draw render time graph">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsChecked="{Binding DrawRenderTimeGraph}"
IsHitTestVisible="False" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Tests">
<MenuItem Command="{Binding ResizeWindow}" Header="Resize window" />

23
samples/RenderDemo/MainWindow.xaml.cs

@ -1,7 +1,9 @@
using System;
using System.Linq.Expressions;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Rendering;
using RenderDemo.ViewModels;
using MiniMvvm;
@ -11,13 +13,26 @@ namespace RenderDemo
{
public MainWindow()
{
this.InitializeComponent();
InitializeComponent();
this.AttachDevTools();
var vm = new MainWindowViewModel();
vm.WhenAnyValue(x => x.DrawDirtyRects).Subscribe(x => Renderer.DrawDirtyRects = x);
vm.WhenAnyValue(x => x.DrawFps).Subscribe(x => Renderer.DrawFps = x);
this.DataContext = vm;
void BindOverlay(Expression<Func<MainWindowViewModel, bool>> expr, RendererDebugOverlays overlay)
=> vm.WhenAnyValue(expr).Subscribe(x =>
{
var diagnostics = Renderer.Diagnostics;
diagnostics.DebugOverlays = x ?
diagnostics.DebugOverlays | overlay :
diagnostics.DebugOverlays & ~overlay;
});
BindOverlay(x => x.DrawDirtyRects, RendererDebugOverlays.DirtyRects);
BindOverlay(x => x.DrawFps, RendererDebugOverlays.Fps);
BindOverlay(x => x.DrawLayoutTimeGraph, RendererDebugOverlays.LayoutTimeGraph);
BindOverlay(x => x.DrawRenderTimeGraph, RendererDebugOverlays.RenderTimeGraph);
DataContext = vm;
}
private void InitializeComponent()

45
samples/RenderDemo/ViewModels/MainWindowViewModel.cs

@ -1,49 +1,66 @@
using System.Reactive;
using System.Threading.Tasks;
using System.Threading.Tasks;
using MiniMvvm;
namespace RenderDemo.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private bool drawDirtyRects = false;
private bool drawFps = true;
private double width = 800;
private double height = 600;
private bool _drawDirtyRects;
private bool _drawFps = true;
private bool _drawLayoutTimeGraph;
private bool _drawRenderTimeGraph;
private double _width = 800;
private double _height = 600;
public MainWindowViewModel()
{
ToggleDrawDirtyRects = MiniCommand.Create(() => DrawDirtyRects = !DrawDirtyRects);
ToggleDrawFps = MiniCommand.Create(() => DrawFps = !DrawFps);
ToggleDrawLayoutTimeGraph = MiniCommand.Create(() => DrawLayoutTimeGraph = !DrawLayoutTimeGraph);
ToggleDrawRenderTimeGraph = MiniCommand.Create(() => DrawRenderTimeGraph = !DrawRenderTimeGraph);
ResizeWindow = MiniCommand.CreateFromTask(ResizeWindowAsync);
}
public bool DrawDirtyRects
{
get => drawDirtyRects;
set => this.RaiseAndSetIfChanged(ref drawDirtyRects, value);
get => _drawDirtyRects;
set => RaiseAndSetIfChanged(ref _drawDirtyRects, value);
}
public bool DrawFps
{
get => drawFps;
set => this.RaiseAndSetIfChanged(ref drawFps, value);
get => _drawFps;
set => RaiseAndSetIfChanged(ref _drawFps, value);
}
public bool DrawLayoutTimeGraph
{
get => _drawLayoutTimeGraph;
set => RaiseAndSetIfChanged(ref _drawLayoutTimeGraph, value);
}
public bool DrawRenderTimeGraph
{
get => _drawRenderTimeGraph;
set => RaiseAndSetIfChanged(ref _drawRenderTimeGraph, value);
}
public double Width
{
get => width;
set => this.RaiseAndSetIfChanged(ref width, value);
get => _width;
set => RaiseAndSetIfChanged(ref _width, value);
}
public double Height
{
get => height;
set => this.RaiseAndSetIfChanged(ref height, value);
get => _height;
set => RaiseAndSetIfChanged(ref _height, value);
}
public MiniCommand ToggleDrawDirtyRects { get; }
public MiniCommand ToggleDrawFps { get; }
public MiniCommand ToggleDrawLayoutTimeGraph { get; }
public MiniCommand ToggleDrawRenderTimeGraph { get; }
public MiniCommand ResizeWindow { get; }
private async Task ResizeWindowAsync()

12
samples/SampleControls/HamburgerMenu/HamburgerMenu.cs

@ -52,6 +52,14 @@ namespace ControlSamples
var (oldBounds, newBounds) = change.GetOldAndNewValue<Rect>();
EnsureSplitViewMode(oldBounds, newBounds);
}
if (change.Property == SelectedItemProperty)
{
if (_splitView is not null && _splitView.DisplayMode == SplitViewDisplayMode.Overlay)
{
_splitView.SetValue(SplitView.IsPaneOpenProperty, false, Avalonia.Data.BindingPriority.Animation);
}
}
}
private void EnsureSplitViewMode(Rect oldBounds, Rect newBounds)
@ -60,12 +68,12 @@ namespace ControlSamples
{
var threshold = ExpandedModeThresholdWidth;
if (newBounds.Width >= threshold && oldBounds.Width < threshold)
if (newBounds.Width >= threshold)
{
_splitView.DisplayMode = SplitViewDisplayMode.Inline;
_splitView.IsPaneOpen = true;
}
else if (newBounds.Width < threshold && oldBounds.Width >= threshold)
else if (newBounds.Width < threshold)
{
_splitView.DisplayMode = SplitViewDisplayMode.Overlay;
_splitView.IsPaneOpen = false;

34
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

@ -20,6 +20,21 @@
</Border>
</Design.PreviewWith>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<Color x:Key="HamburgerBaseHighColor">#99FFFFFF</Color>
<Color x:Key="HamburgerChromeMediumColor">#FF1F1F1F</Color>
<Color x:Key="HamburgerAltHighColor">#FF000000</Color>
<Color x:Key="HamburgerChromeLowColor">#FF171717</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Default">
<Color x:Key="HamburgerBaseHighColor">#99000000</Color>
<Color x:Key="HamburgerChromeMediumColor">#FFE6E6E6</Color>
<Color x:Key="HamburgerAltHighColor">#FFFFFFFF</Color>
<Color x:Key="HamburgerChromeLowColor">#FFF2F2F2</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<x:Double x:Key="PaneCompactWidth">40</x:Double>
<x:Double x:Key="PaneExpandWidth">220</x:Double>
<x:Double x:Key="HeaderHeight">36</x:Double>
@ -36,7 +51,6 @@
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Height" Value="{StaticResource NavigationItemHeight}" />
@ -64,7 +78,7 @@
</Setter>
<Style Selector="^:pointerover /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="Border.Background" Value="{DynamicResource HamburgerChromeLowColor}" />
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
</Style>
@ -101,7 +115,7 @@
VerticalAlignment="Center"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}"
IsVisible="False"
CornerRadius="{DynamicResource ControlCornerRadius}"/>
CornerRadius="4"/>
<ContentPresenter Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}"
Margin="0"
@ -121,9 +135,9 @@
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
<Style Selector="^ /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="Background" Value="{DynamicResource HamburgerChromeLowColor}" />
<Setter Property="BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource HamburgerBaseHighColor}" />
</Style>
</Style>
@ -136,18 +150,18 @@
</Style>
<Style Selector="^:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="Border.Background" Value="{DynamicResource HamburgerChromeLowColor}" />
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource HamburgerBaseHighColor}" />
</Style>
</ControlTheme>
<!-- HamburgerMenu -->
<ControlTheme x:Key="{x:Type catalog:HamburgerMenu}" TargetType="catalog:HamburgerMenu">
<Setter Property="Padding" Value="12 8 4 0" />
<Setter Property="PaneBackground" Value="{DynamicResource SystemChromeMediumColor}" />
<Setter Property="Background" Value="{DynamicResource SystemChromeMediumColor}" />
<Setter Property="ContentBackground" Value="{DynamicResource SystemAltHighColor}" />
<Setter Property="PaneBackground" Value="{DynamicResource HamburgerChromeMediumColor}" />
<Setter Property="Background" Value="{DynamicResource HamburgerChromeMediumColor}" />
<Setter Property="ContentBackground" Value="{DynamicResource HamburgerAltHighColor}" />
<Setter Property="ItemContainerTheme" Value="{StaticResource HamburgerMenuTabItem}"/>
<Setter Property="TabStripPlacement" Value="Left" />
<Setter Property="Template">

2
samples/Sandbox/App.axaml

@ -3,6 +3,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Sandbox.App">
<Application.Styles>
<FluentTheme Mode="Dark"/>
<FluentTheme />
</Application.Styles>
</Application>

11
samples/interop/WindowsInteropTest/Program.cs

@ -1,5 +1,4 @@
using System;
using Avalonia.Controls;
using ControlCatalog;
using Avalonia;
@ -15,7 +14,15 @@ namespace WindowsInteropTest
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
AppBuilder.Configure<App>().UseWin32().UseDirect2D1().SetupWithoutStarting();
AppBuilder.Configure<App>()
.UseWin32()
.UseDirect2D1()
.With(new Win32PlatformOptions
{
UseWindowsUIComposition = false,
ShouldRenderOnUIThread = true // necessary for WPF
})
.SetupWithoutStarting();
System.Windows.Forms.Application.Run(new SelectorForm());
}
}

7
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net461</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
@ -10,9 +10,6 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj" />
<ProjectReference Include="..\..\ControlCatalog\ControlCatalog.csproj">
<Project>{d0a739b9-3c68-4ba6-a328-41606954b6bd}</Project>
<Name>ControlCatalog</Name>
</ProjectReference>
<ProjectReference Include="..\..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup>
</Project>

42
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -5,8 +5,10 @@ using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Input.TextInput;
using Avalonia.Reactive;
namespace Avalonia.Android
{
@ -32,7 +34,7 @@ namespace Avalonia.Android
ActionPrevious = 0x00000007,
}
class AndroidInputMethod<TView> : ITextInputMethodImpl, IAndroidInputMethod
internal class AndroidInputMethod<TView> : ITextInputMethodImpl, IAndroidInputMethod
where TView : View, IInitEditorInfo
{
private readonly TView _host;
@ -68,23 +70,10 @@ namespace Avalonia.Android
public void SetClient(ITextInputMethodClient client)
{
if (_client != null)
{
_client.SurroundingTextChanged -= SurroundingTextChanged;
}
if(_inputConnection != null)
{
_inputConnection.ComposingText = null;
_inputConnection.ComposingRegion = default;
}
_client = client;
if (IsActive)
{
_client.SurroundingTextChanged += SurroundingTextChanged;
_host.RequestFocus();
_imm.RestartInput(View);
@ -101,24 +90,6 @@ namespace Avalonia.Android
}
}
private void SurroundingTextChanged(object sender, EventArgs e)
{
if (IsActive && _inputConnection != null)
{
var surroundingText = Client.SurroundingText;
_inputConnection.SurroundingText = surroundingText;
_imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
if (_inputConnection.ComposingText != null && !_inputConnection.IsCommiting && surroundingText.AnchorOffset == surroundingText.CursorOffset)
{
_inputConnection.CommitText(_inputConnection.ComposingText, 0);
_inputConnection.SetSelection(surroundingText.AnchorOffset, surroundingText.CursorOffset);
}
}
}
public void SetCursorRect(Rect rect)
{
@ -157,17 +128,20 @@ namespace Avalonia.Android
TextInputReturnKeyType.Search => (ImeFlags)CustomImeFlags.ActionSearch,
TextInputReturnKeyType.Next => (ImeFlags)CustomImeFlags.ActionNext,
TextInputReturnKeyType.Previous => (ImeFlags)CustomImeFlags.ActionPrevious,
_ => (ImeFlags)CustomImeFlags.ActionDone
TextInputReturnKeyType.Done => (ImeFlags)CustomImeFlags.ActionDone,
_ => options.Multiline ? ImeFlags.NoEnterAction : (ImeFlags)CustomImeFlags.ActionDone
};
outAttrs.ImeOptions |= ImeFlags.NoFullscreen | ImeFlags.NoExtractUi;
_client.TextEditable = _inputConnection.InputEditable;
return _inputConnection;
});
}
}
public readonly record struct ComposingRegion
internal readonly record struct ComposingRegion
{
private readonly int _start = -1;
private readonly int _end = -1;

1
src/Android/Avalonia.Android/Avalonia.Android.csproj

@ -5,6 +5,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
<DebugType>portable</DebugType>
<AndroidResgenNamespace>Avalonia.Android.Internal</AndroidResgenNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />

127
src/Android/Avalonia.Android/InputEditable.cs

@ -0,0 +1,127 @@
using System;
using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Java.Lang;
using static System.Net.Mime.MediaTypeNames;
namespace Avalonia.Android
{
internal class InputEditable : SpannableStringBuilder, ITextEditable
{
private readonly TopLevelImpl _topLevel;
private readonly IAndroidInputMethod _inputMethod;
private readonly AvaloniaInputConnection _avaloniaInputConnection;
private int _currentBatchLevel;
private string _previousText;
private int _previousSelectionStart;
private int _previousSelectionEnd;
public event EventHandler TextChanged;
public event EventHandler SelectionChanged;
public event EventHandler CompositionChanged;
public InputEditable(TopLevelImpl topLevel, IAndroidInputMethod inputMethod, AvaloniaInputConnection avaloniaInputConnection)
{
_topLevel = topLevel;
_inputMethod = inputMethod;
_avaloniaInputConnection = avaloniaInputConnection;
}
public InputEditable(ICharSequence text) : base(text)
{
}
public InputEditable(string text) : base(text)
{
}
public InputEditable(ICharSequence text, int start, int end) : base(text, start, end)
{
}
public InputEditable(string text, int start, int end) : base(text, start, end)
{
}
protected InputEditable(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
public int SelectionStart
{
get => Selection.GetSelectionStart(this); set
{
var end = SelectionEnd < 0 ? 0 : SelectionEnd;
_avaloniaInputConnection.SetSelection(value, end);
_inputMethod.IMM.UpdateSelection(_topLevel.View, value, end, value, end);
}
}
public int SelectionEnd
{
get => Selection.GetSelectionEnd(this); set
{
var start = SelectionStart < 0 ? 0 : SelectionStart;
_avaloniaInputConnection.SetSelection(start, value);
_inputMethod.IMM.UpdateSelection(_topLevel.View, start, value, start, value);
}
}
public string? Text
{
get => ToString(); set
{
if (Text != value)
{
Clear();
Insert(0, value ?? "");
}
}
}
public int CompositionStart => BaseInputConnection.GetComposingSpanStart(this);
public int CompositionEnd => BaseInputConnection.GetComposingSpanEnd(this);
public void BeginBatchEdit()
{
_currentBatchLevel++;
if (_currentBatchLevel == 1)
{
_previousText = ToString();
_previousSelectionStart = SelectionStart;
_previousSelectionEnd = SelectionEnd;
}
}
public void EndBatchEdit()
{
if (_currentBatchLevel == 1)
{
if(_previousText != Text)
{
TextChanged?.Invoke(this, EventArgs.Empty);
}
if (_previousSelectionStart != SelectionStart || _previousSelectionEnd != SelectionEnd)
{
SelectionChanged?.Invoke(this, EventArgs.Empty);
}
}
_currentBatchLevel--;
}
public void RaiseCompositionChanged()
{
CompositionChanged?.Invoke(this, EventArgs.Empty);
}
}
}

2
src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs

@ -5,7 +5,7 @@ using Avalonia.Input;
namespace Avalonia.Android.Platform.Input
{
public class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
internal class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
private static readonly Dictionary<Keycode, Key> KeyDic = new Dictionary<Keycode, Key>
{
// { Keycode.Cancel?, Key.Cancel },

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs

@ -10,7 +10,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
internal abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
{
bool _invalidateQueued;
readonly object _lock = new object();

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

@ -28,6 +28,7 @@ using Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
using Window = Android.Views.Window;
using Android.Graphics.Drawables;
using Java.Util;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@ -410,159 +411,73 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
private readonly TopLevelImpl _topLevel;
private readonly IAndroidInputMethod _inputMethod;
private readonly InputEditable _editable;
public AvaloniaInputConnection(TopLevelImpl topLevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true)
{
_topLevel = topLevel;
_inputMethod = inputMethod;
_editable = new InputEditable(_topLevel, _inputMethod, this);
}
public TextInputMethodSurroundingText SurroundingText { get; set; }
public override IEditable Editable => _editable;
public string ComposingText { get; internal set; }
public ComposingRegion? ComposingRegion { get; internal set; }
public bool IsComposing => !string.IsNullOrEmpty(ComposingText);
public bool IsCommiting { get; private set; }
internal InputEditable InputEditable => _editable;
public override bool SetComposingRegion(int start, int end)
{
//System.Diagnostics.Debug.WriteLine($"Composing Region: [{start}|{end}] {SurroundingText.Text?.Substring(start, end - start)}");
var ret = base.SetComposingRegion(start, end);
ComposingRegion = new ComposingRegion(start, end);
InputEditable.RaiseCompositionChanged();
return base.SetComposingRegion(start, end);
return ret;
}
public override bool SetComposingText(ICharSequence text, int newCursorPosition)
{
var composingText = text.ToString();
ComposingText = composingText;
_inputMethod.Client?.SetPreeditText(ComposingText);
return base.SetComposingText(text, newCursorPosition);
}
public override bool FinishComposingText()
{
if (!string.IsNullOrEmpty(ComposingText))
if (string.IsNullOrEmpty(composingText))
{
CommitText(ComposingText, ComposingText.Length);
return CommitText(text, newCursorPosition);
}
else
{
ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset);
}
return base.FinishComposingText();
}
public override ICharSequence GetTextBeforeCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
{
if (!string.IsNullOrEmpty(SurroundingText.Text) && length > 0)
{
var start = System.Math.Max(SurroundingText.CursorOffset - length, 0);
var ret = base.SetComposingText(text, newCursorPosition);
var end = System.Math.Min(start + length - 1, SurroundingText.CursorOffset);
InputEditable.RaiseCompositionChanged();
var text = SurroundingText.Text.Substring(start, end - start);
//System.Diagnostics.Debug.WriteLine($"Text Before: {text}");
return new Java.Lang.String(text);
return ret;
}
return null;
}
public override ICharSequence GetTextAfterCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
public override bool BeginBatchEdit()
{
if (!string.IsNullOrEmpty(SurroundingText.Text))
{
var start = SurroundingText.CursorOffset;
var end = System.Math.Min(start + length, SurroundingText.Text.Length);
var text = SurroundingText.Text.Substring(start, end - start);
_editable.BeginBatchEdit();
//System.Diagnostics.Debug.WriteLine($"Text After: {text}");
return new Java.Lang.String(text);
}
return null;
return base.BeginBatchEdit();
}
public override bool CommitText(ICharSequence text, int newCursorPosition)
public override bool EndBatchEdit()
{
IsCommiting = true;
var committedText = text.ToString();
_inputMethod.Client.SetPreeditText(null);
var ret = base.EndBatchEdit();
_editable.EndBatchEdit();
int? start, end;
if(SurroundingText.CursorOffset != SurroundingText.AnchorOffset)
{
start = Math.Min(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
end = Math.Max(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
}
else if (ComposingRegion != null)
{
start = ComposingRegion?.Start;
end = ComposingRegion?.End;
ComposingRegion = null;
}
else
{
start = end = _inputMethod.Client.SurroundingText.CursorOffset;
}
_inputMethod.Client.SelectInSurroundingText((int)start, (int)end);
var time = DateTime.Now.TimeOfDay;
var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (ulong)time.Ticks, _topLevel.InputRoot, committedText);
_topLevel.Input(rawTextEvent);
ComposingText = null;
ComposingRegion = new ComposingRegion(newCursorPosition, newCursorPosition);
return base.CommitText(text, newCursorPosition);
return ret;
}
public override bool DeleteSurroundingText(int beforeLength, int afterLength)
public override bool FinishComposingText()
{
var surroundingText = _inputMethod.Client.SurroundingText;
var selectionStart = surroundingText.CursorOffset;
_inputMethod.Client.SelectInSurroundingText(selectionStart - beforeLength, selectionStart + afterLength);
_inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
surroundingText = _inputMethod.Client.SurroundingText;
selectionStart = surroundingText.CursorOffset;
ComposingRegion = new ComposingRegion(selectionStart, selectionStart);
return base.DeleteSurroundingText(beforeLength, afterLength);
var ret = base.FinishComposingText();
InputEditable.RaiseCompositionChanged();
return ret;
}
public override bool SetSelection(int start, int end)
public override bool CommitText(ICharSequence text, int newCursorPosition)
{
_inputMethod.Client.SelectInSurroundingText(start, end);
ComposingRegion = new ComposingRegion(start, end);
return base.SetSelection(start, end);
var ret = base.CommitText(text, newCursorPosition);
InputEditable.RaiseCompositionChanged();
return ret;
}
public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)

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

@ -177,11 +177,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
public AndroidStorageFile(Activity activity, AndroidUri uri) : base(activity, uri, false)
{
}
public bool CanOpenRead => true;
public bool CanOpenWrite => true;
public Task<Stream> OpenReadAsync() => Task.FromResult(OpenContentStream(Activity, Uri, false)
?? throw new InvalidOperationException("Failed to open content stream"));

6
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs

@ -37,7 +37,7 @@ internal class AndroidStorageProvider : IStorageProvider
return Task.FromResult<IStorageBookmarkFolder?>(new AndroidStorageFolder(_activity, uri, false));
}
public async Task<IStorageFile?> TryGetFileFromPath(Uri filePath)
public async Task<IStorageFile?> TryGetFileFromPathAsync(Uri filePath)
{
if (filePath is null)
{
@ -70,7 +70,7 @@ internal class AndroidStorageProvider : IStorageProvider
return new AndroidStorageFile(_activity, androidUri);
}
public async Task<IStorageFolder?> TryGetFolderFromPath(Uri folderPath)
public async Task<IStorageFolder?> TryGetFolderFromPathAsync(Uri folderPath)
{
if (folderPath is null)
{
@ -103,7 +103,7 @@ internal class AndroidStorageProvider : IStorageProvider
return new AndroidStorageFolder(_activity, androidUri, false);
}
public Task<IStorageFolder?> TryGetWellKnownFolder(WellKnownFolder wellKnownFolder)
public Task<IStorageFolder?> TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder)
{
var dirCode = wellKnownFolder switch
{

4
src/Android/Avalonia.Android/PlatformIconLoader.cs

@ -3,7 +3,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
class PlatformIconLoader : IPlatformIconLoader
internal class PlatformIconLoader : IPlatformIconLoader
{
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
@ -29,7 +29,7 @@ namespace Avalonia.Android
}
// Stores the icon created as a stream to support saving even though an icon is never shown
public class FakeIcon : IWindowIconImpl
internal class FakeIcon : IWindowIconImpl
{
private Stream stream = new MemoryStream();

6
src/Android/Avalonia.Android/Stubs.cs

@ -4,7 +4,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
class WindowingPlatformStub : IWindowingPlatform
internal class WindowingPlatformStub : IWindowingPlatform
{
public IWindowImpl CreateWindow() => throw new NotSupportedException();
@ -13,7 +13,7 @@ namespace Avalonia.Android
public ITrayIconImpl CreateTrayIcon() => null;
}
class PlatformIconLoaderStub : IPlatformIconLoader
internal class PlatformIconLoaderStub : IPlatformIconLoader
{
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
@ -38,7 +38,7 @@ namespace Avalonia.Android
}
}
public class IconStub : IWindowIconImpl
internal class IconStub : IWindowIconImpl
{
private readonly MemoryStream _ms;

39
src/Avalonia.Base/Animation/Animatable.cs

@ -27,7 +27,11 @@ namespace Avalonia.Animation
AvaloniaProperty.Register<Animatable, Transitions?>(nameof(Transitions));
private bool _transitionsEnabled = true;
private bool _isSubscribedToTransitionsCollection = false;
private Dictionary<ITransition, TransitionState>? _transitionState;
private NotifyCollectionChangedEventHandler? _collectionChanged;
private NotifyCollectionChangedEventHandler TransitionsCollectionChangedHandler =>
_collectionChanged ??= TransitionsCollectionChanged;
/// <summary>
/// Gets or sets the clock which controls the animations on the control.
@ -60,9 +64,14 @@ namespace Avalonia.Animation
{
_transitionsEnabled = true;
if (Transitions is object)
if (Transitions is Transitions transitions)
{
AddTransitions(Transitions);
if (!_isSubscribedToTransitionsCollection)
{
_isSubscribedToTransitionsCollection = true;
transitions.CollectionChanged += TransitionsCollectionChangedHandler;
}
AddTransitions(transitions);
}
}
}
@ -72,7 +81,7 @@ namespace Avalonia.Animation
/// </summary>
/// <remarks>
/// This method should not be called from user code, it will be called automatically by the framework
/// when a control is added to the visual tree.
/// when a control is removed from the visual tree.
/// </remarks>
protected void DisableTransitions()
{
@ -80,9 +89,14 @@ namespace Avalonia.Animation
{
_transitionsEnabled = false;
if (Transitions is object)
if (Transitions is Transitions transitions)
{
RemoveTransitions(Transitions);
if (_isSubscribedToTransitionsCollection)
{
_isSubscribedToTransitionsCollection = false;
transitions.CollectionChanged -= TransitionsCollectionChangedHandler;
}
RemoveTransitions(transitions);
}
}
}
@ -109,7 +123,8 @@ namespace Avalonia.Animation
toAdd = newTransitions.Except(oldTransitions).ToList();
}
newTransitions.CollectionChanged += TransitionsCollectionChanged;
newTransitions.CollectionChanged += TransitionsCollectionChangedHandler;
_isSubscribedToTransitionsCollection = true;
AddTransitions(toAdd);
}
@ -122,19 +137,19 @@ namespace Avalonia.Animation
toRemove = oldTransitions.Except(newTransitions).ToList();
}
oldTransitions.CollectionChanged -= TransitionsCollectionChanged;
oldTransitions.CollectionChanged -= TransitionsCollectionChangedHandler;
RemoveTransitions(toRemove);
}
}
else if (_transitionsEnabled &&
Transitions is object &&
Transitions is Transitions transitions &&
_transitionState is object &&
!change.Property.IsDirect &&
change.Priority > BindingPriority.Animation)
{
for (var i = Transitions.Count -1; i >= 0; --i)
for (var i = transitions.Count - 1; i >= 0; --i)
{
var transition = Transitions[i];
var transition = transitions[i];
if (transition.Property == change.Property &&
_transitionState.TryGetValue(transition, out var state))
@ -154,11 +169,11 @@ namespace Avalonia.Animation
{
oldValue = animatedValue;
}
var clock = Clock ?? AvaloniaLocator.Current.GetRequiredService<IGlobalClock>();
state.Instance?.Dispose();
state.Instance = transition.Apply(
this,
Clock ?? AvaloniaLocator.Current.GetRequiredService<IGlobalClock>(),
clock,
oldValue,
newValue);
return;

11
src/Avalonia.Base/Animation/KeySpline.cs

@ -79,15 +79,12 @@ namespace Avalonia.Animation
/// <param name="culture">culture of the string</param>
/// <exception cref="FormatException">Thrown if the string does not have 4 values</exception>
/// <returns>A <see cref="KeySpline"/> with the appropriate values set</returns>
public static KeySpline Parse(string value, CultureInfo culture)
public static KeySpline Parse(string value, CultureInfo? culture)
{
if (culture is null)
culture = CultureInfo.InvariantCulture;
culture ??= CultureInfo.InvariantCulture;
using (var tokenizer = new StringTokenizer((string)value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\"."))
{
return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
}
using var tokenizer = new StringTokenizer(value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\".");
return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
}
/// <summary>

108
src/Avalonia.Base/AvaloniaObject.cs

@ -118,7 +118,7 @@ namespace Avalonia
{
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
_values.ClearLocalValue(property);
_values.ClearValue(property);
}
/// <summary>
@ -152,7 +152,7 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
_values?.ClearLocalValue(property);
_values.ClearValue(property);
}
/// <summary>
@ -242,7 +242,14 @@ namespace Avalonia
return registered.InvokeGetter(this);
}
/// <inheritdoc/>
/// <summary>
/// Gets an <see cref="AvaloniaProperty"/> base value.
/// </summary>
/// <param name="property">The property.</param>
/// <remarks>
/// Gets the value of the property excluding animated values, otherwise <see cref="Optional{T}.Empty"/>.
/// Note that this method does not return property values that come from inherited or default values.
/// </remarks>
public Optional<T> GetBaseValue<T>(StyledProperty<T> property)
{
_ = property ?? throw new ArgumentNullException(nameof(property));
@ -261,7 +268,7 @@ namespace Avalonia
VerifyAccess();
return _values?.IsAnimating(property) ?? false;
return _values.IsAnimating(property);
}
/// <summary>
@ -270,8 +277,8 @@ namespace Avalonia
/// <param name="property">The property.</param>
/// <returns>True if the property is set, otherwise false.</returns>
/// <remarks>
/// Checks whether a value is assigned to the property, or that there is a binding to the
/// property that is producing a value other than <see cref="AvaloniaProperty.UnsetValue"/>.
/// Returns true if <paramref name="property"/> is a styled property which has a value
/// assigned to it or a binding targeting it; otherwise false.
/// </remarks>
public bool IsSet(AvaloniaProperty property)
{
@ -279,7 +286,7 @@ namespace Avalonia
VerifyAccess();
return _values?.IsSet(property) ?? false;
return _values.IsSet(property);
}
/// <summary>
@ -322,7 +329,7 @@ namespace Avalonia
if (value is UnsetValueType)
{
if (priority == BindingPriority.LocalValue)
_values.ClearLocalValue(property);
_values.ClearValue(property);
}
else if (value is not DoNothingType)
{
@ -348,6 +355,57 @@ namespace Avalonia
SetDirectValueUnchecked(property, value);
}
/// <summary>
/// Sets the value of a dependency property without changing its value source.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <remarks>
/// This method is used by a component that programmatically sets the value of one of its
/// own properties without disabling an application's declared use of the property. The
/// method changes the effective value of the property, but existing data bindings and
/// styles will continue to work.
///
/// The new value will have the property's current <see cref="BindingPriority"/>, even if
/// that priority is <see cref="BindingPriority.Unset"/> or
/// <see cref="BindingPriority.Inherited"/>.
/// </remarks>
public void SetCurrentValue(AvaloniaProperty property, object? value) =>
property.RouteSetCurrentValue(this, value);
/// <summary>
/// Sets the value of a dependency property without changing its value source.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <remarks>
/// This method is used by a component that programmatically sets the value of one of its
/// own properties without disabling an application's declared use of the property. The
/// method changes the effective value of the property, but existing data bindings and
/// styles will continue to work.
///
/// The new value will have the property's current <see cref="BindingPriority"/>, even if
/// that priority is <see cref="BindingPriority.Unset"/> or
/// <see cref="BindingPriority.Inherited"/>.
/// </remarks>
public void SetCurrentValue<T>(StyledProperty<T> property, T value)
{
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
LogPropertySet(property, value, BindingPriority.LocalValue);
if (value is UnsetValueType)
{
_values.ClearValue(property);
}
else if (value is not DoNothingType)
{
_values.SetCurrentValue(property, value);
}
}
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
@ -515,14 +573,12 @@ namespace Avalonia
/// <param name="property">The property.</param>
public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
/// <inheritdoc/>
internal void AddInheritanceChild(AvaloniaObject child)
{
_inheritanceChildren ??= new List<AvaloniaObject>();
_inheritanceChildren.Add(child);
}
/// <inheritdoc/>
internal void RemoveInheritanceChild(AvaloniaObject child)
{
_inheritanceChildren?.Remove(child);
@ -541,24 +597,12 @@ namespace Avalonia
return new AvaloniaPropertyValue(
property,
GetValue(property),
BindingPriority.Unset,
"Local Value");
}
else if (_values != null)
{
var result = _values.GetDiagnostic(property);
if (result != null)
{
return result;
}
BindingPriority.LocalValue,
null,
false);
}
return new AvaloniaPropertyValue(
property,
GetValue(property),
BindingPriority.Unset,
"Unset");
return _values.GetDiagnostic(property);
}
internal ValueStore GetValueStore() => _values;
@ -620,14 +664,12 @@ namespace Avalonia
/// <param name="property">The property that has changed.</param>
/// <param name="oldValue">The old property value.</param>
/// <param name="newValue">The new property value.</param>
/// <param name="priority">The priority of the binding that produced the value.</param>
protected void RaisePropertyChanged<T>(
DirectPropertyBase<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority = BindingPriority.LocalValue)
T oldValue,
T newValue)
{
RaisePropertyChanged(property, oldValue, newValue, priority, true);
RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue, true);
}
/// <summary>
@ -676,7 +718,7 @@ namespace Avalonia
/// <returns>
/// True if the value changed, otherwise false.
/// </returns>
protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value)
protected bool SetAndRaise<T>(DirectPropertyBase<T> property, ref T field, T value)
{
VerifyAccess();

50
src/Avalonia.Base/AvaloniaProperty.cs

@ -225,13 +225,8 @@ namespace Avalonia
/// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="coerce">A value coercion callback.</param>
/// <param name="notifying">
/// A method that gets called before and after the property starts being notified on an
/// object; the bool argument will be true before and false afterwards. This callback is
/// intended to support IsDataContextChanging.
/// </param>
/// <returns>A <see cref="StyledProperty{TValue}"/></returns>
public static StyledProperty<TValue> Register<TOwner, TValue>(
string name,
@ -239,8 +234,40 @@ namespace Avalonia
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func<TValue, bool>? validate = null,
Func<AvaloniaObject, TValue, TValue>? coerce = null,
Action<AvaloniaObject, bool>? notifying = null)
Func<AvaloniaObject, TValue, TValue>? coerce = null)
where TOwner : AvaloniaObject
{
_ = name ?? throw new ArgumentNullException(nameof(name));
var metadata = new StyledPropertyMetadata<TValue>(
defaultValue,
defaultBindingMode: defaultBindingMode,
coerce: coerce);
var result = new StyledProperty<TValue>(
name,
typeof(TOwner),
metadata,
inherits,
validate);
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}
/// <inheritdoc cref="Register{TOwner, TValue}" />
/// <param name="notifying">
/// A method that gets called before and after the property starts being notified on an
/// object; the bool argument will be true before and false afterwards. This callback is
/// intended to support IsDataContextChanging.
/// </param>
internal static StyledProperty<TValue> Register<TOwner, TValue>(
string name,
TValue defaultValue,
bool inherits,
BindingMode defaultBindingMode,
Func<TValue, bool>? validate,
Func<AvaloniaObject, TValue, TValue>? coerce,
Action<AvaloniaObject, bool>? notifying)
where TOwner : AvaloniaObject
{
_ = name ?? throw new ArgumentNullException(nameof(name));
@ -496,6 +523,13 @@ namespace Avalonia
object? value,
BindingPriority priority);
/// <summary>
/// Routes an untyped SetCurrentValue call to a typed call.
/// </summary>
/// <param name="o">The object instance.</param>
/// <param name="value">The value.</param>
internal abstract void RouteSetCurrentValue(AvaloniaObject o, object? value);
/// <summary>
/// Routes an untyped Bind call to a typed call.
/// </summary>

18
src/Avalonia.Base/Collections/AvaloniaDictionary.cs

@ -14,11 +14,7 @@ namespace Avalonia.Collections
/// </summary>
/// <typeparam name="TKey">The type of the dictionary key.</typeparam>
/// <typeparam name="TValue">The type of the dictionary value.</typeparam>
public class AvaloniaDictionary<TKey, TValue> : IDictionary<TKey, TValue>,
IDictionary,
INotifyCollectionChanged,
INotifyPropertyChanged
where TKey : notnull
public class AvaloniaDictionary<TKey, TValue> : IAvaloniaDictionary<TKey, TValue> where TKey : notnull
{
private Dictionary<TKey, TValue> _inner;
@ -29,6 +25,14 @@ namespace Avalonia.Collections
{
_inner = new Dictionary<TKey, TValue>();
}
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaDictionary{TKey, TValue}"/> class.
/// </summary>
public AvaloniaDictionary(int capacity)
{
_inner = new Dictionary<TKey, TValue>(capacity);
}
/// <summary>
/// Occurs when the collection changes.
@ -62,6 +66,10 @@ namespace Avalonia.Collections
object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot;
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _inner.Keys;
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _inner.Values;
/// <summary>
/// Gets or sets the named resource.
/// </summary>

112
src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs

@ -0,0 +1,112 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Reactive;
namespace Avalonia.Collections
{
/// <summary>
/// Defines extension methods for working with <see cref="AvaloniaList{T}"/>s.
/// </summary>
public static class AvaloniaDictionaryExtensions
{
/// <summary>
/// Invokes an action for each item in a collection and subsequently each item added or
/// removed from the collection.
/// </summary>
/// <typeparam name="TKey">The key type of the collection items.</typeparam>
/// <typeparam name="TValue">The value type of the collection items.</typeparam>
/// <param name="collection">The collection.</param>
/// <param name="added">
/// An action called initially for each item in the collection and subsequently for each
/// item added to the collection. The parameters passed are the index in the collection and
/// the item.
/// </param>
/// <param name="removed">
/// An action called for each item removed from the collection. The parameters passed are
/// the index in the collection and the item.
/// </param>
/// <param name="reset">
/// An action called when the collection is reset. This will be followed by calls to
/// <paramref name="added"/> for each item present in the collection after the reset.
/// </param>
/// <param name="weakSubscription">
/// Indicates if a weak subscription should be used to track changes to the collection.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
internal static IDisposable ForEachItem<TKey, TValue>(
this IAvaloniaReadOnlyDictionary<TKey, TValue> collection,
Action<TKey, TValue> added,
Action<TKey, TValue> removed,
Action reset,
bool weakSubscription = false)
where TKey : notnull
{
void Add(IEnumerable items)
{
foreach (KeyValuePair<TKey, TValue> pair in items)
{
added(pair.Key, pair.Value);
}
}
void Remove(IEnumerable items)
{
foreach (KeyValuePair<TKey, TValue> pair in items)
{
removed(pair.Key, pair.Value);
}
}
NotifyCollectionChangedEventHandler handler = (_, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
Add(e.NewItems!);
break;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace:
Remove(e.OldItems!);
int newIndex = e.NewStartingIndex;
if(newIndex > e.OldStartingIndex)
{
newIndex -= e.OldItems!.Count;
}
Add(e.NewItems!);
break;
case NotifyCollectionChangedAction.Remove:
Remove(e.OldItems!);
break;
case NotifyCollectionChangedAction.Reset:
if (reset == null)
{
throw new InvalidOperationException(
"Reset called on collection without reset handler.");
}
reset();
Add(collection);
break;
}
};
Add(collection);
if (weakSubscription)
{
return collection.WeakSubscribe(handler);
}
else
{
collection.CollectionChanged += handler;
return Disposable.Create(() => collection.CollectionChanged -= handler);
}
}
}
}

13
src/Avalonia.Base/Collections/IAvaloniaDictionary.cs

@ -0,0 +1,13 @@
using System.Collections;
using System.Collections.Generic;
namespace Avalonia.Collections
{
public interface IAvaloniaDictionary<TKey, TValue>
: IDictionary<TKey, TValue>,
IAvaloniaReadOnlyDictionary<TKey, TValue>,
IDictionary
where TKey : notnull
{
}
}

14
src/Avalonia.Base/Collections/IAvaloniaReadOnlyDictionary.cs

@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
namespace Avalonia.Collections
{
public interface IAvaloniaReadOnlyDictionary<TKey, TValue>
: IReadOnlyDictionary<TKey, TValue>,
INotifyCollectionChanged,
INotifyPropertyChanged
where TKey : notnull
{
}
}

6
src/Avalonia.Base/Controls/IResourceDictionary.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using Avalonia.Styling;
#nullable enable
@ -13,5 +14,10 @@ namespace Avalonia.Controls
/// Gets a collection of child resource dictionaries.
/// </summary>
IList<IResourceProvider> MergedDictionaries { get; }
/// <summary>
/// Gets a collection of merged resource dictionaries that are specifically keyed and composed to address theme scenarios.
/// </summary>
IDictionary<ThemeVariant, IResourceProvider> ThemeDictionaries { get; }
}
}

7
src/Avalonia.Base/Controls/IResourceNode.cs

@ -1,5 +1,5 @@
using System;
using Avalonia.Metadata;
using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Controls
{
@ -23,6 +23,7 @@ namespace Avalonia.Controls
/// Tries to find a resource within the object.
/// </summary>
/// <param name="key">The resource key.</param>
/// <param name="theme">Theme used to select theme dictionary.</param>
/// <param name="value">
/// When this method returns, contains the value associated with the specified key,
/// if the key is found; otherwise, null.
@ -30,6 +31,6 @@ namespace Avalonia.Controls
/// <returns>
/// True if the resource if found, otherwise false.
/// </returns>
bool TryGetResource(object key, out object? value);
bool TryGetResource(object key, ThemeVariant? theme, out object? value);
}
}

91
src/Avalonia.Base/Controls/ResourceDictionary.cs

@ -1,9 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Templates;
using Avalonia.Media;
using Avalonia.Styling;
namespace Avalonia.Controls
{
@ -15,6 +18,7 @@ namespace Avalonia.Controls
private Dictionary<object, object?>? _inner;
private IResourceHost? _owner;
private AvaloniaList<IResourceProvider>? _mergedDictionaries;
private AvaloniaDictionary<ThemeVariant, IResourceProvider>? _themeDictionary;
/// <summary>
/// Initializes a new instance of the <see cref="ResourceDictionary"/> class.
@ -69,14 +73,14 @@ namespace Avalonia.Controls
_mergedDictionaries.ForEachItem(
x =>
{
if (Owner is object)
if (Owner is not null)
{
x.AddOwner(Owner);
}
},
x =>
{
if (Owner is object)
if (Owner is not null)
{
x.RemoveOwner(Owner);
}
@ -88,6 +92,34 @@ namespace Avalonia.Controls
}
}
public IDictionary<ThemeVariant, IResourceProvider> ThemeDictionaries
{
get
{
if (_themeDictionary == null)
{
_themeDictionary = new AvaloniaDictionary<ThemeVariant, IResourceProvider>(2);
_themeDictionary.ForEachItem(
(_, x) =>
{
if (Owner is not null)
{
x.AddOwner(Owner);
}
},
(_, x) =>
{
if (Owner is not null)
{
x.RemoveOwner(Owner);
}
},
() => throw new NotSupportedException("Dictionary reset not supported"));
}
return _themeDictionary;
}
}
bool IResourceNode.HasResources
{
get
@ -152,16 +184,47 @@ namespace Avalonia.Controls
return false;
}
public bool TryGetResource(object key, out object? value)
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
{
if (TryGetValue(key, out value))
return true;
if (_themeDictionary is not null)
{
IResourceProvider? themeResourceProvider;
if (theme is not null && theme != ThemeVariant.Default)
{
if (_themeDictionary.TryGetValue(theme, out themeResourceProvider)
&& themeResourceProvider.TryGetResource(key, theme, out value))
{
return true;
}
var themeInherit = theme.InheritVariant;
while (themeInherit is not null)
{
if (_themeDictionary.TryGetValue(themeInherit, out themeResourceProvider)
&& themeResourceProvider.TryGetResource(key, theme, out value))
{
return true;
}
themeInherit = themeInherit.InheritVariant;
}
}
if (_themeDictionary.TryGetValue(ThemeVariant.Default, out themeResourceProvider)
&& themeResourceProvider.TryGetResource(key, theme, out value))
{
return true;
}
}
if (_mergedDictionaries != null)
{
for (var i = _mergedDictionaries.Count - 1; i >= 0; --i)
{
if (_mergedDictionaries[i].TryGetResource(key, out value))
if (_mergedDictionaries[i].TryGetResource(key, theme, out value))
{
return true;
}
@ -248,7 +311,7 @@ namespace Avalonia.Controls
var hasResources = _inner?.Count > 0;
if (_mergedDictionaries is object)
if (_mergedDictionaries is not null)
{
foreach (var i in _mergedDictionaries)
{
@ -256,6 +319,14 @@ namespace Avalonia.Controls
hasResources |= i.HasResources;
}
}
if (_themeDictionary is not null)
{
foreach (var i in _themeDictionary.Values)
{
i.AddOwner(owner);
hasResources |= i.HasResources;
}
}
if (hasResources)
{
@ -273,7 +344,7 @@ namespace Avalonia.Controls
var hasResources = _inner?.Count > 0;
if (_mergedDictionaries is object)
if (_mergedDictionaries is not null)
{
foreach (var i in _mergedDictionaries)
{
@ -281,6 +352,14 @@ namespace Avalonia.Controls
hasResources |= i.HasResources;
}
}
if (_themeDictionary is not null)
{
foreach (var i in _themeDictionary.Values)
{
i.RemoveOwner(owner);
hasResources |= i.HasResources;
}
}
if (hasResources)
{

125
src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

@ -1,6 +1,4 @@
using System;
using Avalonia.Data.Converters;
using Avalonia.LogicalTree;
using Avalonia.Reactive;
using Avalonia.Styling;
@ -41,21 +39,66 @@ namespace Avalonia.Controls
control = control ?? throw new ArgumentNullException(nameof(control));
key = key ?? throw new ArgumentNullException(nameof(key));
IResourceNode? current = control;
return control.TryFindResource(key, null, out value);
}
/// <summary>
/// Finds the specified resource by searching up the logical tree and then global styles.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="theme">Theme used to select theme dictionary.</param>
/// <param name="key">The resource key.</param>
/// <returns>The resource, or <see cref="AvaloniaProperty.UnsetValue"/> if not found.</returns>
public static object? FindResource(this IResourceHost control, ThemeVariant? theme, object key)
{
control = control ?? throw new ArgumentNullException(nameof(control));
key = key ?? throw new ArgumentNullException(nameof(key));
if (control.TryFindResource(key, theme, out var value))
{
return value;
}
return AvaloniaProperty.UnsetValue;
}
/// <summary>
/// Tries to the specified resource by searching up the logical tree and then global styles.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="key">The resource key.</param>
/// <param name="theme">Theme used to select theme dictionary.</param>
/// <param name="value">On return, contains the resource if found, otherwise null.</param>
/// <returns>True if the resource was found; otherwise false.</returns>
public static bool TryFindResource(this IResourceHost control, object key, ThemeVariant? theme, out object? value)
{
control = control ?? throw new ArgumentNullException(nameof(control));
key = key ?? throw new ArgumentNullException(nameof(key));
IResourceHost? current = control;
while (current != null)
{
if (current.TryGetResource(key, out value))
if (current.TryGetResource(key, theme, out value))
{
return true;
}
current = (current as IStyleHost)?.StylingParent as IResourceNode;
current = (current as IStyleHost)?.StylingParent as IResourceHost;
}
value = null;
return false;
}
/// <inheritdoc cref="IResourceNode.TryGetResource" />
public static bool TryGetResource(this IResourceHost control, object key, out object? value)
{
control = control ?? throw new ArgumentNullException(nameof(control));
key = key ?? throw new ArgumentNullException(nameof(key));
return control.TryGetResource(key, null, out value);
}
public static IObservable<object?> GetResourceObservable(
this IResourceHost control,
@ -95,24 +138,49 @@ namespace Avalonia.Controls
protected override void Initialize()
{
_target.ResourcesChanged += ResourcesChanged;
if (_target is StyledElement themeStyleable)
{
themeStyleable.PropertyChanged += PropertyChanged;
}
}
protected override void Deinitialize()
{
_target.ResourcesChanged -= ResourcesChanged;
if (_target is StyledElement themeStyleable)
{
themeStyleable.PropertyChanged -= PropertyChanged;
}
}
protected override void Subscribed(IObserver<object?> observer, bool first)
{
observer.OnNext(Convert(_target.FindResource(_key)));
observer.OnNext(GetValue());
}
private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e)
{
PublishNext(Convert(_target.FindResource(_key)));
PublishNext(GetValue());
}
private void PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == StyledElement.ActualThemeVariantProperty)
{
PublishNext(GetValue());
}
}
private object? Convert(object? value) => _converter?.Invoke(value) ?? value;
private object? GetValue()
{
if (_target is not StyledElement themeStyleable
|| !_target.TryFindResource(_key, themeStyleable.ActualThemeVariant, out var value))
{
value = _target.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
}
return _converter?.Invoke(value) ?? value;
}
}
private class FloatingResourceObservable : LightweightObservableBase<object?>
@ -134,7 +202,7 @@ namespace Avalonia.Controls
_target.OwnerChanged += OwnerChanged;
_owner = _target.Owner;
if (_owner is object)
if (_owner is not null)
{
_owner.ResourcesChanged += ResourcesChanged;
}
@ -148,43 +216,68 @@ namespace Avalonia.Controls
protected override void Subscribed(IObserver<object?> observer, bool first)
{
if (_target.Owner is object)
if (_target.Owner is not null)
{
observer.OnNext(Convert(_target.Owner.FindResource(_key)));
observer.OnNext(GetValue());
}
}
private void PublishNext()
{
if (_target.Owner is object)
if (_target.Owner is not null)
{
PublishNext(Convert(_target.Owner.FindResource(_key)));
PublishNext(GetValue());
}
}
private void OwnerChanged(object? sender, EventArgs e)
{
if (_owner is object)
if (_owner is not null)
{
_owner.ResourcesChanged -= ResourcesChanged;
}
if (_owner is StyledElement styleable)
{
styleable.PropertyChanged += PropertyChanged;
}
_owner = _target.Owner;
if (_owner is object)
if (_owner is not null)
{
_owner.ResourcesChanged += ResourcesChanged;
}
if (_owner is StyledElement styleable2)
{
styleable2.PropertyChanged += PropertyChanged;
}
PublishNext();
}
private void PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == StyledElement.ActualThemeVariantProperty)
{
PublishNext();
}
}
private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e)
{
PublishNext();
}
private object? Convert(object? value) => _converter?.Invoke(value) ?? value;
private object? GetValue()
{
if (!(_target.Owner is StyledElement themeStyleable)
|| !_target.Owner.TryFindResource(_key, themeStyleable.ActualThemeVariant, out var value))
{
value = _target.Owner?.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
}
return _converter?.Invoke(value) ?? value;
}
}
}
}

2
src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@ -30,7 +30,7 @@ namespace Avalonia.Data.Converters
{
if (value == null)
{
return targetType.IsValueType ? AvaloniaProperty.UnsetValue : null;
return null;
}
if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)

4
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@ -22,7 +22,7 @@ namespace Avalonia.Data.Core
if (target is INotifyPropertyChanged inpc)
{
WeakEvents.PropertyChanged.Subscribe(inpc, this);
WeakEvents.ThreadSafePropertyChanged.Subscribe(inpc, this);
}
ValueChanged(GetValue(target));
@ -39,7 +39,7 @@ namespace Avalonia.Data.Core
if (target is INotifyPropertyChanged inpc)
{
WeakEvents.PropertyChanged.Unsubscribe(inpc, this);
WeakEvents.ThreadSafePropertyChanged.Unsubscribe(inpc, this);
}
}
}

4
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -160,7 +160,7 @@ namespace Avalonia.Data.Core.Plugins
var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null)
WeakEvents.PropertyChanged.Unsubscribe(inpc, this);
WeakEvents.ThreadSafePropertyChanged.Unsubscribe(inpc, this);
}
private object? GetReferenceTarget()
@ -185,7 +185,7 @@ namespace Avalonia.Data.Core.Plugins
var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null)
WeakEvents.PropertyChanged.Subscribe(inpc, this);
WeakEvents.ThreadSafePropertyChanged.Subscribe(inpc, this);
}
}
}

2
src/Avalonia.Base/Data/InstancedBinding.cs

@ -23,7 +23,7 @@ namespace Avalonia.Data
/// <param name="priority">The priority of the binding.</param>
/// <remarks>
/// This constructor can be used to create any type of binding and as such requires an
/// <see cref="ISubject{Object}"/> as the binding source because this is the only binding
/// <see cref="IObservable{Object}"/> as the binding source because this is the only binding
/// source which can be used for all binding modes. If you wish to create an instance with
/// something other than a subject, use one of the static creation methods on this class.
/// </remarks>

3
src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs

@ -1,6 +1,3 @@
using System;
using Avalonia.Data;
namespace Avalonia.Diagnostics
{
/// <summary>

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

Loading…
Cancel
Save