Browse Source

Merge branch 'master' into feat/drm-init-size

pull/9119/head
Markus Wenzl 4 years ago
committed by GitHub
parent
commit
d0ec9173bc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      .editorconfig
  2. 1
      .github/FUNDING.yml
  3. 40
      Avalonia.sln
  4. 4
      NOTICE.md
  5. 15
      azure-pipelines-integrationtests.yml
  6. 12
      azure-pipelines.yml
  7. 5
      build/NetAnalyzers.props
  8. 2
      build/SharedVersion.props
  9. 13
      dirs.proj
  10. 4
      global.json
  11. 4
      native/Avalonia.Native/src/OSX/Screens.mm
  12. 3
      packages/Avalonia/AvaloniaBuildTasks.targets
  13. 12
      samples/ControlCatalog.Android/MainActivity.cs
  14. 13
      samples/ControlCatalog.Android/SplashActivity.cs
  15. 0
      samples/ControlCatalog.Blazor.Web/App.razor
  16. 17
      samples/ControlCatalog.Blazor.Web/App.razor.cs
  17. 29
      samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj
  18. 0
      samples/ControlCatalog.Blazor.Web/Pages/Index.razor
  19. 29
      samples/ControlCatalog.Blazor.Web/Program.cs
  20. 0
      samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json
  21. 0
      samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor
  22. 3
      samples/ControlCatalog.Blazor.Web/_Imports.razor
  23. 0
      samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css
  24. 0
      samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico
  25. 0
      samples/ControlCatalog.Blazor.Web/wwwroot/index.html
  26. 1
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  27. 20
      samples/ControlCatalog.Web/App.razor.cs
  28. 69
      samples/ControlCatalog.Web/ControlCatalog.Web.csproj
  29. 32
      samples/ControlCatalog.Web/EmbedSample.Browser.cs
  30. 0
      samples/ControlCatalog.Web/Logo.svg
  31. 37
      samples/ControlCatalog.Web/Program.cs
  32. 7
      samples/ControlCatalog.Web/Roots.xml
  33. 23
      samples/ControlCatalog.Web/app.css
  34. 0
      samples/ControlCatalog.Web/embed.js
  35. 0
      samples/ControlCatalog.Web/favicon.ico
  36. 2
      samples/ControlCatalog.Web/index.html
  37. 4
      samples/ControlCatalog.Web/main.js
  38. 0
      samples/ControlCatalog.Web/runtimeconfig.template.json
  39. 8
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  40. 16
      samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs
  41. 24
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml
  42. 17
      samples/ControlCatalog/Pages/ExpanderPage.xaml
  43. 4
      samples/ControlCatalog/Pages/ScreenPage.cs
  44. 4
      samples/MobileSandbox.Android/MainActivity.cs
  45. 4
      samples/MobileSandbox.Android/SplashActivity.cs
  46. 4
      samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
  47. 105
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  48. 70
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  49. 34
      src/Android/Avalonia.Android/AvaloniaSplashActivity.cs
  50. 3
      src/Android/Avalonia.Android/AvaloniaView.cs
  51. 11
      src/Android/Avalonia.Android/AvaloniaViewModel.cs
  52. 11
      src/Android/Avalonia.Android/IActivityResultHandler.cs
  53. 6
      src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
  54. 13
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  55. 6
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
  56. 15
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
  57. 26
      src/Android/Avalonia.Android/SingleViewLifetime.cs
  58. 7
      src/Avalonia.Base/Avalonia.Base.csproj
  59. 2
      src/Avalonia.Base/AvaloniaProperty.cs
  60. 2
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  61. 2
      src/Avalonia.Base/Input/Cursor.cs
  62. 2
      src/Avalonia.Base/Input/DragDrop.cs
  63. 8
      src/Avalonia.Base/Input/DragDropDevice.cs
  64. 49
      src/Avalonia.Base/Media/Color.cs
  65. 12
      src/Avalonia.Base/Media/FontManager.cs
  66. 58
      src/Avalonia.Base/Media/FontMetrics.cs
  67. 27
      src/Avalonia.Base/Media/FontSimulations.cs
  68. 24
      src/Avalonia.Base/Media/GlyphMetrics.cs
  69. 95
      src/Avalonia.Base/Media/GlyphRun.cs
  70. 125
      src/Avalonia.Base/Media/GlyphTypeface.cs
  71. 30
      src/Avalonia.Base/Media/HslColor.cs
  72. 30
      src/Avalonia.Base/Media/HsvColor.cs
  73. 73
      src/Avalonia.Base/Media/IGlyphTypeface.cs
  74. 4
      src/Avalonia.Base/Media/PathMarkupParser.cs
  75. 2
      src/Avalonia.Base/Media/PreciseEllipticArcHelper.cs
  76. 16
      src/Avalonia.Base/Media/TextDecoration.cs
  77. 6
      src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
  78. 9
      src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs
  79. 29
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  80. 26
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  81. 24
      src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs
  82. 4
      src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs
  83. 2
      src/Avalonia.Base/Media/TextFormatting/Unicode/BinaryReaderExtensions.cs
  84. 7
      src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
  85. 2
      src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs
  86. 2
      src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs
  87. 2
      src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrie.cs
  88. 2
      src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.Constants.cs
  89. 2
      src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.cs
  90. 2
      src/Avalonia.Base/Media/Typeface.cs
  91. 2
      src/Avalonia.Base/Media/UnicodeRange.cs
  92. 2
      src/Avalonia.Base/Platform/IFontManagerImpl.cs
  93. 22
      src/Avalonia.Base/Platform/IGlyphRunBuffer.cs
  94. 37
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  95. 2
      src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
  96. 2
      src/Avalonia.Base/Utilities/BinarySearchExtension.cs
  97. 2
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  98. 2
      src/Avalonia.Base/Utilities/MathUtilities.cs
  99. 2
      src/Avalonia.Base/Utilities/ReadOnlySlice.cs
  100. 49
      src/Avalonia.Base/Utilities/SpanHelpers.cs

14
.editorconfig

@ -134,6 +134,20 @@ csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false csharp_space_between_square_brackets = false
space_within_single_line_array_initializer_braces = true space_within_single_line_array_initializer_braces = true
#Net Analyzer
dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed.
# CA1802: Use literals where appropriate
dotnet_diagnostic.CA1802.severity = warning
# CA1820: Test for empty strings using string length
dotnet_diagnostic.CA1820.severity = warning
# CA1821: Remove empty finalizers
dotnet_diagnostic.CA1821.severity = warning
# CA1825: Avoid zero-length array allocations
dotnet_diagnostic.CA1825.severity = warning
#CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
dotnet_diagnostic.CA1847.severity = warning
# Wrapping preferences # Wrapping preferences
csharp_wrap_before_ternary_opsigns = false csharp_wrap_before_ternary_opsigns = false

1
.github/FUNDING.yml

@ -1 +1,2 @@
github: avaloniaui
open_collective: avalonia open_collective: avalonia

40
Avalonia.sln

@ -41,6 +41,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DE
src\Shared\IsExternalInit.cs = src\Shared\IsExternalInit.cs src\Shared\IsExternalInit.cs = src\Shared\IsExternalInit.cs
src\Shared\ModuleInitializer.cs = src\Shared\ModuleInitializer.cs src\Shared\ModuleInitializer.cs = src\Shared\ModuleInitializer.cs
src\Shared\SourceGeneratorAttributes.cs = src\Shared\SourceGeneratorAttributes.cs src\Shared\SourceGeneratorAttributes.cs = src\Shared\SourceGeneratorAttributes.cs
src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs = src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs
EndProjectSection EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI", "src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI", "src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}"
@ -90,6 +91,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
build\ApiDiff.props = build\ApiDiff.props build\ApiDiff.props = build\ApiDiff.props
build\AvaloniaPublicKey.props = build\AvaloniaPublicKey.props
build\Base.props = build\Base.props build\Base.props = build\Base.props
build\Binding.props = build\Binding.props build\Binding.props = build\Binding.props
build\CoreLibraries.props = build\CoreLibraries.props build\CoreLibraries.props = build\CoreLibraries.props
@ -102,8 +104,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\Microsoft.CSharp.props = build\Microsoft.CSharp.props build\Microsoft.CSharp.props = build\Microsoft.CSharp.props
build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
build\Moq.props = build\Moq.props build\Moq.props = build\Moq.props
build\NetAnalyzers.props = build\NetAnalyzers.props
build\NetCore.props = build\NetCore.props build\NetCore.props = build\NetCore.props
build\NetFX.props = build\NetFX.props build\NetFX.props = build\NetFX.props
build\NullableEnable.props = build\NullableEnable.props
build\ReactiveUI.props = build\ReactiveUI.props build\ReactiveUI.props = build\ReactiveUI.props
build\ReferenceCoreLibraries.props = build\ReferenceCoreLibraries.props build\ReferenceCoreLibraries.props = build\ReferenceCoreLibraries.props
build\Rx.props = build\Rx.props build\Rx.props = build\Rx.props
@ -198,8 +202,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{86A3F706-DC3
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{C08E9894-AA92-426E-BF56-033E262CAD3E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}"
@ -216,15 +218,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevGenerators", "src\tools\
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Avalonia.Web\Avalonia.Web.csproj", "{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Avalonia.Web\Avalonia.Web.csproj", "{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Sample", "src\Web\Avalonia.Web.Sample\Avalonia.Web.Sample.csproj", "{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.iOS", "samples\MobileSandbox.iOS\MobileSandbox.iOS.csproj", "{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.iOS", "samples\MobileSandbox.iOS\MobileSandbox.iOS.csproj", "{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Blazor.Web", "samples\ControlCatalog.Blazor.Web\ControlCatalog.Blazor.Web.csproj", "{6A710364-AE6D-40BD-968B-024311527AC2}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{8B3E8405-DE18-4048-A459-9CA4AC3319A2}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -480,10 +484,6 @@ Global
{25831348-EB2A-483E-9576-E8F6528674A5}.Debug|Any CPU.Build.0 = Debug|Any CPU {25831348-EB2A-483E-9576-E8F6528674A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.Build.0 = Release|Any CPU {25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.Build.0 = Release|Any CPU
{C08E9894-AA92-426E-BF56-033E262CAD3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C08E9894-AA92-426E-BF56-033E262CAD3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C08E9894-AA92-426E-BF56-033E262CAD3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C08E9894-AA92-426E-BF56-033E262CAD3E}.Release|Any CPU.Build.0 = Release|Any CPU
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -516,10 +516,6 @@ Global
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.Build.0 = Debug|Any CPU {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.ActiveCfg = Release|Any CPU {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.Build.0 = Release|Any CPU {76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.Build.0 = Release|Any CPU
{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Release|Any CPU.Build.0 = Release|Any CPU
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.Build.0 = Debug|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.ActiveCfg = Release|Any CPU {3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -536,6 +532,14 @@ Global
{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.ActiveCfg = Release|Any CPU {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.Build.0 = Release|Any CPU {62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.Build.0 = Release|Any CPU
{6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.Build.0 = Release|Any CPU
{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -579,6 +583,7 @@ Global
{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098} {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@ -586,7 +591,6 @@ Global
{676D6BFD-029D-4E43-BFC7-3892265CE251} = {9B9E3891-2366-4253-A952-D08BCEB71098} {676D6BFD-029D-4E43-BFC7-3892265CE251} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098} {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@ -594,12 +598,12 @@ Global
{EABE2161-989B-42BF-BD8D-1E34B20C21F1} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {EABE2161-989B-42BF-BD8D-1E34B20C21F1} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {76D39FF6-6B4F-46C4-93CD-E6FC4665739E} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{3B8519C1-2F51-4F12-A348-120AB91D4532} = {9B9E3891-2366-4253-A952-D08BCEB71098} {3B8519C1-2F51-4F12-A348-120AB91D4532} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB} = {9B9E3891-2366-4253-A952-D08BCEB71098} {FED9A71D-00D7-4F40-A9E4-1229EEA28EEB} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{62D392C9-81CF-487F-92E8-598B2AF3FDCE} = {9B9E3891-2366-4253-A952-D08BCEB71098} {62D392C9-81CF-487F-92E8-598B2AF3FDCE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{6A710364-AE6D-40BD-968B-024311527AC2} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{8B3E8405-DE18-4048-A459-9CA4AC3319A2} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

4
NOTICE.md

@ -111,7 +111,7 @@ DEALINGS IN THE SOFTWARE.
# Metsys.Bson # Metsys.Bson
Copyright (c) 2010, Karl Seguin - http://www.openmymind.net/ Copyright (c) 2010, Karl Seguin - https://www.openmymind.net/
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@ -302,4 +302,4 @@ https://github.com/chromium/chromium
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

15
azure-pipelines-integrationtests.yml

@ -12,6 +12,16 @@ jobs:
name: 'AvaloniaMacPool' name: 'AvaloniaMacPool'
steps: steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.401'
inputs:
version: 6.0.401
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
inputs:
version: 7.0.100-rc.2.22477.23
- script: system_profiler SPDisplaysDataType |grep Resolution - script: system_profiler SPDisplaysDataType |grep Resolution
- script: | - script: |
@ -45,6 +55,11 @@ jobs:
inputs: inputs:
version: 6.0.401 version: 6.0.401
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
inputs:
version: 7.0.100-rc.2.22477.23
- task: Windows Application Driver@0 - task: Windows Application Driver@0
inputs: inputs:
OperationType: 'Start' OperationType: 'Start'

12
azure-pipelines.yml

@ -35,9 +35,9 @@ jobs:
version: 6.0.401 version: 6.0.401
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12' displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
inputs: inputs:
version: 7.0.100-rc.1.22431.12 version: 7.0.100-rc.2.22477.23
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Workloads' displayName: 'Install Workloads'
@ -72,9 +72,9 @@ jobs:
version: 6.0.401 version: 6.0.401
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12' displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
inputs: inputs:
version: 7.0.100-rc.1.22431.12 version: 7.0.100-rc.2.22477.23
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Workloads' displayName: 'Install Workloads'
@ -143,9 +143,9 @@ jobs:
version: 6.0.401 version: 6.0.401
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12' displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
inputs: inputs:
version: 7.0.100-rc.1.22431.12 version: 7.0.100-rc.2.22477.23
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Workloads' displayName: 'Install Workloads'

5
build/NetAnalyzers.props

@ -0,0 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>
</Project>

2
build/SharedVersion.props

@ -8,7 +8,7 @@
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl> <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>CS1591</NoWarn> <NoWarn>CS1591</NoWarn>
<LangVersion>latest</LangVersion> <LangVersion>preview</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Icon.png</PackageIcon> <PackageIcon>Icon.png</PackageIcon>
<PackageDescription>Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.</PackageDescription> <PackageDescription>Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.</PackageDescription>

13
dirs.proj

@ -9,21 +9,18 @@
<ProjectReference Remove="**/*.shproj" /> <ProjectReference Remove="**/*.shproj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" /> <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/**/*.*proj" /> <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/**/*.*proj" />
<ProjectReference Remove="tests/Avalonia.ReactiveUI.Events.UnitTests/Avalonia.ReactiveUI.Events.UnitTests.csproj" /> <!-- Exclude iOS, Android and Web samples from build -->
<ProjectReference Remove="samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj" /> <ProjectReference Remove="samples/*.iOS/*.csproj" />
<ProjectReference Remove="samples/MobileSandbox.iOS/MobileSandbox.iOS.csproj" /> <ProjectReference Remove="samples/*.Android/*.csproj" />
<ProjectReference Remove="samples/ControlCatalog.iOS.Legacy/ControlCatalog.iOS.Legacy.csproj" /> <ProjectReference Remove="samples/*.Web/*.csproj" />
<ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
<ProjectReference Remove="samples/MobileSandbox.Android/MobileSandbox.Android.csproj" />
<ProjectReference Remove="src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows')) OR '$(MSBuildRuntimeType)' != 'Full'"> <ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows')) OR '$(MSBuildRuntimeType)' != 'Full'">
<ProjectReference Remove="src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj" /> <ProjectReference Remove="src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj" />
<ProjectReference Remove="samples/interop/**/*.*proj" /> <ProjectReference Remove="samples/interop/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.Desktop/*.*proj" /> <ProjectReference Remove="samples/ControlCatalog.Desktop/*.*proj" />
</ItemGroup> </ItemGroup>
<!-- Build android and iOS projects only on Windows, where we have installed android workload -->
<!-- Build android and iOS projects only on Windows, where we have installed android workload -->
<ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows'))"> <ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows'))">
<ProjectReference Remove="src/Android/**/*.*proj" /> <ProjectReference Remove="src/Android/**/*.*proj" />
<ProjectReference Remove="src/iOS/**/*.*proj" /> <ProjectReference Remove="src/iOS/**/*.*proj" />

4
global.json

@ -1,4 +1,8 @@
{ {
"sdk": {
"version": "7.0.100-rc.2.22477.23",
"rollForward": "latestFeature"
},
"msbuild-sdks": { "msbuild-sdks": {
"Microsoft.Build.Traversal": "1.0.43", "Microsoft.Build.Traversal": "1.0.43",
"MSBuild.Sdk.Extras": "3.0.22", "MSBuild.Sdk.Extras": "3.0.22",

4
native/Avalonia.Native/src/OSX/Screens.mm

@ -41,9 +41,9 @@ public:
ret->WorkingArea.X = [screen visibleFrame].origin.x; ret->WorkingArea.X = [screen visibleFrame].origin.x;
ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height; ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height;
ret->PixelDensity = [screen backingScaleFactor]; ret->Scaling = [screen backingScaleFactor];
ret->Primary = index == 0; ret->IsPrimary = index == 0;
return S_OK; return S_OK;
} }

3
packages/Avalonia/AvaloniaBuildTasks.targets

@ -3,7 +3,6 @@
<_AvaloniaUseExternalMSBuild>$(AvaloniaUseExternalMSBuild)</_AvaloniaUseExternalMSBuild> <_AvaloniaUseExternalMSBuild>$(AvaloniaUseExternalMSBuild)</_AvaloniaUseExternalMSBuild>
<_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild> <_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild>
<AvaloniaXamlReportImportance Condition="'$(AvaloniaXamlReportImportance)' == ''">low</AvaloniaXamlReportImportance> <AvaloniaXamlReportImportance Condition="'$(AvaloniaXamlReportImportance)' == ''">low</AvaloniaXamlReportImportance>
<_AvaloniaPatchComInterop Condition="'$(_AvaloniaPatchComInterop)' == ''">false</_AvaloniaPatchComInterop>
<_AvaloniaSkipXamlCompilation Condition="'$(_AvaloniaSkipXamlCompilation)' == ''">false</_AvaloniaSkipXamlCompilation> <_AvaloniaSkipXamlCompilation Condition="'$(_AvaloniaSkipXamlCompilation)' == ''">false</_AvaloniaSkipXamlCompilation>
</PropertyGroup> </PropertyGroup>
@ -71,7 +70,6 @@
Output="$(AvaloniaResourcesTemporaryFilePath)" Output="$(AvaloniaResourcesTemporaryFilePath)"
Root="$(MSBuildProjectDirectory)" Root="$(MSBuildProjectDirectory)"
Resources="@(AvaloniaResource)" Resources="@(AvaloniaResource)"
EmbeddedResources="@(EmbeddedResources)"
ReportImportance="$(AvaloniaXamlReportImportance)"/> ReportImportance="$(AvaloniaXamlReportImportance)"/>
<Exec <Exec
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'" Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
@ -106,7 +104,6 @@
AssemblyOriginatorKeyFile="$(AssemblyOriginatorKeyFile)" AssemblyOriginatorKeyFile="$(AssemblyOriginatorKeyFile)"
SignAssembly="$(SignAssembly)" SignAssembly="$(SignAssembly)"
DelaySign="$(DelaySign)" DelaySign="$(DelaySign)"
EnableComInteropPatching="$(_AvaloniaPatchComInterop)"
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)" SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)" DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
/> />

12
samples/ControlCatalog.Android/MainActivity.cs

@ -5,16 +5,8 @@ using Avalonia.Android;
namespace ControlCatalog.Android namespace ControlCatalog.Android
{ {
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
public class MainActivity : AvaloniaActivity<App> public class MainActivity : AvaloniaMainActivity
{ {
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
});
}
} }
} }

13
samples/ControlCatalog.Android/SplashActivity.cs

@ -1,12 +1,23 @@
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Content.PM;
using Android.OS; using Android.OS;
using Avalonia.Android;
namespace ControlCatalog.Android namespace ControlCatalog.Android
{ {
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : Activity public class SplashActivity : AvaloniaSplashActivity<App>
{ {
protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
});
}
protected override void OnCreate(Bundle? savedInstanceState) protected override void OnCreate(Bundle? savedInstanceState)
{ {
base.OnCreate(savedInstanceState); base.OnCreate(savedInstanceState);

0
samples/ControlCatalog.Web/App.razor → samples/ControlCatalog.Blazor.Web/App.razor

17
samples/ControlCatalog.Blazor.Web/App.razor.cs

@ -0,0 +1,17 @@
using Avalonia;
using Avalonia.Web.Blazor;
namespace ControlCatalog.Blazor.Web;
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();
}
}

29
samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<Nullable>enable</Nullable>
<EmccTotalMemory>16777216</EmccTotalMemory>
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
<BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
</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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup>
<Import Project="..\..\build\ReferenceCoreLibraries.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.props" />
<Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.targets" />
</Project>

0
samples/ControlCatalog.Web/Pages/Index.razor → samples/ControlCatalog.Blazor.Web/Pages/Index.razor

29
samples/ControlCatalog.Blazor.Web/Program.cs

@ -0,0 +1,29 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using ControlCatalog.Blazor.Web;
public class Program
{
public static async Task Main(string[] args)
{
await CreateHostBuilder(args).Build().RunAsync();
}
public static WebAssemblyHostBuilder CreateHostBuilder(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
return builder;
}
}

0
samples/ControlCatalog.Web/Properties/launchSettings.json → samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json

0
samples/ControlCatalog.Web/Shared/MainLayout.razor → samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor

3
samples/ControlCatalog.Web/_Imports.razor → samples/ControlCatalog.Blazor.Web/_Imports.razor

@ -6,6 +6,5 @@
@using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using ControlCatalog.Web @using ControlCatalog.Blazor.Web.Shared
@using ControlCatalog.Web.Shared
@using SkiaSharp @using SkiaSharp

0
samples/ControlCatalog.Web/wwwroot/css/app.css → samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css

0
samples/ControlCatalog.Web/wwwroot/favicon.ico → samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

0
samples/ControlCatalog.Web/wwwroot/index.html → samples/ControlCatalog.Blazor.Web/wwwroot/index.html

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

@ -5,6 +5,7 @@
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RuntimeFrameworkVersion>6.0.9</RuntimeFrameworkVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(RunNativeAotCompilation)' == 'true'"> <PropertyGroup Condition="'$(RunNativeAotCompilation)' == 'true'">

20
samples/ControlCatalog.Web/App.razor.cs

@ -1,20 +0,0 @@
using Avalonia;
using Avalonia.Web.Blazor;
namespace ControlCatalog.Web;
public partial class App
{
protected override void OnParametersSet()
{
WebAppBuilder.Configure<ControlCatalog.App>()
.AfterSetup(_ =>
{
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
})
.With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
.SetupWithSingleViewLifetime();
base.OnParametersSet();
}
}

69
samples/ControlCatalog.Web/ControlCatalog.Web.csproj

@ -1,57 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable> <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<!--Temporal hack that fixes compilation in VS--> <WasmMainJSPath>main.js</WasmMainJSPath>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <OutputType>Exe</OutputType>
<EmccTotalMemory>16777216</EmccTotalMemory> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport> <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
<BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData> <WasmBuildNative>true</WasmBuildNative>
<EmccFlags>-sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0</EmccFlags>
</PropertyGroup> </PropertyGroup>
<!-- In debug, make builds faster by reducing optimizations --> <PropertyGroup Condition="'$(Configuration)'=='Release'">
<PropertyGroup Condition="'$(Configuration)' == 'Debug'"> <RunAOTCompilation>true</RunAOTCompilation>
<WasmNativeStrip>false</WasmNativeStrip>
<EmccCompileOptimizationFlag>-O1</EmccCompileOptimizationFlag>
<RunAOTCompilation>false</RunAOTCompilation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<Optimize>true</Optimize>
<WasmNativeStrip>true</WasmNativeStrip>
<EmccCompileOptimizationFlag>-O3</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-O3</EmccLinkOptimizationFlag>
<RunAOTCompilation>false</RunAOTCompilation>
<DebuggerSupport>false</DebuggerSupport>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
<EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<InvariantGlobalization>true</InvariantGlobalization>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
<UseNativeHttpHandler>true</UseNativeHttpHandler>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<PublishTrimmed>true</PublishTrimmed> <PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode> <TrimMode>full</TrimMode>
<TrimmerRemoveSymbols>true</TrimmerRemoveSymbols> <WasmBuildNative>true</WasmBuildNative>
<InvariantGlobalization>true</InvariantGlobalization>
<EmccCompileOptimizationFlag>-O2</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-O2</EmccLinkOptimizationFlag>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0" /> <TrimmerRootDescriptor Include="Roots.xml" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> <ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj" /> <ProjectReference Include="..\..\src\Web\Avalonia.Web\Avalonia.Web.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" /> <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\ReferenceCoreLibraries.props" /> <ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" /> <WasmExtraFilesToDeploy Include="index.html" />
<WasmExtraFilesToDeploy Include="main.js" />
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.targets" /> <WasmExtraFilesToDeploy Include="embed.js" />
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.CompilationTuning.props" /> <WasmExtraFilesToDeploy Include="favicon.ico" />
<WasmExtraFilesToDeploy Include="Logo.svg" />
<WasmExtraFilesToDeploy Include="app.css" />
</ItemGroup>
<Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.props" />
<Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.targets" />
</Project> </Project>

32
samples/ControlCatalog.Web/EmbedSample.Browser.cs

@ -1,34 +1,42 @@
using System; using System;
using System.Runtime.InteropServices.JavaScript;
using Avalonia;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Web.Blazor; using Avalonia.Web;
using ControlCatalog.Pages; using ControlCatalog.Pages;
using Microsoft.JSInterop;
namespace ControlCatalog.Web; namespace ControlCatalog.Web;
public class EmbedSampleWeb : INativeDemoControl public class EmbedSampleWeb : INativeDemoControl
{ {
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault) public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{ {
var runtime = AvaloniaLocator.Current.GetRequiredService<IJSInProcessRuntime>();
if (isSecond) if (isSecond)
{ {
var iframe = runtime.Invoke<IJSInProcessObjectReference>("document.createElement", "iframe"); var iframe = EmbedInterop.CreateElement("iframe");
iframe.InvokeVoid("setAttribute", "src", "https://www.youtube.com/embed/kZCIporjJ70"); iframe.SetProperty("src", "https://www.youtube.com/embed/kZCIporjJ70");
return new JSObjectControlHandle(iframe); return new JSObjectControlHandle(iframe);
} }
else else
{ {
// window.createAppButton source is defined in "app.js" file. var defaultHandle = (JSObjectControlHandle)createDefault();
var button = runtime.Invoke<IJSInProcessObjectReference>("window.createAppButton");
_ = JSHost.ImportAsync("embed.js", "./embed.js").ContinueWith(_ =>
{
EmbedInterop.AddAppButton(defaultHandle.Object);
});
return new JSObjectControlHandle(button); return defaultHandle;
} }
} }
} }
internal static partial class EmbedInterop
{
[JSImport("globalThis.document.createElement")]
public static partial JSObject CreateElement(string tagName);
[JSImport("addAppButton", "embed.js")]
public static partial void AddAppButton(JSObject parentObject);
}

0
src/Web/Avalonia.Web.Sample/Logo.svg → samples/ControlCatalog.Web/Logo.svg

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

37
samples/ControlCatalog.Web/Program.cs

@ -1,29 +1,22 @@
using System; using System.Runtime.Versioning;
using System.Net.Http; using Avalonia;
using System.Threading.Tasks; using Avalonia.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using ControlCatalog;
using Microsoft.Extensions.DependencyInjection;
using ControlCatalog.Web; using ControlCatalog.Web;
public class Program [assembly:SupportedOSPlatform("browser")]
internal partial class Program
{ {
public static async Task Main(string[] args) private static void Main(string[] args)
{ {
await CreateHostBuilder(args).Build().RunAsync(); BuildAvaloniaApp()
.AfterSetup(_ =>
{
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
}).SetupBrowserApp("out");
} }
public static WebAssemblyHostBuilder CreateHostBuilder(string[] args) public static AppBuilder BuildAvaloniaApp()
{ => AppBuilder.Configure<App>();
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
return builder;
}
} }

7
samples/ControlCatalog.Web/Roots.xml

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

23
src/Web/Avalonia.Web.Sample/app.css → samples/ControlCatalog.Web/app.css

@ -4,12 +4,15 @@
} }
#avalonia-splash { #avalonia-splash {
position: absolute; position: relative;
height: 100%; height: 100%;
width: 100%; width: 100%;
color: whitesmoke; color: whitesmoke;
background: #171C2C; background: #171C2C;
font-family: 'Nunito', sans-serif; font-family: 'Nunito', sans-serif;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
} }
#avalonia-splash a{ #avalonia-splash a{
@ -24,15 +27,23 @@
} }
.splash-close { .splash-close {
animation: fadeOut 1s forwards; animation: slide 0.5s linear 1s forwards;
} }
@keyframes fadeOut { @keyframes slide {
from { 0% {
opacity: 1; top: 0%;
} }
to { 50% {
opacity: 80%;
}
100% {
top: 100%;
overflow: hidden;
opacity: 0; opacity: 0;
display: none;
visibility: collapse;
} }
} }

0
src/Web/Avalonia.Web.Sample/embed.js → samples/ControlCatalog.Web/embed.js

0
src/Web/Avalonia.Web.Sample/favicon.ico → samples/ControlCatalog.Web/favicon.ico

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

2
src/Web/Avalonia.Web.Sample/index.html → samples/ControlCatalog.Web/index.html

@ -4,7 +4,7 @@
<html> <html>
<head> <head>
<title>Avalonia.Web.Sample</title> <title>AvaloniaUI - ControlCatalog</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="modulepreload" href="./main.js" /> <link rel="modulepreload" href="./main.js" />

4
src/Web/Avalonia.Web.Sample/main.js → samples/ControlCatalog.Web/main.js

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

0
src/Web/Avalonia.Web.Sample/runtimeconfig.template.json → samples/ControlCatalog.Web/runtimeconfig.template.json

8
samples/ControlCatalog/Pages/ColorPickerPage.xaml

@ -11,16 +11,17 @@
x:Class="ControlCatalog.Pages.ColorPickerPage"> x:Class="ControlCatalog.Pages.ColorPickerPage">
<UserControl.Resources> <UserControl.Resources>
<pc:ThirdComponentConverter x:Key="ThirdComponent" />
</UserControl.Resources> </UserControl.Resources>
<Grid ColumnDefinitions="Auto,10,Auto,10,Auto" <Grid x:Name="LayoutRoot"
ColumnDefinitions="Auto,10,Auto"
RowDefinitions="Auto,Auto"> RowDefinitions="Auto,Auto">
<ColorView Grid.Column="0" <ColorView Grid.Column="0"
Grid.Row="0" Grid.Row="0"
ColorSpectrumShape="Ring" /> ColorSpectrumShape="Ring" />
<ColorPicker Grid.Column="0" <ColorPicker Grid.Column="0"
Grid.Row="1" Grid.Row="1"
HsvColor="hsv(120, 1, 1)"
Margin="0,50,0,0"> Margin="0,50,0,0">
<ColorPicker.Palette> <ColorPicker.Palette>
<controls:FlatColorPalette /> <controls:FlatColorPalette />
@ -56,8 +57,5 @@
IsAccentColorsVisible="False" IsAccentColorsVisible="False"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" /> HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
</Grid> </Grid>
<Grid Grid.Column="4"
Grid.Row="0">
</Grid>
</Grid> </Grid>
</UserControl> </UserControl>

16
samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs

@ -1,6 +1,8 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media;
namespace ControlCatalog.Pages namespace ControlCatalog.Pages
{ {
@ -9,6 +11,20 @@ namespace ControlCatalog.Pages
public ColorPickerPage() public ColorPickerPage()
{ {
InitializeComponent(); InitializeComponent();
var layoutRoot = this.GetControl<Grid>("LayoutRoot");
// ColorPicker added from code-behind
var colorPicker = new ColorPicker()
{
Color = Colors.Blue,
Margin = new Thickness(0, 50, 0, 0),
HorizontalAlignment = HorizontalAlignment.Center,
};
Grid.SetColumn(colorPicker, 2);
Grid.SetRow(colorPicker, 1);
layoutRoot.Children.Add(colorPicker);
} }
private void InitializeComponent() private void InitializeComponent()

24
samples/ControlCatalog/Pages/DateTimePickerPage.xaml

@ -13,17 +13,17 @@
Margin="16" Margin="16"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Spacing="16"> Spacing="16">
<TextBlock FontSize="18">A simple DatePicker with a header</TextBlock> <TextBlock FontSize="18">A simple DatePicker</TextBlock>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<DatePicker Header="Pick a date" /> <DatePicker />
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;DatePicker Header="Pick a date" /&gt; &lt;DatePicker/&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>
@ -33,7 +33,7 @@
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<DatePicker Header="Pick a date"> <DatePicker >
<DataValidationErrors.Error> <DataValidationErrors.Error>
<sys:Exception /> <sys:Exception />
</DataValidationErrors.Error> </DataValidationErrors.Error>
@ -79,24 +79,24 @@
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker Header="Pick a time"> <TimePicker>
<DataValidationErrors.Error> <DataValidationErrors.Error>
<sys:Exception /> <sys:Exception />
</DataValidationErrors.Error> </DataValidationErrors.Error>
</TimePicker> </TimePicker>
</Border> </Border>
<TextBlock FontSize="18">A TimePicker with a header and minute increments specified.</TextBlock> <TextBlock FontSize="18">A TimePicker with minute increments specified.</TextBlock>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker Header="Arrival time" MinuteIncrement="15" /> <TimePicker MinuteIncrement="15" />
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;TimePicker Header="Arrival time" MinuteIncrement="15" /&gt; &lt;TimePicker MinuteIncrement="15" /&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>
@ -107,13 +107,13 @@
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /> <TimePicker ClockIdentifier="12HourClock"/>
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /&gt; &lt;TimePicker ClockIdentifier="12HourClock" /&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>
@ -124,13 +124,13 @@
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15"> BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /> <TimePicker ClockIdentifier="24HourClock" />
</Border> </Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15"> <TextBlock Padding="15">
<TextBlock.Text> <TextBlock.Text>
<x:String> <x:String>
&lt;TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /&gt; &lt;TimePicker ClockIdentifier="24HourClock" /&gt;
</x:String> </x:String>
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>

17
samples/ControlCatalog/Pages/ExpanderPage.xaml

@ -32,6 +32,23 @@
<TextBlock>Expanded content</TextBlock> <TextBlock>Expanded content</TextBlock>
</StackPanel> </StackPanel>
</Expander> </Expander>
<Expander ExpandDirection="Down"
CornerRadius="{Binding CornerRadius}">
<Expander.Header>
<Button Content="Control in Header" />
</Expander.Header>
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander Header="Disabled"
IsEnabled="False"
ExpandDirection="Down"
CornerRadius="{Binding CornerRadius}">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<CheckBox IsChecked="{Binding Rounded}">Rounded</CheckBox> <CheckBox IsChecked="{Binding Rounded}">Rounded</CheckBox>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

4
samples/ControlCatalog/Pages/ScreenPage.cs

@ -62,10 +62,10 @@ namespace ControlCatalog.Pages
CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"); CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20)); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20));
formattedText = CreateFormattedText($"Scaling: {screen.PixelDensity * 100}%"); formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40));
formattedText = CreateFormattedText($"Primary: {screen.Primary}"); formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60)); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60));
formattedText = formattedText =

4
samples/MobileSandbox.Android/MainActivity.cs

@ -5,8 +5,8 @@ using Avalonia.Android;
namespace MobileSandbox.Android namespace MobileSandbox.Android
{ {
[Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
public class MainActivity : AvaloniaActivity<App> public class MainActivity : AvaloniaMainActivity
{ {
} }
} }

4
samples/MobileSandbox.Android/SplashActivity.cs

@ -1,11 +1,11 @@
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS; using Avalonia.Android;
namespace MobileSandbox.Android namespace MobileSandbox.Android
{ {
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : Activity public class SplashActivity : AvaloniaSplashActivity<App>
{ {
protected override void OnResume() protected override void OnResume()
{ {

4
samples/RenderDemo/Pages/GlyphRunPage.xaml.cs

@ -22,7 +22,7 @@ namespace RenderDemo.Pages
public class GlyphRunControl : Control public class GlyphRunControl : Control
{ {
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random(); private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1]; private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1]; private char[] _characters = new char[1];
@ -81,7 +81,7 @@ namespace RenderDemo.Pages
public class GlyphRunGeometryControl : Control public class GlyphRunGeometryControl : Control
{ {
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random(); private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1]; private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1]; private char[] _characters = new char[1];

105
src/Android/Avalonia.Android/AvaloniaActivity.cs

@ -1,105 +0,0 @@
using Android.OS;
using AndroidX.AppCompat.App;
using Android.Content.Res;
using AndroidX.Lifecycle;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls;
using Android.Runtime;
using Android.App;
using Android.Content;
using System;
namespace Avalonia.Android
{
public abstract class AvaloniaActivity : AppCompatActivity
{
internal class SingleViewLifetime : ISingleViewApplicationLifetime
{
public AvaloniaView View { get; internal set; }
public Control MainView
{
get => (Control)View.Content;
set => View.Content = value;
}
}
internal Action<int, Result, Intent> ActivityResult;
internal AvaloniaView View;
internal AvaloniaViewModel _viewModel;
protected abstract AppBuilder CreateAppBuilder();
protected override void OnCreate(Bundle savedInstanceState)
{
var builder = CreateAppBuilder();
var lifetime = new SingleViewLifetime();
builder.AfterSetup(x =>
{
_viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel;
View = new AvaloniaView(this);
if (_viewModel.Content != null)
{
View.Content = _viewModel.Content;
}
SetContentView(View);
lifetime.View = View;
View.Prepare();
});
builder.SetupWithLifetime(lifetime);
base.OnCreate(savedInstanceState);
}
public object Content
{
get
{
return _viewModel.Content;
}
set
{
_viewModel.Content = value;
if (View != null)
View.Content = value;
}
}
public override void OnConfigurationChanged(Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
}
protected override void OnDestroy()
{
View.Content = null;
base.OnDestroy();
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
ActivityResult?.Invoke(requestCode, resultCode, data);
}
}
public abstract class AvaloniaActivity<TApp> : AvaloniaActivity where TApp : Application, new()
{
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
protected override AppBuilder CreateAppBuilder()
{
var builder = AppBuilder.Configure<TApp>();
return CustomizeAppBuilder(builder);
}
}
}

70
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -0,0 +1,70 @@
using System;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using AndroidX.AppCompat.App;
using AndroidX.Lifecycle;
namespace Avalonia.Android
{
public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler
{
internal static object ViewContent;
public Action<int, Result, Intent> ActivityResult { get; set; }
internal AvaloniaView View;
protected override void OnCreate(Bundle savedInstanceState)
{
View = new AvaloniaView(this);
if (ViewContent != null)
{
View.Content = ViewContent;
}
if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime)
{
lifetime.View = View;
}
base.OnCreate(savedInstanceState);
SetContentView(View);
}
public object Content
{
get
{
return ViewContent;
}
set
{
ViewContent = value;
if (View != null)
View.Content = value;
}
}
public override void OnConfigurationChanged(Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
}
protected override void OnDestroy()
{
View.Content = null;
base.OnDestroy();
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
ActivityResult?.Invoke(requestCode, resultCode, data);
}
}
}

34
src/Android/Avalonia.Android/AvaloniaSplashActivity.cs

@ -0,0 +1,34 @@
using Android.OS;
using AndroidX.AppCompat.App;
using AndroidX.Lifecycle;
namespace Avalonia.Android
{
public abstract class AvaloniaSplashActivity : AppCompatActivity
{
protected abstract AppBuilder CreateAppBuilder();
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
var builder = CreateAppBuilder();
var lifetime = new SingleViewLifetime();
builder.SetupWithLifetime(lifetime);
}
}
public abstract class AvaloniaSplashActivity<TApp> : AvaloniaSplashActivity where TApp : Application, new()
{
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
protected override AppBuilder CreateAppBuilder()
{
var builder = AppBuilder.Configure<TApp>();
return CustomizeAppBuilder(builder);
}
}
}

3
src/Android/Avalonia.Android/AvaloniaView.cs

@ -21,10 +21,7 @@ namespace Avalonia.Android
{ {
_view = new ViewImpl(this); _view = new ViewImpl(this);
AddView(_view.View); AddView(_view.View);
}
internal void Prepare ()
{
_root = new EmbeddableControlRoot(_view); _root = new EmbeddableControlRoot(_view);
_root.Prepare(); _root.Prepare();
} }

11
src/Android/Avalonia.Android/AvaloniaViewModel.cs

@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Android
{
internal class AvaloniaViewModel : AndroidX.Lifecycle.ViewModel
{
public object Content { get; set; }
}
}

11
src/Android/Avalonia.Android/IActivityResultHandler.cs

@ -0,0 +1,11 @@
using System;
using Android.App;
using Android.Content;
namespace Avalonia.Android
{
public interface IActivityResultHandler
{
public Action<int, Result, Intent> ActivityResult { get; set; }
}
}

6
src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs

@ -1,4 +1,5 @@
using Avalonia.OpenGL.Egl; using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces; using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL namespace Avalonia.Android.OpenGL
@ -19,7 +20,8 @@ namespace Avalonia.Android.OpenGL
public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info) public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info)
{ {
if (EglPlatformOpenGlInterface.TryCreate() is EglPlatformOpenGlInterface egl) var feature = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
if (feature is EglPlatformOpenGlInterface egl)
{ {
return new GlPlatformSurface(egl, info); return new GlPlatformSurface(egl, info);
} }

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

@ -1,22 +1,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Android.App;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.Media.TV;
using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Text;
using Android.Views; using Android.Views;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using Android.Widget;
using Avalonia.Android.OpenGL; using Avalonia.Android.OpenGL;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Android.Platform.Storage; using Avalonia.Android.Platform.Storage;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input; using Avalonia.Input;
@ -29,7 +23,6 @@ using Avalonia.Platform.Storage;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Java.Lang; using Java.Lang;
using static System.Net.Mime.MediaTypeNames;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
{ {
@ -59,7 +52,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
StorageProvider = new AndroidStorageProvider((AvaloniaActivity)avaloniaView.Context); StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
} }
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@ -301,7 +294,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
IsComposing = true; IsComposing = true;
_inputMethod.Client.SetPreeditText(ComposingText); _inputMethod.Client?.SetPreeditText(ComposingText);
return base.SetComposingText(text, newCursorPosition); return base.SetComposingText(text, newCursorPosition);
} }

6
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs

@ -15,7 +15,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
internal class AndroidMotionEventsHelper : IDisposable internal class AndroidMotionEventsHelper : IDisposable
{ {
private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(ClearMode.Never); private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(ClearMode.Never);
private static readonly float s_radiansToDegree = (float)(180f * Math.PI); private const float RadiansToDegree = (float)(180f * Math.PI);
private readonly TouchDevice _touchDevice; private readonly TouchDevice _touchDevice;
private readonly MouseDevice _mouseDevice; private readonly MouseDevice _mouseDevice;
private readonly PenDevice _penDevice; private readonly PenDevice _penDevice;
@ -223,7 +223,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{ {
Position = new Point(e.GetX(index), e.GetY(index)) / _view.RenderScaling, Position = new Point(e.GetX(index), e.GetY(index)) / _view.RenderScaling,
Pressure = Math.Min(e.GetPressure(index), 1), // android pressure can depend on the device, can be mixed up with "GetSize", may be larger than 1.0f on some devices Pressure = Math.Min(e.GetPressure(index), 1), // android pressure can depend on the device, can be mixed up with "GetSize", may be larger than 1.0f on some devices
Twist = e.GetOrientation(index) * s_radiansToDegree Twist = e.GetOrientation(index) * RadiansToDegree
}; };
} }
@ -233,7 +233,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{ {
Position = new Point(e.GetHistoricalX(index, pos), e.GetHistoricalY(index, pos)) / _view.RenderScaling, Position = new Point(e.GetHistoricalX(index, pos), e.GetHistoricalY(index, pos)) / _view.RenderScaling,
Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1), Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1),
Twist = e.GetHistoricalOrientation(index, pos) * s_radiansToDegree Twist = e.GetHistoricalOrientation(index, pos) * RadiansToDegree
}; };
} }

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

@ -14,10 +14,10 @@ namespace Avalonia.Android.Platform.Storage;
internal class AndroidStorageProvider : IStorageProvider internal class AndroidStorageProvider : IStorageProvider
{ {
private readonly AvaloniaActivity _activity; private readonly Activity _activity;
private int _lastRequestCode = 20000; private int _lastRequestCode = 20000;
public AndroidStorageProvider(AvaloniaActivity activity) public AndroidStorageProvider(Activity activity)
{ {
_activity = activity; _activity = activity;
} }
@ -119,7 +119,10 @@ internal class AndroidStorageProvider : IStorageProvider
var tcs = new TaskCompletionSource<Intent?>(); var tcs = new TaskCompletionSource<Intent?>();
var currentRequestCode = _lastRequestCode++; var currentRequestCode = _lastRequestCode++;
_activity.ActivityResult += OnActivityResult; if (_activity is IActivityResultHandler mainActivity)
{
mainActivity.ActivityResult += OnActivityResult;
}
_activity.StartActivityForResult(pickerIntent, currentRequestCode); _activity.StartActivityForResult(pickerIntent, currentRequestCode);
var result = await tcs.Task; var result = await tcs.Task;
@ -158,7 +161,11 @@ internal class AndroidStorageProvider : IStorageProvider
return; return;
} }
_activity.ActivityResult -= OnActivityResult;
if (_activity is IActivityResultHandler mainActivity)
{
mainActivity.ActivityResult -= OnActivityResult;
}
_ = tcs.TrySetResult(resultCode == Result.Ok ? data : null); _ = tcs.TrySetResult(resultCode == Result.Ok ? data : null);
} }

26
src/Android/Avalonia.Android/SingleViewLifetime.cs

@ -0,0 +1,26 @@
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android
{
internal class SingleViewLifetime : ISingleViewApplicationLifetime
{
private AvaloniaView _view;
public AvaloniaView View
{
get => _view; internal set
{
if (_view != null)
{
_view.Content = null;
_view.Dispose();
}
_view = value;
_view.Content = MainView;
}
}
public Control MainView { get; set; }
}
}

7
src/Avalonia.Base/Avalonia.Base.csproj

@ -21,6 +21,10 @@
<Import Project="..\..\build\NullableEnable.props" /> <Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" /> <Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\SourceGenerators.props" /> <Import Project="..\..\build\SourceGenerators.props" />
<ItemGroup>
<Compile Include="..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" />
<Compile Include="..\Shared\StringCompatibilityExtensions.cs" Link="Compatibility\StringCompatibilityExtensions.cs" />
</ItemGroup>
<ItemGroup Label="InternalsVisibleTo"> <ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.Base.UnitTests, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Base.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
@ -38,11 +42,12 @@
<InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)"/> <InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" /> <InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Compatibility\" />
<Folder Include="Rendering\Composition\Utils" /> <Folder Include="Rendering\Composition\Utils" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
src/Avalonia.Base/AvaloniaProperty.cs

@ -41,7 +41,7 @@ namespace Avalonia
{ {
_ = name ?? throw new ArgumentNullException(nameof(name)); _ = name ?? throw new ArgumentNullException(nameof(name));
if (name.Contains(".")) if (name.Contains('.'))
{ {
throw new ArgumentException("'name' may not contain periods."); throw new ArgumentException("'name' may not contain periods.");
} }

2
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -228,7 +228,7 @@ namespace Avalonia
_ = type ?? throw new ArgumentNullException(nameof(type)); _ = type ?? throw new ArgumentNullException(nameof(type));
_ = name ?? throw new ArgumentNullException(nameof(name)); _ = name ?? throw new ArgumentNullException(nameof(name));
if (name.Contains(".")) if (name.Contains('.'))
{ {
throw new InvalidOperationException("Attached properties not supported."); throw new InvalidOperationException("Attached properties not supported.");
} }

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

@ -33,7 +33,7 @@ namespace Avalonia.Input
DragLink, DragLink,
None, None,
// Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/ // Not available in GTK directly, see https://www.pixelbeat.org/programming/x_cursors/
// We might enable them later, preferably, by loading pixmax directly from theme with fallback image // We might enable them later, preferably, by loading pixmax directly from theme with fallback image
// SizeNorthWestSouthEast, // SizeNorthWestSouthEast,
// SizeNorthEastSouthWest, // SizeNorthEastSouthWest,

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

@ -13,7 +13,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Event which is raised, when a drag-and-drop operation leaves the element. /// Event which is raised, when a drag-and-drop operation leaves the element.
/// </summary> /// </summary>
public static readonly RoutedEvent<RoutedEventArgs> DragLeaveEvent = RoutedEvent.Register<RoutedEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); public static readonly RoutedEvent<DragEventArgs> DragLeaveEvent = RoutedEvent.Register<DragEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary> /// <summary>
/// Event which is raised, when a drag-and-drop operation is updated while over the element. /// Event which is raised, when a drag-and-drop operation is updated while over the element.
/// </summary> /// </summary>

8
src/Avalonia.Base/Input/DragDropDevice.cs

@ -54,7 +54,7 @@ namespace Avalonia.Input
try try
{ {
if (_lastTarget != null) if (_lastTarget != null)
_lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers);
return RaiseDragEvent(target, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers); return RaiseDragEvent(target, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers);
} }
finally finally
@ -63,13 +63,13 @@ namespace Avalonia.Input
} }
} }
private void DragLeave(IInputElement inputRoot) private void DragLeave(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{ {
if (_lastTarget == null) if (_lastTarget == null)
return; return;
try try
{ {
_lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers);
} }
finally finally
{ {
@ -106,7 +106,7 @@ namespace Avalonia.Input
e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers); e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break; break;
case RawDragEventType.DragLeave: case RawDragEventType.DragLeave:
DragLeave(e.Root); DragLeave(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break; break;
case RawDragEventType.Drop: case RawDragEventType.Drop:
e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers); e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);

49
src/Avalonia.Base/Media/Color.cs

@ -9,6 +9,7 @@ using System;
using System.Globalization; using System.Globalization;
#if !BUILDTASK #if !BUILDTASK
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
using static Avalonia.Utilities.SpanHelpers;
#endif #endif
namespace Avalonia.Media namespace Avalonia.Media
@ -295,9 +296,7 @@ namespace Avalonia.Media
return false; return false;
} }
// TODO: (netstandard 2.1) Can use allocation free parsing. if (!input.TryParseUInt(NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed))
if (!uint.TryParse(input.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out var parsed))
{ {
return false; return false;
} }
@ -382,9 +381,9 @@ namespace Avalonia.Media
if (components.Length == 3) // RGB if (components.Length == 3) // RGB
{ {
if (InternalTryParseByte(components[0], out byte red) && if (InternalTryParseByte(components[0].AsSpan(), out byte red) &&
InternalTryParseByte(components[1], out byte green) && InternalTryParseByte(components[1].AsSpan(), out byte green) &&
InternalTryParseByte(components[2], out byte blue)) InternalTryParseByte(components[2].AsSpan(), out byte blue))
{ {
color = new Color(0xFF, red, green, blue); color = new Color(0xFF, red, green, blue);
return true; return true;
@ -392,10 +391,10 @@ namespace Avalonia.Media
} }
else if (components.Length == 4) // RGBA else if (components.Length == 4) // RGBA
{ {
if (InternalTryParseByte(components[0], out byte red) && if (InternalTryParseByte(components[0].AsSpan(), out byte red) &&
InternalTryParseByte(components[1], out byte green) && InternalTryParseByte(components[1].AsSpan(), out byte green) &&
InternalTryParseByte(components[2], out byte blue) && InternalTryParseByte(components[2].AsSpan(), out byte blue) &&
InternalTryParseDouble(components[3], out double alpha)) InternalTryParseDouble(components[3].AsSpan(), out double alpha))
{ {
color = new Color((byte)Math.Round(alpha * 255.0), red, green, blue); color = new Color((byte)Math.Round(alpha * 255.0), red, green, blue);
return true; return true;
@ -403,17 +402,14 @@ namespace Avalonia.Media
} }
// Local function to specially parse a byte value with an optional percentage sign // Local function to specially parse a byte value with an optional percentage sign
bool InternalTryParseByte(string inString, out byte outByte) bool InternalTryParseByte(ReadOnlySpan<char> inString, out byte outByte)
{ {
// The percent sign, if it exists, must be at the end of the number // The percent sign, if it exists, must be at the end of the number
int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
if (percentIndex >= 0) if (percentIndex >= 0)
{ {
var result = double.TryParse( var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString.Substring(0, percentIndex),
NumberStyles.Number,
CultureInfo.InvariantCulture,
out double percentage); out double percentage);
outByte = (byte)Math.Round((percentage / 100.0) * 255.0); outByte = (byte)Math.Round((percentage / 100.0) * 255.0);
@ -421,37 +417,28 @@ namespace Avalonia.Media
} }
else else
{ {
return byte.TryParse( return inString.TryParseByte(NumberStyles.Number, CultureInfo.InvariantCulture,
inString,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out outByte); out outByte);
} }
} }
// Local function to specially parse a double value with an optional percentage sign // Local function to specially parse a double value with an optional percentage sign
bool InternalTryParseDouble(string inString, out double outDouble) bool InternalTryParseDouble(ReadOnlySpan<char> inString, out double outDouble)
{ {
// The percent sign, if it exists, must be at the end of the number // The percent sign, if it exists, must be at the end of the number
int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
if (percentIndex >= 0) if (percentIndex >= 0)
{ {
var result = double.TryParse( var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString.Substring(0, percentIndex), out double percentage);
NumberStyles.Number,
CultureInfo.InvariantCulture,
out double percentage);
outDouble = percentage / 100.0; outDouble = percentage / 100.0;
return result; return result;
} }
else else
{ {
return double.TryParse( return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out outDouble); out outDouble);
} }
} }

12
src/Avalonia.Base/Media/FontManager.cs

@ -13,8 +13,8 @@ namespace Avalonia.Media
/// </summary> /// </summary>
public sealed class FontManager public sealed class FontManager
{ {
private readonly ConcurrentDictionary<Typeface, GlyphTypeface> _glyphTypefaceCache = private readonly ConcurrentDictionary<Typeface, IGlyphTypeface> _glyphTypefaceCache =
new ConcurrentDictionary<Typeface, GlyphTypeface>(); new ConcurrentDictionary<Typeface, IGlyphTypeface>();
private readonly FontFamily _defaultFontFamily; private readonly FontFamily _defaultFontFamily;
private readonly IReadOnlyList<FontFallback>? _fontFallbacks; private readonly IReadOnlyList<FontFallback>? _fontFallbacks;
@ -81,13 +81,13 @@ namespace Avalonia.Media
PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates); PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates);
/// <summary> /// <summary>
/// Returns a new <see cref="GlyphTypeface"/>, or an existing one if a matching <see cref="GlyphTypeface"/> exists. /// Returns a new <see cref="IGlyphTypeface"/>, or an existing one if a matching <see cref="IGlyphTypeface"/> exists.
/// </summary> /// </summary>
/// <param name="typeface">The typeface.</param> /// <param name="typeface">The typeface.</param>
/// <returns> /// <returns>
/// The <see cref="GlyphTypeface"/>. /// The <see cref="IGlyphTypeface"/>.
/// </returns> /// </returns>
public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) public IGlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
{ {
while (true) while (true)
{ {
@ -96,7 +96,7 @@ namespace Avalonia.Media
return glyphTypeface; return glyphTypeface;
} }
glyphTypeface = new GlyphTypeface(typeface); glyphTypeface = PlatformImpl.CreateGlyphTypeface(typeface);
if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface)) if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface))
{ {

58
src/Avalonia.Base/Media/FontMetrics.cs

@ -0,0 +1,58 @@
namespace Avalonia.Media
{
/// <summary>
/// The font metrics is holding information about a font's ascent, descent, etc. in design em units.
/// </summary>
public readonly struct FontMetrics
{
/// <summary>
/// Gets the font design units per em.
/// </summary>
public short DesignEmHeight { get; init; }
/// <summary>
/// A <see cref="bool"/> value indicating whether all glyphs in the font have the same advancement.
/// </summary>
public bool IsFixedPitch { get; init; }
/// <summary>
/// Gets the recommended distance above the baseline in design em size.
/// </summary>
public int Ascent { get; init; }
/// <summary>
/// Gets the recommended distance under the baseline in design em size.
/// </summary>
public int Descent { get; init; }
/// <summary>
/// Gets the recommended additional space between two lines of text in design em size.
/// </summary>
public int LineGap { get; init; }
/// <summary>
/// Gets the recommended line spacing of a formed text line.
/// </summary>
public int LineSpacing => Descent - Ascent + LineGap;
/// <summary>
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
/// </summary>
public int UnderlinePosition { get; init; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int UnderlineThickness { get; init; }
/// <summary>
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
/// </summary>
public int StrikethroughPosition { get; init; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int StrikethroughThickness { get; init; }
}
}

27
src/Avalonia.Base/Media/FontSimulations.cs

@ -0,0 +1,27 @@
using System;
namespace Avalonia.Media
{
/// <summary>
/// Specifies algorithmic style simulations to be applied to the typeface.
/// Bold and oblique simulations can be combined via bitwise OR operation.
/// </summary>
[Flags]
public enum FontSimulations : byte
{
/// <summary>
/// No simulations are performed.
/// </summary>
None = 0x0000,
/// <summary>
/// Algorithmic emboldening is performed.
/// </summary>
Bold = 0x0001,
/// <summary>
/// Algorithmic italicization is performed.
/// </summary>
Oblique = 0x0002
}
}

24
src/Avalonia.Base/Media/GlyphMetrics.cs

@ -0,0 +1,24 @@
namespace Avalonia.Media;
public readonly struct GlyphMetrics
{
/// <summary>
/// Distance from the x-origin to the left extremum of the glyph.
/// </summary>
public int XBearing { get; init; }
/// <summary>
/// Distance from the top extremum of the glyph to the y-origin.
/// </summary>
public int YBearing{ get; init; }
/// <summary>
/// Distance from the left extremum of the glyph to the right extremum.
/// </summary>
public int Width{ get; init; }
/// <summary>
/// Distance from the top extremum of the glyph to the bottom extremum.
/// </summary>
public int Height{ get; init; }
}

95
src/Avalonia.Base/Media/GlyphRun.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -15,7 +16,7 @@ namespace Avalonia.Media
private static readonly IComparer<int> s_descendingComparer = new ReverseComparer<int>(); private static readonly IComparer<int> s_descendingComparer = new ReverseComparer<int>();
private IGlyphRunImpl? _glyphRunImpl; private IGlyphRunImpl? _glyphRunImpl;
private GlyphTypeface _glyphTypeface; private IGlyphTypeface _glyphTypeface;
private double _fontRenderingEmSize; private double _fontRenderingEmSize;
private int _biDiLevel; private int _biDiLevel;
private Point? _baselineOrigin; private Point? _baselineOrigin;
@ -42,7 +43,7 @@ namespace Avalonia.Media
/// <param name="glyphClusters">The glyph clusters.</param> /// <param name="glyphClusters">The glyph clusters.</param>
/// <param name="biDiLevel">The bidi level.</param> /// <param name="biDiLevel">The bidi level.</param>
public GlyphRun( public GlyphRun(
GlyphTypeface glyphTypeface, IGlyphTypeface glyphTypeface,
double fontRenderingEmSize, double fontRenderingEmSize,
ReadOnlySlice<char> characters, ReadOnlySlice<char> characters,
IReadOnlyList<ushort> glyphIndices, IReadOnlyList<ushort> glyphIndices,
@ -69,9 +70,9 @@ namespace Avalonia.Media
} }
/// <summary> /// <summary>
/// Gets the <see cref="Media.GlyphTypeface"/> for the <see cref="GlyphRun"/>. /// Gets the <see cref="IGlyphTypeface"/> for the <see cref="GlyphRun"/>.
/// </summary> /// </summary>
public GlyphTypeface GlyphTypeface => _glyphTypeface; public IGlyphTypeface GlyphTypeface => _glyphTypeface;
/// <summary> /// <summary>
/// Gets or sets the em size used for rendering the <see cref="GlyphRun"/>. /// Gets or sets the em size used for rendering the <see cref="GlyphRun"/>.
@ -171,7 +172,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets the scale of the current <see cref="Media.GlyphTypeface"/> /// Gets the scale of the current <see cref="Media.GlyphTypeface"/>
/// </summary> /// </summary>
internal double Scale => FontRenderingEmSize / GlyphTypeface.DesignEmHeight; internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight;
/// <summary> /// <summary>
/// Returns <c>true</c> if the text direction is left-to-right. Otherwise, returns <c>false</c>. /// Returns <c>true</c> if the text direction is left-to-right. Otherwise, returns <c>false</c>.
@ -612,7 +613,7 @@ namespace Avalonia.Media
/// <returns>The baseline origin.</returns> /// <returns>The baseline origin.</returns>
private Point CalculateBaselineOrigin() private Point CalculateBaselineOrigin()
{ {
return new Point(0, -GlyphTypeface.Ascent * Scale); return new Point(0, -GlyphTypeface.Metrics.Ascent * Scale);
} }
private GlyphRunMetrics CreateGlyphRunMetrics() private GlyphRunMetrics CreateGlyphRunMetrics()
@ -636,7 +637,7 @@ namespace Avalonia.Media
} }
var isReversed = firstCluster > lastCluster; var isReversed = firstCluster > lastCluster;
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; var height = GlyphTypeface.Metrics.LineSpacing * Scale;
var widthIncludingTrailingWhitespace = 0d; var widthIncludingTrailingWhitespace = 0d;
var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount); var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount);
@ -854,9 +855,87 @@ namespace Avalonia.Media
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
_glyphRunImpl = CreateGlyphRunImpl();
}
private IGlyphRunImpl CreateGlyphRunImpl()
{
IGlyphRunImpl glyphRunImpl;
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>(); var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
var count = GlyphIndices.Count;
var scale = (float)(FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight);
if (GlyphOffsets == null)
{
if (GlyphTypeface.Metrics.IsFixedPitch)
{
var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
for (int i = 0; i < glyphs.Length; i++)
{
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
else
{
var buffer = platformRenderInterface.AllocateHorizontalGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
var positions = buffer.GlyphPositions;
var width = 0d;
for (var i = 0; i < count; i++)
{
positions[i] = (float)width;
if (GlyphAdvances == null)
{
width += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
}
else
{
width += GlyphAdvances[i];
}
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
}
else
{
var buffer = platformRenderInterface.AllocatePositionedGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
var glyphs = buffer.GlyphIndices;
var glyphPositions = buffer.GlyphPositions;
var currentX = 0.0;
for (var i = 0; i < count; i++)
{
var glyphOffset = GlyphOffsets[i];
glyphPositions[i] = new PointF((float)(currentX + glyphOffset.X), (float)glyphOffset.Y);
if (GlyphAdvances == null)
{
currentX += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
}
else
{
currentX += GlyphAdvances[i];
}
glyphs[i] = GlyphIndices[i];
}
glyphRunImpl = buffer.Build();
}
_glyphRunImpl = platformRenderInterface.CreateGlyphRun(this); return glyphRunImpl;
} }
void IDisposable.Dispose() void IDisposable.Dispose()

125
src/Avalonia.Base/Media/GlyphTypeface.cs

@ -1,125 +0,0 @@
using System;
using Avalonia.Platform;
namespace Avalonia.Media
{
public sealed class GlyphTypeface : IDisposable
{
public GlyphTypeface(Typeface typeface)
: this(FontManager.Current.PlatformImpl.CreateGlyphTypeface(typeface))
{
}
public GlyphTypeface(IGlyphTypefaceImpl platformImpl)
{
PlatformImpl = platformImpl;
}
public IGlyphTypefaceImpl PlatformImpl { get; }
/// <summary>
/// Gets the font design units per em.
/// </summary>
public short DesignEmHeight => PlatformImpl.DesignEmHeight;
/// <summary>
/// Gets the recommended distance above the baseline in design em size.
/// </summary>
public int Ascent => PlatformImpl.Ascent;
/// <summary>
/// Gets the recommended distance under the baseline in design em size.
/// </summary>
public int Descent => PlatformImpl.Descent;
/// <summary>
/// Gets the recommended additional space between two lines of text in design em size.
/// </summary>
public int LineGap => PlatformImpl.LineGap;
/// <summary>
/// Gets the recommended line height.
/// </summary>
public int LineHeight => Descent - Ascent + LineGap;
/// <summary>
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
/// </summary>
public int UnderlinePosition => PlatformImpl.UnderlinePosition;
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int UnderlineThickness => PlatformImpl.UnderlineThickness;
/// <summary>
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
/// </summary>
public int StrikethroughPosition => PlatformImpl.StrikethroughPosition;
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int StrikethroughThickness => PlatformImpl.StrikethroughThickness;
/// <summary>
/// A <see cref="bool"/> value indicating whether all glyphs in the font have the same advancement.
/// </summary>
public bool IsFixedPitch => PlatformImpl.IsFixedPitch;
/// <summary>
/// Returns an glyph index for the specified codepoint.
/// </summary>
/// <remarks>
/// Returns a replacement glyph if a glyph isn't found.
/// </remarks>
/// <param name="codepoint">The codepoint.</param>
/// <returns>
/// A glyph index.
/// </returns>
public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint);
/// <summary>
/// Tries to get an glyph index for specified codepoint.
/// </summary>
/// <param name="codepoint">The codepoint.</param>
/// <param name="glyph">A glyph index.</param>
/// <returns>
/// <c>true</c> if an glyph index was found, <c>false</c> otherwise.
/// </returns>
public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = PlatformImpl.GetGlyph(codepoint);
return glyph != 0;
}
/// <summary>
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
/// </summary>
/// <param name="codepoints">The codepoints to map.</param>
/// <returns></returns>
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) => PlatformImpl.GetGlyphs(codepoints);
/// <summary>
/// Returns the glyph advance for the specified glyph.
/// </summary>
/// <param name="glyph">The glyph.</param>
/// <returns>
/// The advance.
/// </returns>
public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph);
/// <summary>
/// Returns an array of glyph advances in design em size.
/// </summary>
/// <param name="glyphs">The glyph indices.</param>
/// <returns></returns>
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) => PlatformImpl.GetGlyphAdvances(glyphs);
void IDisposable.Dispose()
{
PlatformImpl?.Dispose();
}
}
}

30
src/Avalonia.Base/Media/HslColor.cs

@ -302,9 +302,9 @@ namespace Avalonia.Media
if (components.Length == 3) // HSL if (components.Length == 3) // HSL
{ {
if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
TryInternalParse(components[1], out double saturation) && TryInternalParse(components[1].AsSpan(), out double saturation) &&
TryInternalParse(components[2], out double lightness)) TryInternalParse(components[2].AsSpan(), out double lightness))
{ {
hslColor = new HslColor(1.0, hue, saturation, lightness); hslColor = new HslColor(1.0, hue, saturation, lightness);
return true; return true;
@ -312,10 +312,10 @@ namespace Avalonia.Media
} }
else if (components.Length == 4) // HSLA else if (components.Length == 4) // HSLA
{ {
if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
TryInternalParse(components[1], out double saturation) && TryInternalParse(components[1].AsSpan(), out double saturation) &&
TryInternalParse(components[2], out double lightness) && TryInternalParse(components[2].AsSpan(), out double lightness) &&
TryInternalParse(components[3], out double alpha)) TryInternalParse(components[3].AsSpan(), out double alpha))
{ {
hslColor = new HslColor(alpha, hue, saturation, lightness); hslColor = new HslColor(alpha, hue, saturation, lightness);
return true; return true;
@ -323,28 +323,22 @@ namespace Avalonia.Media
} }
// Local function to specially parse a double value with an optional percentage sign // Local function to specially parse a double value with an optional percentage sign
bool TryInternalParse(string inString, out double outDouble) bool TryInternalParse(ReadOnlySpan<char> inString, out double outDouble)
{ {
// The percent sign, if it exists, must be at the end of the number // The percent sign, if it exists, must be at the end of the number
int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
if (percentIndex >= 0) if (percentIndex >= 0)
{ {
var result = double.TryParse( var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString.Substring(0, percentIndex), out double percentage);
NumberStyles.Number,
CultureInfo.InvariantCulture,
out double percentage);
outDouble = percentage / 100.0; outDouble = percentage / 100.0;
return result; return result;
} }
else else
{ {
return double.TryParse( return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out outDouble); out outDouble);
} }
} }

30
src/Avalonia.Base/Media/HsvColor.cs

@ -302,9 +302,9 @@ namespace Avalonia.Media
if (components.Length == 3) // HSV if (components.Length == 3) // HSV
{ {
if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
TryInternalParse(components[1], out double saturation) && TryInternalParse(components[1].AsSpan(), out double saturation) &&
TryInternalParse(components[2], out double value)) TryInternalParse(components[2].AsSpan(), out double value))
{ {
hsvColor = new HsvColor(1.0, hue, saturation, value); hsvColor = new HsvColor(1.0, hue, saturation, value);
return true; return true;
@ -312,10 +312,10 @@ namespace Avalonia.Media
} }
else if (components.Length == 4) // HSVA else if (components.Length == 4) // HSVA
{ {
if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
TryInternalParse(components[1], out double saturation) && TryInternalParse(components[1].AsSpan(), out double saturation) &&
TryInternalParse(components[2], out double value) && TryInternalParse(components[2].AsSpan(), out double value) &&
TryInternalParse(components[3], out double alpha)) TryInternalParse(components[3].AsSpan(), out double alpha))
{ {
hsvColor = new HsvColor(alpha, hue, saturation, value); hsvColor = new HsvColor(alpha, hue, saturation, value);
return true; return true;
@ -323,28 +323,22 @@ namespace Avalonia.Media
} }
// Local function to specially parse a double value with an optional percentage sign // Local function to specially parse a double value with an optional percentage sign
bool TryInternalParse(string inString, out double outDouble) bool TryInternalParse(ReadOnlySpan<char> inString, out double outDouble)
{ {
// The percent sign, if it exists, must be at the end of the number // The percent sign, if it exists, must be at the end of the number
int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal);
if (percentIndex >= 0) if (percentIndex >= 0)
{ {
var result = double.TryParse( var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString.Substring(0, percentIndex), out double percentage);
NumberStyles.Number,
CultureInfo.InvariantCulture,
out double percentage);
outDouble = percentage / 100.0; outDouble = percentage / 100.0;
return result; return result;
} }
else else
{ {
return double.TryParse( return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture,
inString,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out outDouble); out outDouble);
} }
} }

73
src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs → src/Avalonia.Base/Media/IGlyphTypeface.cs

@ -1,56 +1,39 @@
using System; using System;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Platform namespace Avalonia.Media
{ {
[Unstable] [Unstable]
public interface IGlyphTypefaceImpl : IDisposable public interface IGlyphTypeface : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the font design units per em. /// Gets the number of glyphs held by this glyph typeface.
/// </summary> /// </summary>
short DesignEmHeight { get; } int GlyphCount { get; }
/// <summary> /// <summary>
/// Gets the recommended distance above the baseline in design em size. /// Gets the font metrics.
/// </summary> /// </summary>
int Ascent { get; } /// <returns>
/// The font metrics.
/// <summary> /// </returns>
/// Gets the recommended distance under the baseline in design em size. FontMetrics Metrics { get; }
/// </summary>
int Descent { get; }
/// <summary>
/// Gets the recommended additional space between two lines of text in design em size.
/// </summary>
int LineGap { get; }
/// <summary>
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
/// </summary>
int UnderlinePosition { get; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
int UnderlineThickness { get; }
/// <summary>
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
/// </summary>
int StrikethroughPosition { get; }
/// <summary> /// <summary>
/// Gets a value that indicates the thickness of the underline in design em size. /// Gets the algorithmic style simulations applied to this glyph typeface.
/// </summary> /// </summary>
int StrikethroughThickness { get; } FontSimulations FontSimulations { get; }
/// <summary> /// <summary>
/// A <see cref="bool"/> value indicating whether all glyphs in the font have the same advancement. /// Tries to get a glyph's metrics in em units.
/// </summary> /// </summary>
bool IsFixedPitch { get; } /// <param name="glyph">The glyph id.</param>
/// <param name="metrics">The glyph metrics.</param>
/// <returns>
/// <c>true</c> if an glyph's metrics was found, <c>false</c> otherwise.
/// </returns>
bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics);
/// <summary> /// <summary>
/// Returns an glyph index for the specified codepoint. /// Returns an glyph index for the specified codepoint.
/// </summary> /// </summary>
@ -63,6 +46,16 @@ namespace Avalonia.Platform
/// </returns> /// </returns>
ushort GetGlyph(uint codepoint); ushort GetGlyph(uint codepoint);
/// <summary>
/// Tries to get an glyph index for specified codepoint.
/// </summary>
/// <param name="codepoint">The codepoint.</param>
/// <param name="glyph">A glyph index.</param>
/// <returns>
/// <c>true</c> if an glyph index was found, <c>false</c> otherwise.
/// </returns>
bool TryGetGlyph(uint codepoint, out ushort glyph);
/// <summary> /// <summary>
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>. /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
/// </summary> /// </summary>
@ -89,5 +82,13 @@ namespace Avalonia.Platform
/// An array of glyph advances. /// An array of glyph advances.
/// </returns> /// </returns>
int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs); int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs);
/// <summary>
/// Returns the contents of the table data for the specified tag.
/// </summary>
/// <param name="tag">The table tag to get the data for.</param>
/// <param name="table">The contents of the table data for the specified tag.</param>
/// <returns>Returns <c>true</c> if the content exists, otherwise <c>false</c>.</returns>
bool TryGetTable(uint tag, out byte[] table);
} }
} }

4
src/Avalonia.Base/Media/PathMarkupParser.cs

@ -188,7 +188,7 @@ namespace Avalonia.Media
_isOpen = true; _isOpen = true;
} }
private void SetFillRule(ref ReadOnlySpan<char> span) private void SetFillRule(scoped ref ReadOnlySpan<char> span)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
@ -452,7 +452,7 @@ namespace Avalonia.Media
return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || span[0] == '.' || char.IsDigit(span[0])); return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || span[0] == '.' || char.IsDigit(span[0]));
} }
private static bool ReadArgument(ref ReadOnlySpan<char> remaining, out ReadOnlySpan<char> argument) private static bool ReadArgument(scoped ref ReadOnlySpan<char> remaining, out ReadOnlySpan<char> argument)
{ {
remaining = SkipWhitespace(remaining); remaining = SkipWhitespace(remaining);
if (remaining.IsEmpty) if (remaining.IsEmpty)

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

@ -1081,7 +1081,7 @@ namespace Avalonia.Media
Point c = rest * (cs) + translation; Point c = rest * (cs) + translation;
// See "http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter" to understand // See "https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter" to understand
// how the ellipse center is calculated // how the ellipse center is calculated

16
src/Avalonia.Base/Media/TextDecoration.cs

@ -155,9 +155,9 @@ namespace Avalonia.Media
/// </summary> /// </summary>
/// <param name="drawingContext">The drawing context.</param> /// <param name="drawingContext">The drawing context.</param>
/// <param name="glyphRun">The decorated run.</param> /// <param name="glyphRun">The decorated run.</param>
/// <param name="fontMetrics">The font metrics of the decorated run.</param> /// <param name="textMetrics">The font metrics of the decorated run.</param>
/// <param name="defaultBrush">The default brush that is used to draw the decoration.</param> /// <param name="defaultBrush">The default brush that is used to draw the decoration.</param>
internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, FontMetrics fontMetrics, IBrush defaultBrush) internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, TextMetrics textMetrics, IBrush defaultBrush)
{ {
var baselineOrigin = glyphRun.BaselineOrigin; var baselineOrigin = glyphRun.BaselineOrigin;
var thickness = StrokeThickness; var thickness = StrokeThickness;
@ -168,16 +168,16 @@ namespace Avalonia.Media
switch (Location) switch (Location)
{ {
case TextDecorationLocation.Underline: case TextDecorationLocation.Underline:
thickness = fontMetrics.UnderlineThickness; thickness = textMetrics.UnderlineThickness;
break; break;
case TextDecorationLocation.Strikethrough: case TextDecorationLocation.Strikethrough:
thickness = fontMetrics.StrikethroughThickness; thickness = textMetrics.StrikethroughThickness;
break; break;
} }
break; break;
case TextDecorationUnit.FontRenderingEmSize: case TextDecorationUnit.FontRenderingEmSize:
thickness = fontMetrics.FontRenderingEmSize * thickness; thickness = textMetrics.FontRenderingEmSize * thickness;
break; break;
} }
@ -189,17 +189,17 @@ namespace Avalonia.Media
origin += glyphRun.BaselineOrigin; origin += glyphRun.BaselineOrigin;
break; break;
case TextDecorationLocation.Strikethrough: case TextDecorationLocation.Strikethrough:
origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.StrikethroughPosition); origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.StrikethroughPosition);
break; break;
case TextDecorationLocation.Underline: case TextDecorationLocation.Underline:
origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.UnderlinePosition); origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.UnderlinePosition);
break; break;
} }
switch (StrokeOffsetUnit) switch (StrokeOffsetUnit)
{ {
case TextDecorationUnit.FontRenderingEmSize: case TextDecorationUnit.FontRenderingEmSize:
origin += new Point(0, StrokeOffset * fontMetrics.FontRenderingEmSize); origin += new Point(0, StrokeOffset * textMetrics.FontRenderingEmSize);
break; break;
case TextDecorationUnit.Pixel: case TextDecorationUnit.Pixel:
origin += new Point(0, StrokeOffset); origin += new Point(0, StrokeOffset);

6
src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs

@ -8,13 +8,13 @@ namespace Avalonia.Media.TextFormatting
{ {
private static readonly IComparer<GlyphInfo> s_clusterComparer = new CompareClusters(); private static readonly IComparer<GlyphInfo> s_clusterComparer = new CompareClusters();
public ShapedBuffer(ReadOnlySlice<char> text, int length, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) public ShapedBuffer(ReadOnlySlice<char> text, int length, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
: this(text, new GlyphInfo[length], glyphTypeface, fontRenderingEmSize, bidiLevel) : this(text, new GlyphInfo[length], glyphTypeface, fontRenderingEmSize, bidiLevel)
{ {
} }
internal ShapedBuffer(ReadOnlySlice<char> text, ArraySlice<GlyphInfo> glyphInfos, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) internal ShapedBuffer(ReadOnlySlice<char> text, ArraySlice<GlyphInfo> glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
{ {
Text = text; Text = text;
GlyphInfos = glyphInfos; GlyphInfos = glyphInfos;
@ -29,7 +29,7 @@ namespace Avalonia.Media.TextFormatting
public int Length => GlyphInfos.Length; public int Length => GlyphInfos.Length;
public GlyphTypeface GlyphTypeface { get; } public IGlyphTypeface GlyphTypeface { get; }
public double FontRenderingEmSize { get; } public double FontRenderingEmSize { get; }

9
src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs

@ -1,5 +1,4 @@
using System; using System;
using System.Diagnostics;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -18,7 +17,7 @@ namespace Avalonia.Media.TextFormatting
Text = shapedBuffer.Text; Text = shapedBuffer.Text;
Properties = properties; Properties = properties;
TextSourceLength = Text.Length; TextSourceLength = Text.Length;
FontMetrics = new FontMetrics(properties.Typeface, properties.FontRenderingEmSize); TextMetrics = new TextMetrics(properties.Typeface, properties.FontRenderingEmSize);
} }
public bool IsReversed { get; private set; } public bool IsReversed { get; private set; }
@ -36,9 +35,9 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/> /// <inheritdoc/>
public override int TextSourceLength { get; } public override int TextSourceLength { get; }
public FontMetrics FontMetrics { get; } public TextMetrics TextMetrics { get; }
public override double Baseline => -FontMetrics.Ascent; public override double Baseline => -TextMetrics.Ascent;
public override Size Size => GlyphRun.Size; public override Size Size => GlyphRun.Size;
@ -89,7 +88,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textDecoration in Properties.TextDecorations) foreach (var textDecoration in Properties.TextDecorations)
{ {
textDecoration.Draw(drawingContext, GlyphRun, FontMetrics, Properties.ForegroundBrush); textDecoration.Draw(drawingContext, GlyphRun, TextMetrics, Properties.ForegroundBrush);
} }
} }
} }

29
src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs

@ -477,25 +477,28 @@ namespace Avalonia.Media.TextFormatting
{ {
case ShapedTextCharacters shapedTextCharacters: case ShapedTextCharacters shapedTextCharacters:
{ {
var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphClusters[0]; if(shapedTextCharacters.ShapedBuffer.Length > 0)
var lastCluster = firstCluster;
for (var i = 0; i < shapedTextCharacters.ShapedBuffer.Length; i++)
{ {
var glyphInfo = shapedTextCharacters.ShapedBuffer[i]; var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphClusters[0];
var lastCluster = firstCluster;
if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth) for (var i = 0; i < shapedTextCharacters.ShapedBuffer.Length; i++)
{ {
measuredLength += Math.Max(0, lastCluster - firstCluster); var glyphInfo = shapedTextCharacters.ShapedBuffer[i];
goto found; if (currentWidth + glyphInfo.GlyphAdvance > paragraphWidth)
} {
measuredLength += Math.Max(0, lastCluster - firstCluster);
lastCluster = glyphInfo.GlyphCluster; goto found;
currentWidth += glyphInfo.GlyphAdvance; }
}
measuredLength += currentRun.TextSourceLength; lastCluster = glyphInfo.GlyphCluster;
currentWidth += glyphInfo.GlyphAdvance;
}
measuredLength += currentRun.TextSourceLength;
}
break; break;
} }

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

@ -1378,17 +1378,17 @@ namespace Avalonia.Media.TextFormatting
private TextLineMetrics CreateLineMetrics() private TextLineMetrics CreateLineMetrics()
{ {
var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface; var fontMetrics = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface.Metrics;
var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize; var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight;
var width = 0d; var width = 0d;
var widthIncludingWhitespace = 0d; var widthIncludingWhitespace = 0d;
var trailingWhitespaceLength = 0; var trailingWhitespaceLength = 0;
var newLineLength = 0; var newLineLength = 0;
var ascent = glyphTypeface.Ascent * scale; var ascent = fontMetrics.Ascent * scale;
var descent = glyphTypeface.Descent * scale; var descent = fontMetrics.Descent * scale;
var lineGap = glyphTypeface.LineGap * scale; var lineGap = fontMetrics.LineGap * scale;
var height = descent - ascent + lineGap; var height = descent - ascent + lineGap;
@ -1400,26 +1400,26 @@ namespace Avalonia.Media.TextFormatting
{ {
case ShapedTextCharacters textRun: case ShapedTextCharacters textRun:
{ {
var fontMetrics = var textMetrics =
new FontMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize); new TextMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize);
if (fontRenderingEmSize < textRun.Properties.FontRenderingEmSize) if (fontRenderingEmSize < textRun.Properties.FontRenderingEmSize)
{ {
fontRenderingEmSize = textRun.Properties.FontRenderingEmSize; fontRenderingEmSize = textRun.Properties.FontRenderingEmSize;
if (ascent > fontMetrics.Ascent) if (ascent > textMetrics.Ascent)
{ {
ascent = fontMetrics.Ascent; ascent = textMetrics.Ascent;
} }
if (descent < fontMetrics.Descent) if (descent < textMetrics.Descent)
{ {
descent = fontMetrics.Descent; descent = textMetrics.Descent;
} }
if (lineGap < fontMetrics.LineGap) if (lineGap < textMetrics.LineGap)
{ {
lineGap = fontMetrics.LineGap; lineGap = textMetrics.LineGap;
} }
if (descent - ascent + lineGap > height) if (descent - ascent + lineGap > height)

24
src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs → src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs

@ -1,33 +1,33 @@
namespace Avalonia.Media.TextFormatting namespace Avalonia.Media.TextFormatting
{ {
/// <summary> /// <summary>
/// A metric that holds information about font specific measurements. /// A metric that holds information about text specific measurements.
/// </summary> /// </summary>
public readonly struct FontMetrics public readonly struct TextMetrics
{ {
public FontMetrics(Typeface typeface, double fontRenderingEmSize) public TextMetrics(Typeface typeface, double fontRenderingEmSize)
{ {
var glyphTypeface = typeface.GlyphTypeface; var fontMetrics = typeface.GlyphTypeface.Metrics;
var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight; var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight;
FontRenderingEmSize = fontRenderingEmSize; FontRenderingEmSize = fontRenderingEmSize;
Ascent = glyphTypeface.Ascent * scale; Ascent = fontMetrics.Ascent * scale;
Descent = glyphTypeface.Descent * scale; Descent = fontMetrics.Descent * scale;
LineGap = glyphTypeface.LineGap * scale; LineGap = fontMetrics.LineGap * scale;
LineHeight = Descent - Ascent + LineGap; LineHeight = Descent - Ascent + LineGap;
UnderlineThickness = glyphTypeface.UnderlineThickness * scale; UnderlineThickness = fontMetrics.UnderlineThickness * scale;
UnderlinePosition = glyphTypeface.UnderlinePosition * scale; UnderlinePosition = fontMetrics.UnderlinePosition * scale;
StrikethroughThickness = glyphTypeface.StrikethroughThickness * scale; StrikethroughThickness = fontMetrics.StrikethroughThickness * scale;
StrikethroughPosition = glyphTypeface.StrikethroughPosition * scale; StrikethroughPosition = fontMetrics.StrikethroughPosition * scale;
} }
/// <summary> /// <summary>

4
src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs

@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting
public readonly struct TextShaperOptions public readonly struct TextShaperOptions
{ {
public TextShaperOptions( public TextShaperOptions(
GlyphTypeface typeface, IGlyphTypeface typeface,
double fontRenderingEmSize = 12, double fontRenderingEmSize = 12,
sbyte bidiLevel = 0, sbyte bidiLevel = 0,
CultureInfo? culture = null, CultureInfo? culture = null,
@ -24,7 +24,7 @@ namespace Avalonia.Media.TextFormatting
/// <summary> /// <summary>
/// Get the typeface. /// Get the typeface.
/// </summary> /// </summary>
public GlyphTypeface Typeface { get; } public IGlyphTypeface Typeface { get; }
/// <summary> /// <summary>
/// Get the font rendering em size. /// Get the font rendering em size.
/// </summary> /// </summary>

2
src/Avalonia.Base/Media/TextFormatting/Unicode/BinaryReaderExtensions.cs

@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain // not use this product except in compliance with the License. You may obtain
// a copy of the License at // a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

7
src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs

@ -1,4 +1,5 @@
using System.Runtime.CompilerServices; using System;
using System.Runtime.CompilerServices;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode namespace Avalonia.Media.TextFormatting.Unicode
@ -104,7 +105,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// <summary> /// <summary>
/// Gets the canonical representation of a given codepoint. /// Gets the canonical representation of a given codepoint.
/// <see href="http://www.unicode.org/L2/L2013/13123-norm-and-bpa.pdf"/> /// <see href="https://www.unicode.org/L2/L2013/13123-norm-and-bpa.pdf"/>
/// </summary> /// </summary>
/// <param name="codePoint">The code point to be mapped.</param> /// <param name="codePoint">The code point to be mapped.</param>
/// <returns>The mapped canonical code point, or the passed <paramref name="codePoint"/>.</returns> /// <returns>The mapped canonical code point, or the passed <paramref name="codePoint"/>.</returns>
@ -165,7 +166,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// <param name="index">The index to read at.</param> /// <param name="index">The index to read at.</param>
/// <param name="count">The count of character that were read.</param> /// <param name="count">The count of character that were read.</param>
/// <returns></returns> /// <returns></returns>
public static Codepoint ReadAt(ReadOnlySlice<char> text, int index, out int count) public static Codepoint ReadAt(ReadOnlySpan<char> text, int index, out int count)
{ {
count = 1; count = 1;

2
src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs

@ -2,6 +2,6 @@ namespace Avalonia.Media.TextFormatting.Unicode
{ {
internal static class GraphemeBreak internal static class GraphemeBreak
{ {
public static byte[] Data => new byte[0]; public static byte[] Data => System.Array.Empty<byte>();
} }
} }

2
src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs

@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain // not use this product except in compliance with the License. You may obtain
// a copy of the License at // a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

2
src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrie.cs

@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain // not use this product except in compliance with the License. You may obtain
// a copy of the License at // a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

2
src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.Constants.cs

@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain // not use this product except in compliance with the License. You may obtain
// a copy of the License at // a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

2
src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.cs

@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain // not use this product except in compliance with the License. You may obtain
// a copy of the License at // a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

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

@ -81,7 +81,7 @@ namespace Avalonia.Media
/// <value> /// <value>
/// The glyph typeface. /// The glyph typeface.
/// </value> /// </value>
public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); public IGlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this);
public static bool operator !=(Typeface a, Typeface b) public static bool operator !=(Typeface a, Typeface b)
{ {

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

@ -163,7 +163,7 @@ namespace Avalonia.Media
throw new FormatException("Could not parse specified Unicode range segment."); throw new FormatException("Could not parse specified Unicode range segment.");
} }
if (!single.Value.Contains("?")) if (!single.Value.Contains('?'))
{ {
start = int.Parse(single.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); start = int.Parse(single.Groups[1].Value, System.Globalization.NumberStyles.HexNumber);
end = start; end = start;

2
src/Avalonia.Base/Platform/IFontManagerImpl.cs

@ -43,6 +43,6 @@ namespace Avalonia.Platform
/// <returns>0 /// <returns>0
/// The created glyph typeface. Can be <c>Null</c> if it was not possible to create a glyph typeface. /// The created glyph typeface. Can be <c>Null</c> if it was not possible to create a glyph typeface.
/// </returns> /// </returns>
IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface); IGlyphTypeface CreateGlyphTypeface(Typeface typeface);
} }
} }

22
src/Avalonia.Base/Platform/IGlyphRunBuffer.cs

@ -0,0 +1,22 @@
using System;
using System.Drawing;
namespace Avalonia.Platform
{
public interface IGlyphRunBuffer
{
Span<ushort> GlyphIndices { get; }
IGlyphRunImpl Build();
}
public interface IHorizontalGlyphRunBuffer : IGlyphRunBuffer
{
Span<float> GlyphPositions { get; }
}
public interface IPositionedGlyphRunBuffer : IGlyphRunBuffer
{
Span<PointF> GlyphPositions { get; }
}
}

37
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -171,11 +171,40 @@ namespace Avalonia.Platform
IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride); IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride);
/// <summary> /// <summary>
/// Creates a platform implementation of a glyph run. /// Allocates a platform glyph run buffer.
/// </summary> /// </summary>
/// <param name="glyphRun">The glyph run.</param> /// <param name="glyphTypeface">The glyph typeface.</param>
/// <returns></returns> /// <param name="fontRenderingEmSize">The font rendering em size.</param>
IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun); /// <param name="length">The length.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
/// <remarks>
/// This buffer only holds glyph indices.
/// </remarks>
IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary>
/// Allocates a horizontal platform glyph run buffer.
/// </summary>
/// <param name="glyphTypeface">The glyph typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="length">The length.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
/// <remarks>
/// This buffer holds glyph indices and glyph advances.
/// </remarks>
IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary>
/// Allocates a positioned platform glyph run buffer.
/// </summary>
/// <param name="glyphTypeface">The glyph typeface.</param>
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="length">The length.</param>
/// <returns>An <see cref="IGlyphRunBuffer"/>.</returns>
/// <remarks>
/// This buffer holds glyph indices, glyph advances and glyph positions.
/// </remarks>
IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
/// <summary> /// <summary>
/// Gets a value indicating whether the platform directly supports rectangles with rounded corners. /// Gets a value indicating whether the platform directly supports rectangles with rounded corners.

2
src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs

@ -25,7 +25,7 @@ internal class FpsCounter
// ASCII chars // ASCII chars
private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1]; private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1];
public FpsCounter(GlyphTypeface typeface) public FpsCounter(IGlyphTypeface typeface)
{ {
for (var c = FirstChar; c <= LastChar; c++) for (var c = FirstChar; c <= LastChar; c++)
{ {

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

@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain // not use this product except in compliance with the License. You may obtain
// a copy of the License at // a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

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

@ -8,7 +8,7 @@ namespace Avalonia.Utilities
#endif #endif
static class IdentifierParser static class IdentifierParser
{ {
public static ReadOnlySpan<char> ParseIdentifier(this ref CharacterReader r) public static ReadOnlySpan<char> ParseIdentifier(this scoped ref CharacterReader r)
{ {
if (IsValidIdentifierStart(r.Peek)) if (IsValidIdentifierStart(r.Peek))
{ {

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

@ -11,7 +11,7 @@ namespace Avalonia.Utilities
static class MathUtilities static class MathUtilities
{ {
// smallest such that 1.0+DoubleEpsilon != 1.0 // smallest such that 1.0+DoubleEpsilon != 1.0
internal static readonly double DoubleEpsilon = 2.2204460492503131e-016; internal const double DoubleEpsilon = 2.2204460492503131e-016;
private const float FloatEpsilon = 1.192092896e-07F; private const float FloatEpsilon = 1.192092896e-07F;

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

@ -214,6 +214,8 @@ namespace Avalonia.Utilities
return new ReadOnlySlice<T>(memory); return new ReadOnlySlice<T>(memory);
} }
public static implicit operator ReadOnlySpan<T>(ReadOnlySlice<T> slice) => slice.Span;
internal class ReadOnlySliceDebugView internal class ReadOnlySliceDebugView
{ {
private readonly ReadOnlySlice<T> _readOnlySlice; private readonly ReadOnlySlice<T> _readOnlySlice;

49
src/Avalonia.Base/Utilities/SpanHelpers.cs

@ -0,0 +1,49 @@
using System;
using System.Globalization;
using System.Runtime.CompilerServices;
namespace Avalonia.Utilities
{
public static class SpanHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseUInt(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out uint value)
{
#if NETSTANDARD2_0
return uint.TryParse(span.ToString(), style, provider, out value);
#else
return uint.TryParse(span, style, provider, out value);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseInt(this ReadOnlySpan<char> span, out int value)
{
#if NETSTANDARD2_0
return int.TryParse(span.ToString(), out value);
#else
return int.TryParse(span, out value);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseDouble(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out double value)
{
#if NETSTANDARD2_0
return double.TryParse(span.ToString(), style, provider, out value);
#else
return double.TryParse(span, style, provider, out value);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseByte(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out byte value)
{
#if NETSTANDARD2_0
return byte.TryParse(span.ToString(), style, provider, out value);
#else
return byte.TryParse(span, style, provider, out value);
#endif
}
}
}

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

Loading…
Cancel
Save