Browse Source

Merge branch 'master' into feature/heterogeneous-item-containers-2

pull/11216/head
Steven Kirk 3 years ago
committed by GitHub
parent
commit
377844fc2e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      Avalonia.sln
  2. 32
      build/ExternalConsumers.props
  3. 6
      native/Avalonia.Native/src/OSX/AvnView.mm
  4. 2
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  5. 63
      readme.md
  6. 15
      samples/ControlCatalog/Pages/ComboBoxPage.xaml
  7. 15
      samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs
  8. 1
      samples/Sandbox/MainWindow.axaml.cs
  9. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  10. 3
      src/Avalonia.Base/Data/BindingPriority.cs
  11. 2
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  12. 2
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  13. 2
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  14. 3
      src/Avalonia.Base/Data/InstancedBinding.cs
  15. 3
      src/Avalonia.Base/Input/DataFormats.cs
  16. 3
      src/Avalonia.Base/Input/DataObjectExtensions.cs
  17. 27
      src/Avalonia.Base/Layout/LayoutManager.cs
  18. 3
      src/Avalonia.Base/Media/Color.cs
  19. 33
      src/Avalonia.Base/Media/DrawingContext.cs
  20. 10
      src/Avalonia.Base/Media/DrawingGroup.cs
  21. 4
      src/Avalonia.Base/Media/DrawingImage.cs
  22. 10
      src/Avalonia.Base/Media/EdgeMode.cs
  23. 42
      src/Avalonia.Base/Media/GlyphRun.cs
  24. 23
      src/Avalonia.Base/Media/GlyphRunMetrics.cs
  25. 4
      src/Avalonia.Base/Media/IImage.cs
  26. 8
      src/Avalonia.Base/Media/ITileBrush.cs
  27. 6
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  28. 6
      src/Avalonia.Base/Media/Imaging/BitmapBlendingMode.cs
  29. 10
      src/Avalonia.Base/Media/Imaging/BitmapInterpolationMode.cs
  30. 4
      src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs
  31. 8
      src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs
  32. 5
      src/Avalonia.Base/Media/ImmediateDrawingContext.cs
  33. 7
      src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs
  34. 11
      src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs
  35. 16
      src/Avalonia.Base/Media/PlatformDrawingContext.cs
  36. 127
      src/Avalonia.Base/Media/RenderOptions.cs
  37. 8
      src/Avalonia.Base/Media/TextDecoration.cs
  38. 4
      src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
  39. 33
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  40. 11
      src/Avalonia.Base/Media/TextRenderingMode.cs
  41. 13
      src/Avalonia.Base/Media/TileBrush.cs
  42. 19
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  43. 22
      src/Avalonia.Base/Platform/IGlyphRunBuffer.cs
  44. 19
      src/Avalonia.Base/Platform/IGlyphRunImpl.cs
  45. 3
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  46. 8
      src/Avalonia.Base/Platform/StandardAssetLoader.cs
  47. 4
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  48. 36
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  49. 21
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  50. 2
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  51. 7
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  52. 120
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  53. 68
      src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs
  54. 5
      src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
  55. 21
      src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs
  56. 42
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  57. 57
      src/Avalonia.Base/Threading/DispatcherFrame.cs
  58. 2
      src/Avalonia.Base/Threading/DispatcherOperation.cs
  59. 3
      src/Avalonia.Base/Threading/DispatcherPriority.cs
  60. 4
      src/Avalonia.Base/Visual.cs
  61. 5
      src/Avalonia.Base/VisualTree/IVisualWithRoundRectClip.cs
  62. 1
      src/Avalonia.Base/composition-schema.xml
  63. 17
      src/Avalonia.Controls/ComboBox.cs
  64. 4
      src/Avalonia.Controls/ContextMenu.cs
  65. 5
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  66. 7
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  67. 4
      src/Avalonia.Controls/Image.cs
  68. 3
      src/Avalonia.Controls/ItemsControl.cs
  69. 3
      src/Avalonia.Controls/Platform/Dialogs/ISystemDialogImpl.cs
  70. 3
      src/Avalonia.Controls/Platform/Dialogs/SystemDialogImpl.cs
  71. 6
      src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs
  72. 5
      src/Avalonia.Controls/Platform/Screen.cs
  73. 4
      src/Avalonia.Controls/Primitives/Popup.cs
  74. 15
      src/Avalonia.Controls/Primitives/Thumb.cs
  75. 19
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  76. 28
      src/Avalonia.Controls/Primitives/Track.cs
  77. 3
      src/Avalonia.Controls/Screens.cs
  78. 15
      src/Avalonia.Controls/SystemDialog.cs
  79. 26
      src/Avalonia.Controls/TextBlock.cs
  80. 3
      src/Avalonia.Controls/TreeView.cs
  81. 2
      src/Avalonia.Controls/TreeViewItem.cs
  82. 3
      src/Avalonia.Controls/Utils/StringUtils.cs
  83. 1
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  84. 5
      src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
  85. 2
      src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
  86. 13
      src/Avalonia.Native/AvaloniaNativeDragSource.cs
  87. 12
      src/Avalonia.Native/WindowImplBase.cs
  88. 7
      src/Avalonia.Native/avn.idl
  89. 4
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  90. 2
      src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts
  91. 7
      src/Directory.Build.props
  92. 13
      src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  93. 39
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  94. 118
      src/Skia/Avalonia.Skia/GlyphRunImpl.cs
  95. 30
      src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
  96. 1
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  97. 76
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  98. 31
      src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs
  99. 4
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  100. 2
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

28
Avalonia.sln

@ -262,9 +262,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo.iOS", "samples
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headless", "Headless", "{FF237916-7150-496B-89ED-6CA3292896E7}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headless", "Headless", "{FF237916-7150-496B-89ED-6CA3292896E7}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.UnitTests", "tests\Avalonia.Headless.UnitTests\Avalonia.Headless.UnitTests.csproj", "{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.UnitTests", "tests\Avalonia.Headless.UnitTests\Avalonia.Headless.UnitTests.csproj", "{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -605,14 +605,6 @@ Global
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.Build.0 = Release|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -637,6 +629,14 @@ Global
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Build.0 = Release|Any CPU {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Build.0 = Release|Any CPU
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Deploy.0 = Release|Any CPU {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Deploy.0 = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -681,6 +681,8 @@ Global
{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} {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}
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7}
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7}
{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}
{BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098} {BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@ -704,10 +706,6 @@ Global
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7}
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7}
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7}
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098} {A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@ -715,6 +713,8 @@ Global
{22E3BC08-EAF7-4889-BDC4-B4D3046C4E2D} = {9B9E3891-2366-4253-A952-D08BCEB71098} {22E3BC08-EAF7-4889-BDC4-B4D3046C4E2D} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{4CDAD037-34A2-4CCF-A03A-C6C7B988A572} = {9B9E3891-2366-4253-A952-D08BCEB71098} {4CDAD037-34A2-4CCF-A03A-C6C7B988A572} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD} = {9B9E3891-2366-4253-A952-D08BCEB71098} {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7}
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

32
build/ExternalConsumers.props

@ -0,0 +1,32 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<InternalsVisibleTo Include="AvaloniaUI.Xpf.WinApiShim, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a7f4b8b7db0bfb8d74992dc94ecafae031019197ff4263d87ac0a5835fab101c973ccab6fa6e7d90e8f987374f7c6de18dd0b5cd7d6c41e574a8bc66b64836b7c7e707e1aa393d27e33a08f372c1c9965be81658937c85698f4a1c0f73be68a61ffce06d49d1366bf18464c20a29859ccf105fc2d5e35c7ae68919eab668bf8e" />
<InternalsVisibleTo Include="System.Windows.Forms, PublicKey=00000000000000000400000000000000" />
<InternalsVisibleTo Include="System.Windows.Forms.Primitives, PublicKey=00000000000000000400000000000000" />
<InternalsVisibleTo Include="PresentationFramework-SystemData, PublicKey=00000000000000000400000000000000" />
<InternalsVisibleTo Include="PresentationFramework.Aero, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="System.Xaml, PublicKey=00000000000000000400000000000000" />
<InternalsVisibleTo Include="PresentationFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="PresentationFramework-SystemDrawing, PublicKey=00000000000000000400000000000000" />
<InternalsVisibleTo Include="PresentationFramework-SystemCore, PublicKey=00000000000000000400000000000000" />
<InternalsVisibleTo Include="WindowsFormsIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="PresentationFramework.AeroLite, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="PresentationFramework.Aero2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="UIAutomationClient, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="PresentationFramework.Luna, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="UIAutomationTypes, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="PresentationFramework.Royale, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="System.Windows.Input.Manipulations, PublicKey=00000000000000000400000000000000" />
<InternalsVisibleTo Include="System.Windows.Controls.Ribbon, PublicKey=00000000000000000400000000000000" />
<InternalsVisibleTo Include="PresentationFramework-SystemXml, PublicKey=00000000000000000400000000000000" />
<InternalsVisibleTo Include="ReachFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="System.Printing, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="PresentationFramework-SystemXmlLinq, PublicKey=00000000000000000400000000000000" />
<InternalsVisibleTo Include="PresentationUI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="Atlantis, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a7f4b8b7db0bfb8d74992dc94ecafae031019197ff4263d87ac0a5835fab101c973ccab6fa6e7d90e8f987374f7c6de18dd0b5cd7d6c41e574a8bc66b64836b7c7e707e1aa393d27e33a08f372c1c9965be81658937c85698f4a1c0f73be68a61ffce06d49d1366bf18464c20a29859ccf105fc2d5e35c7ae68919eab668bf8e" />
<InternalsVisibleTo Include="WindowsBase, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="PresentationFramework.Classic, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="UIAutomationProvider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
<InternalsVisibleTo Include="PresentationCore, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9" />
</ItemGroup>
</Project>

6
native/Avalonia.Native/src/OSX/AvnView.mm

@ -275,7 +275,7 @@
delta.Y = [event deltaY]; delta.Y = [event deltaY];
} }
uint32 timestamp = static_cast<uint32>([event timestamp] * 1000); uint64_t timestamp = static_cast<uint64_t>([event timestamp] * 1000);
auto modifiers = [self getModifiers:[event modifierFlags]]; auto modifiers = [self getModifiers:[event modifierFlags]];
if(type != Move || if(type != Move ||
@ -444,7 +444,7 @@
auto key = s_KeyMap[[event keyCode]]; auto key = s_KeyMap[[event keyCode]];
uint32_t timestamp = static_cast<uint32_t>([event timestamp] * 1000); uint64_t timestamp = static_cast<uint64_t>([event timestamp] * 1000);
auto modifiers = [self getModifiers:[event modifierFlags]]; auto modifiers = [self getModifiers:[event modifierFlags]];
if(_parent != nullptr) if(_parent != nullptr)
@ -657,7 +657,7 @@
[self unmarkText]; [self unmarkText];
uint32_t timestamp = static_cast<uint32_t>([NSDate timeIntervalSinceReferenceDate] * 1000); uint64_t timestamp = static_cast<uint64_t>([NSDate timeIntervalSinceReferenceDate] * 1000);
_lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(timestamp, [text UTF8String]); _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(timestamp, [text UTF8String]);

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

@ -460,7 +460,7 @@
auto point = [self translateLocalPoint:avnPoint]; auto point = [self translateLocalPoint:avnPoint];
AvnVector delta = { 0, 0 }; AvnVector delta = { 0, 0 };
_parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta); _parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint64>([event timestamp] * 1000), AvnInputModifiersNone, point, delta);
} }
if(!_isTransitioningToFullScreen) if(!_isTransitioningToFullScreen)

63
readme.md

@ -1,26 +1,43 @@
[![GH_Banner](https://user-images.githubusercontent.com/552074/218457976-92e76834-9e22-4e35-acfa-aa50281bc0f9.png)](https://avaloniaui.net/xpf) ![Star our repo to show support](https://user-images.githubusercontent.com/552074/235945895-1b896994-a0b6-4e7c-a522-c5688c4ec1b9.png)
![Header](https://user-images.githubusercontent.com/552074/235865745-2a8e7274-4f66-4f77-8f05-feeb76e7d478.png)
[![Telegram](https://raw.githubusercontent.com/Patrolavia/telegram-badge/master/chat.svg)](https://t.me/Avalonia) [![Telegram](https://raw.githubusercontent.com/Patrolavia/telegram-badge/master/chat.svg)](https://t.me/Avalonia)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![Discord](https://img.shields.io/badge/discord-join%20chat-46BC99)]( https://aka.ms/dotnet-discord) [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) ![License](https://img.shields.io/github/license/avaloniaui/avalonia.svg) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![Discord](https://img.shields.io/badge/discord-join%20chat-46BC99)]( https://aka.ms/dotnet-discord) [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) ![License](https://img.shields.io/github/license/avaloniaui/avalonia.svg)
<br /> <br />
[![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) [![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg)
# ⚠️ **v11 Update - Pausing community contributions** ⚠️ **v11 Update - [Pausing community contributions](https://github.com/AvaloniaUI/Avalonia/discussions/10599)**
for more information see [this](https://github.com/AvaloniaUI/Avalonia/discussions/10599) discussion.
## 📖 About ## 📖 About
Avalonia is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS. Avalonia is mature and production ready. We also have in beta release support for iOS, Android and in early stages support for browser via WASM. [Avalonia](https://avaloniaui.net) is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of platforms such as Windows, macOS, Linux, iOS, Android and WebAssembly. Avalonia is mature and production ready and is used by companies, including [Schneider Electric](https://avaloniaui.net/showcase#se), [Unity](https://avaloniaui.net/showcase#unity), [JetBrains](https://avaloniaui.net/showcase#rider) and [Github](https://avaloniaui.net/showcase#github).
Considered by many to be the spiritual successor to WPF, Avalonia UI provides a familiar, modern development experience for XAML developers creating cross-platform applications. While Avalonia UI is [similar to WPF](https://docs.avaloniaui.net/misc/wpf), it isn't a 1:1 copy, and you'll find plenty of improvements.
![image](https://user-images.githubusercontent.com/4672627/152126443-932966cf-57e7-4e77-9be6-62463a66b9f8.png) For those seeking a cross-platform WPF, we have created [Avalonia XPF](https://avaloniaui.net/xpf), enabling WPF applications to run on macOS and Linux with little to no code changes. Avalonia XPF is a commercial product and is licensed per-app, per-platform.
To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239). You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been. [Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia! #### Roadmap
To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239).
#### Breaking Changes
You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been.
#### Awesome Avalonia
[Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia!
## 🚀 Getting Started ## 🚀 Getting Started
See our [Get Started](https://avaloniaui.net/GettingStarted) guide to begin developing apps with Avalonia UI.
### Visual Studio
The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](https://docs.avaloniaui.net/docs/getting-started). The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](https://docs.avaloniaui.net/docs/getting-started).
### JetBrains Rider
[JetBrains Rider](https://www.jetbrains.com/rider/whatsnew/?mkt_tok=eyJpIjoiTURBNU1HSmhNV0kwTUdFMiIsInQiOiJtNnU2VEc1TlNLa1ZRVkROYmdZYVpYREJsaU1qdUhmS3dxSzRHczdYWHl0RVlTNDMwSFwvNUs3VENTNVM0bVcyNFdaRmVYZzVWTTF1N3VrQWNGTkJreEhlam1hMlB4UVVWcHBGM1dNOUxoXC95YnRQdGgyUXl1YmZCM3h3d3BVWWdBIn0%3D#avalonia-support) now has official support for Avalonia.
Code completion, inspections and refactorings are supported out of the box, for XAML previewer add `https://plugins.jetbrains.com/plugins/dev/14839` to plugin repositories and install [AvaloniaRider](https://github.com/ForNeVeR/AvaloniaRider) plugin.
### Avalonia Packages
Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/Avalonia/ Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/Avalonia/
Use these commands in the Package Manager console to install Avalonia manually: Use these commands in the Package Manager console to install Avalonia manually:
@ -30,31 +47,26 @@ Install-Package Avalonia.Desktop
``` ```
## Showcase ## Showcase
[![Showcase_Banner](https://user-images.githubusercontent.com/552074/235946124-bf6fda52-0c9f-4730-868b-0de957e5b97b.png)](https://avaloniaui.net/showcase)
Examples of UIs built with Avalonia
<video src="https://user-images.githubusercontent.com/4672627/152325602-28df36ec-6444-44a6-aebe-90ad52c8f27a.mp4"></video>
([Lunacy](https://icons8.com/lunacy))
![image](https://user-images.githubusercontent.com/4672627/152325740-261c27a3-e6f0-4662-bff7-4796d4940e04.png)
([PlasticSCM](https://www.plasticscm.com/))
![image](https://user-images.githubusercontent.com/4672627/152326453-14944c4d-33da-4d50-a268-b87f80927adb.png)
([WasabiWallet](https://www.wasabiwallet.io/))
See what others have built with Avalonia UI on our [Showcase](https://avaloniaui.net/Showcase). We welcome submissions!
## JetBrains Rider
[JetBrains Rider](https://www.jetbrains.com/rider/whatsnew/?mkt_tok=eyJpIjoiTURBNU1HSmhNV0kwTUdFMiIsInQiOiJtNnU2VEc1TlNLa1ZRVkROYmdZYVpYREJsaU1qdUhmS3dxSzRHczdYWHl0RVlTNDMwSFwvNUs3VENTNVM0bVcyNFdaRmVYZzVWTTF1N3VrQWNGTkJreEhlam1hMlB4UVVWcHBGM1dNOUxoXC95YnRQdGgyUXl1YmZCM3h3d3BVWWdBIn0%3D#avalonia-support) now has official support for Avalonia.
Code completion, inspections and refactorings are supported out of the box, for XAML previewer add `https://plugins.jetbrains.com/plugins/dev/14839` to plugin repositories and install [AvaloniaRider](https://github.com/ForNeVeR/AvaloniaRider) plugin.
## Bleeding Edge Builds ## Bleeding Edge Builds
We also have a [nightly build](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed) which tracks the current state of master. Although these packages are less stable than the release on NuGet.org, you'll get all the latest features and bugfixes right away and many of our users actually prefer this feed! We also have a [nightly build](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed) which tracks the current state of master. Although these packages are less stable than the release on NuGet.org, you'll get all the latest features and bugfixes right away and many of our users actually prefer this feed!
## Documentation ## Learning
Documentation can be found at https://docs.avaloniaui.net. We also have a [tutorial](https://docs.avaloniaui.net/docs/getting-started/programming-with-avalonia) over there for newcomers. ### Documentation
Documentation can be found at https://docs.avaloniaui.net.
### Tutorials
We also have a [tutorial](https://docs.avaloniaui.net/docs/getting-started/programming-with-avalonia) over there for newcomers.
### Samples
We have a [range of samples](https://github.com/AvaloniaUI/Avalonia.Samples) to help you get started.
## Building and Using ## Building and Using
@ -116,3 +128,8 @@ We have a range of [support plans available](https://avaloniaui.net/support) for
## .NET Foundation ## .NET Foundation
This project is supported by the [.NET Foundation](https://dotnetfoundation.org). This project is supported by the [.NET Foundation](https://dotnetfoundation.org).
## Avalonia XPF
Unleash the full potential of your existing WPF apps with our cross-platform UI framework, enabling WPF apps to run on macOS and Linux without requiring expensive and risky rewrites.
[![GH_Banner](https://user-images.githubusercontent.com/552074/218457976-92e76834-9e22-4e35-acfa-aa50281bc0f9.png)](https://avaloniaui.net/xpf)

15
samples/ControlCatalog/Pages/ComboBoxPage.xaml

@ -98,6 +98,21 @@
<ComboBoxItem>Inline Item 3</ComboBoxItem> <ComboBoxItem>Inline Item 3</ComboBoxItem>
<ComboBoxItem>Inline Item 4</ComboBoxItem> <ComboBoxItem>Inline Item 4</ComboBoxItem>
</ComboBox> </ComboBox>
<ComboBox WrapSelection="{Binding WrapSelection}" ItemsSource="{Binding Values}" DisplayMemberBinding="{Binding Name}">
</ComboBox>
<ComboBox WrapSelection="{Binding WrapSelection}" ItemsSource="{Binding Values}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding Id}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</WrapPanel> </WrapPanel>
<CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox> <CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox>

15
samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs

@ -16,5 +16,20 @@ namespace ControlCatalog.ViewModels
get => _wrapSelection; get => _wrapSelection;
set => this.RaiseAndSetIfChanged(ref _wrapSelection, value); set => this.RaiseAndSetIfChanged(ref _wrapSelection, value);
} }
public ObservableCollection<IdAndName> Values { get; set; } = new ObservableCollection<IdAndName>
{
new IdAndName(){ Id = "Id 1", Name = "Name 1" },
new IdAndName(){ Id = "Id 2", Name = "Name 2" },
new IdAndName(){ Id = "Id 3", Name = "Name 3" },
new IdAndName(){ Id = "Id 4", Name = "Name 4" },
new IdAndName(){ Id = "Id 5", Name = "Name 5" },
};
}
public class IdAndName
{
public string Id { get; set; }
public string Name { get; set; }
} }
} }

1
samples/Sandbox/MainWindow.axaml.cs

@ -1,6 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Input.TextInput;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Win32.WinRT.Composition; using Avalonia.Win32.WinRT.Composition;

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -22,6 +22,7 @@
<Import Project="..\..\build\SourceGenerators.props" /> <Import Project="..\..\build\SourceGenerators.props" />
<ItemGroup> <ItemGroup>
<Compile Include="..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" /> <Compile Include="..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" />
<Compile Include="..\Shared\ModuleInitializer.cs" Link="ModuleInitializer.cs" />
<Compile Include="..\Shared\StringCompatibilityExtensions.cs" Link="Compatibility\StringCompatibilityExtensions.cs" /> <Compile Include="..\Shared\StringCompatibilityExtensions.cs" Link="Compatibility\StringCompatibilityExtensions.cs" />
</ItemGroup> </ItemGroup>

3
src/Avalonia.Base/Data/BindingPriority.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
namespace Avalonia.Data namespace Avalonia.Data
{ {
@ -47,7 +48,7 @@ namespace Avalonia.Data
/// </summary> /// </summary>
Unset = int.MaxValue, Unset = int.MaxValue,
[Obsolete("Use Template priority")] [Obsolete("Use Template priority"), EditorBrowsable(EditorBrowsableState.Never)]
TemplatedParent = Template, TemplatedParent = Template,
} }
} }

2
src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs

@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary> /// <summary>
/// Validates properties on that have <see cref="ValidationAttribute"/>s. /// Validates properties on that have <see cref="ValidationAttribute"/>s.
/// </summary> /// </summary>
internal class DataAnnotationsValidationPlugin : IDataValidationPlugin public class DataAnnotationsValidationPlugin : IDataValidationPlugin
{ {
/// <inheritdoc/> /// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]

2
src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@ -7,7 +7,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary> /// <summary>
/// Validates properties that report errors by throwing exceptions. /// Validates properties that report errors by throwing exceptions.
/// </summary> /// </summary>
internal class ExceptionValidationPlugin : IDataValidationPlugin public class ExceptionValidationPlugin : IDataValidationPlugin
{ {
/// <inheritdoc/> /// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]

2
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary> /// <summary>
/// Validates properties on objects that implement <see cref="INotifyDataErrorInfo"/>. /// Validates properties on objects that implement <see cref="INotifyDataErrorInfo"/>.
/// </summary> /// </summary>
internal class IndeiValidationPlugin : IDataValidationPlugin public class IndeiValidationPlugin : IDataValidationPlugin
{ {
private static readonly WeakEvent<INotifyDataErrorInfo, DataErrorsChangedEventArgs> private static readonly WeakEvent<INotifyDataErrorInfo, DataErrorsChangedEventArgs>
ErrorsChangedWeakEvent = WeakEvent.Register<INotifyDataErrorInfo, DataErrorsChangedEventArgs>( ErrorsChangedWeakEvent = WeakEvent.Register<INotifyDataErrorInfo, DataErrorsChangedEventArgs>(

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

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using Avalonia.Reactive; using Avalonia.Reactive;
using ObservableEx = Avalonia.Reactive.Observable; using ObservableEx = Avalonia.Reactive.Observable;
@ -49,7 +50,7 @@ namespace Avalonia.Data
/// </summary> /// </summary>
public IObservable<object?> Source { get; } public IObservable<object?> Source { get; }
[Obsolete("Use Source property")] [Obsolete("Use Source property"), EditorBrowsable(EditorBrowsableState.Never)]
public IObservable<object?> Observable => Source; public IObservable<object?> Observable => Source;
/// <summary> /// <summary>

3
src/Avalonia.Base/Input/DataFormats.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
namespace Avalonia.Input namespace Avalonia.Input
{ {
@ -17,7 +18,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Dataformat for one or more filenames /// Dataformat for one or more filenames
/// </summary> /// </summary>
[Obsolete("Use DataFormats.Files, this format is supported only on desktop platforms.")] [Obsolete("Use DataFormats.Files, this format is supported only on desktop platforms."), EditorBrowsable(EditorBrowsableState.Never)]
public static readonly string FileNames = nameof(FileNames); public static readonly string FileNames = nameof(FileNames);
} }
} }

3
src/Avalonia.Base/Input/DataObjectExtensions.cs

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
@ -25,7 +26,7 @@ namespace Avalonia.Input
/// <returns> /// <returns>
/// Collection of file names. If format isn't available, returns null. /// Collection of file names. If format isn't available, returns null.
/// </returns> /// </returns>
[System.Obsolete("Use GetFiles, this method is supported only on desktop platforms.")] [System.Obsolete("Use GetFiles, this method is supported only on desktop platforms."), EditorBrowsable(EditorBrowsableState.Never)]
public static IEnumerable<string>? GetFileNames(this IDataObject dataObject) public static IEnumerable<string>? GetFileNames(this IDataObject dataObject)
{ {
return (dataObject.Get(DataFormats.FileNames) as IEnumerable<string>) return (dataObject.Get(DataFormats.FileNames) as IEnumerable<string>)

27
src/Avalonia.Base/Layout/LayoutManager.cs

@ -21,6 +21,7 @@ namespace Avalonia.Layout
private readonly Layoutable _owner; private readonly Layoutable _owner;
private readonly LayoutQueue<Layoutable> _toMeasure = new LayoutQueue<Layoutable>(v => !v.IsMeasureValid); private readonly LayoutQueue<Layoutable> _toMeasure = new LayoutQueue<Layoutable>(v => !v.IsMeasureValid);
private readonly LayoutQueue<Layoutable> _toArrange = new LayoutQueue<Layoutable>(v => !v.IsArrangeValid); private readonly LayoutQueue<Layoutable> _toArrange = new LayoutQueue<Layoutable>(v => !v.IsArrangeValid);
private readonly List<Layoutable> _toArrangeAfterMeasure = new();
private readonly Action _executeLayoutPass; private readonly Action _executeLayoutPass;
private List<EffectiveViewportChangedListener>? _effectiveViewportChangedListeners; private List<EffectiveViewportChangedListener>? _effectiveViewportChangedListeners;
private bool _disposed; private bool _disposed;
@ -266,9 +267,14 @@ namespace Avalonia.Layout
if (!control.IsArrangeValid) if (!control.IsArrangeValid)
{ {
Arrange(control); if (Arrange(control) == ArrangeResult.AncestorMeasureInvalid)
_toArrangeAfterMeasure.Add(control);
} }
} }
foreach (var i in _toArrangeAfterMeasure)
InvalidateArrange(i);
_toArrangeAfterMeasure.Clear();
} }
private bool Measure(Layoutable control) private bool Measure(Layoutable control)
@ -304,19 +310,19 @@ namespace Avalonia.Layout
return true; return true;
} }
private bool Arrange(Layoutable control) private ArrangeResult Arrange(Layoutable control)
{ {
if (!control.IsVisible || !control.IsAttachedToVisualTree) if (!control.IsVisible || !control.IsAttachedToVisualTree)
return false; return ArrangeResult.NotVisible;
if (control.VisualParent is Layoutable parent) if (control.VisualParent is Layoutable parent)
{ {
if (!Arrange(parent)) if (Arrange(parent) is var parentResult && parentResult != ArrangeResult.Arranged)
return false; return parentResult;
} }
if (!control.IsMeasureValid) if (!control.IsMeasureValid)
return false; return ArrangeResult.AncestorMeasureInvalid;
if (!control.IsArrangeValid) if (!control.IsArrangeValid)
{ {
@ -332,7 +338,7 @@ namespace Avalonia.Layout
} }
} }
return true; return ArrangeResult.Arranged;
} }
private void QueueLayoutPass() private void QueueLayoutPass()
@ -435,5 +441,12 @@ namespace Avalonia.Layout
public Layoutable Listener { get; } public Layoutable Listener { get; }
public Rect Viewport { get; set; } public Rect Viewport { get; set; }
} }
private enum ArrangeResult
{
Arranged,
NotVisible,
AncestorMeasureInvalid,
}
} }
} }

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

@ -6,6 +6,7 @@
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System; using System;
using System.ComponentModel;
using System.Globalization; using System.Globalization;
#if !BUILDTASK #if !BUILDTASK
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
@ -465,7 +466,7 @@ namespace Avalonia.Media
} }
/// <inheritdoc cref="Color.ToUInt32"/> /// <inheritdoc cref="Color.ToUInt32"/>
[Obsolete("Use Color.ToUInt32() instead.")] [Obsolete("Use Color.ToUInt32() instead."), EditorBrowsable(EditorBrowsableState.Never)]
public uint ToUint32() public uint ToUint32()
{ {
return ToUInt32(); return ToUInt32();

33
src/Avalonia.Base/Media/DrawingContext.cs

@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Media.Imaging;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -53,12 +53,10 @@ namespace Avalonia.Media
/// <param name="source">The image.</param> /// <param name="source">The image.</param>
/// <param name="sourceRect">The rect in the image to draw.</param> /// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param> /// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param> public virtual void DrawImage(IImage source, Rect sourceRect, Rect destRect)
public virtual void DrawImage(IImage source, Rect sourceRect, Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode = default)
{ {
_ = source ?? throw new ArgumentNullException(nameof(source)); _ = source ?? throw new ArgumentNullException(nameof(source));
source.Draw(this, sourceRect, destRect, bitmapInterpolationMode); source.Draw(this, sourceRect, destRect);
} }
/// <summary> /// <summary>
@ -68,8 +66,7 @@ namespace Avalonia.Media
/// <param name="opacity">The opacity to draw with.</param> /// <param name="opacity">The opacity to draw with.</param>
/// <param name="sourceRect">The rect in the image to draw.</param> /// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param> /// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param> internal abstract void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect);
internal abstract void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
/// <summary> /// <summary>
/// Draws a line. /// Draws a line.
@ -286,8 +283,7 @@ namespace Avalonia.Media
Opacity, Opacity,
Clip, Clip,
GeometryClip, GeometryClip,
OpacityMask, OpacityMask
BitmapBlendMode
} }
public RestoreState(DrawingContext context, PushedStateType type) public RestoreState(DrawingContext context, PushedStateType type)
@ -312,8 +308,6 @@ namespace Avalonia.Media
_context.PopGeometryClipCore(); _context.PopGeometryClipCore();
else if (_type == PushedStateType.OpacityMask) else if (_type == PushedStateType.OpacityMask)
_context.PopOpacityMaskCore(); _context.PopOpacityMaskCore();
else if (_type == PushedStateType.BitmapBlendMode)
_context.PopBitmapBlendModeCore();
} }
} }
@ -394,16 +388,6 @@ namespace Avalonia.Media
} }
protected abstract void PushOpacityMaskCore(IBrush mask, Rect bounds); protected abstract void PushOpacityMaskCore(IBrush mask, Rect bounds);
public PushedState PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
PushBitmapBlendMode(blendingMode);
_states ??= StateStackPool.Get();
_states.Push(new RestoreState(this, RestoreState.PushedStateType.BitmapBlendMode));
return new PushedState(this);
}
protected abstract void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode);
/// <summary> /// <summary>
/// Pushes a matrix transformation. /// Pushes a matrix transformation.
/// </summary> /// </summary>
@ -417,11 +401,11 @@ namespace Avalonia.Media
return new PushedState(this); return new PushedState(this);
} }
[Obsolete("Use PushTransform")] [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)]
public PushedState PushPreTransform(Matrix matrix) => PushTransform(matrix); public PushedState PushPreTransform(Matrix matrix) => PushTransform(matrix);
[Obsolete("Use PushTransform")] [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)]
public PushedState PushPostTransform(Matrix matrix) => PushTransform(matrix); public PushedState PushPostTransform(Matrix matrix) => PushTransform(matrix);
[Obsolete("Use PushTransform")] [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)]
public PushedState PushTransformContainer() => PushTransform(Matrix.Identity); public PushedState PushTransformContainer() => PushTransform(Matrix.Identity);
@ -431,7 +415,6 @@ namespace Avalonia.Media
protected abstract void PopGeometryClipCore(); protected abstract void PopGeometryClipCore();
protected abstract void PopOpacityCore(); protected abstract void PopOpacityCore();
protected abstract void PopOpacityMaskCore(); protected abstract void PopOpacityMaskCore();
protected abstract void PopBitmapBlendModeCore();
protected abstract void PopTransformCore(); protected abstract void PopTransformCore();
private static bool PenIsVisible(IPen? pen) private static bool PenIsVisible(IPen? pen)

10
src/Avalonia.Base/Media/DrawingGroup.cs

@ -196,13 +196,7 @@ namespace Avalonia.Media
throw new NotImplementedException(); throw new NotImplementedException();
} }
protected override void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode) internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{
throw new NotImplementedException();
}
internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -321,8 +315,6 @@ namespace Avalonia.Media
protected override void PopOpacityMaskCore() => Pop(); protected override void PopOpacityMaskCore() => Pop();
protected override void PopBitmapBlendModeCore() => Pop();
protected override void PopTransformCore() => Pop(); protected override void PopTransformCore() => Pop();
/// <summary> /// <summary>

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

@ -1,6 +1,5 @@
using System; using System;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Media.Imaging;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -43,8 +42,7 @@ namespace Avalonia.Media
void IImage.Draw( void IImage.Draw(
DrawingContext context, DrawingContext context,
Rect sourceRect, Rect sourceRect,
Rect destRect, Rect destRect)
BitmapInterpolationMode bitmapInterpolationMode)
{ {
var drawing = Drawing; var drawing = Drawing;

10
src/Avalonia.Base/Media/EdgeMode.cs

@ -0,0 +1,10 @@
namespace Avalonia.Media
{
public enum EdgeMode : byte
{
Unspecified,
Antialias,
Aliased
}
}

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

@ -153,7 +153,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets the conservative bounding box of the <see cref="GlyphRun"/>. /// Gets the conservative bounding box of the <see cref="GlyphRun"/>.
/// </summary> /// </summary>
public Rect Bounds => PlatformImpl.Item.Bounds; public Rect Bounds => new Rect(new Size(Metrics.WidthIncludingTrailingWhitespace, Metrics.Height));
/// <summary> /// <summary>
/// ///
@ -166,7 +166,7 @@ namespace Avalonia.Media
/// </summary> /// </summary>
public Point BaselineOrigin public Point BaselineOrigin
{ {
get => PlatformImpl.Item.BaselineOrigin; get => _baselineOrigin ?? new Point(0, Metrics.Baseline);
set => Set(ref _baselineOrigin, value); set => Set(ref _baselineOrigin, value);
} }
@ -676,13 +676,17 @@ namespace Avalonia.Media
} }
} }
return new GlyphRunMetrics( return new GlyphRunMetrics
width, {
trailingWhitespaceLength, Baseline = -GlyphTypeface.Metrics.Ascent * Scale,
newLineLength, Width = width,
firstCluster, WidthIncludingTrailingWhitespace = widthIncludingTrailingWhitespace,
lastCluster Height = height,
); NewLineLength = newLineLength,
TrailingWhitespaceLength = trailingWhitespaceLength,
FirstCluster = firstCluster,
LastCluster = lastCluster
};
} }
private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount) private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount)
@ -820,10 +824,11 @@ namespace Avalonia.Media
private IRef<IGlyphRunImpl> CreateGlyphRunImpl() private IRef<IGlyphRunImpl> CreateGlyphRunImpl()
{ {
var platformImpl = s_renderInterface.CreateGlyphRun( var platformImpl = s_renderInterface.CreateGlyphRun(
GlyphTypeface, GlyphTypeface,
FontRenderingEmSize, FontRenderingEmSize,
GlyphInfos, GlyphInfos,
_baselineOrigin ?? new Point(0, -GlyphTypeface.Metrics.Ascent * Scale)); BaselineOrigin,
Bounds);
_platformImpl = RefCountable.Create(platformImpl); _platformImpl = RefCountable.Create(platformImpl);
@ -835,5 +840,16 @@ namespace Avalonia.Media
_platformImpl?.Dispose(); _platformImpl?.Dispose();
_platformImpl = null; _platformImpl = null;
} }
/// <summary>
/// Gets the intersections of specified upper and lower limit.
/// </summary>
/// <param name="lowerLimit">Upper limit.</param>
/// <param name="upperLimit">Lower limit.</param>
/// <returns></returns>
public IReadOnlyList<float> GetIntersections(float lowerLimit, float upperLimit)
{
return PlatformImpl.Item.GetIntersections(lowerLimit, upperLimit);
}
} }
} }

23
src/Avalonia.Base/Media/GlyphRunMetrics.cs

@ -2,23 +2,20 @@
{ {
public readonly record struct GlyphRunMetrics public readonly record struct GlyphRunMetrics
{ {
public GlyphRunMetrics(double width, int trailingWhitespaceLength, int newLineLength, int firstCluster, int lastCluster) public double Baseline { get; init; }
{
Width = width;
TrailingWhitespaceLength = trailingWhitespaceLength;
NewLineLength = newLineLength;
FirstCluster = firstCluster;
LastCluster = lastCluster;
}
public double Width { get; } public double Width { get; init; }
public int TrailingWhitespaceLength { get; } public double WidthIncludingTrailingWhitespace { get; init; }
public int NewLineLength { get; } public double Height { get; init; }
public int FirstCluster { get; } public int TrailingWhitespaceLength { get; init; }
public int LastCluster { get; } public int NewLineLength { get; init; }
public int FirstCluster { get; init; }
public int LastCluster { get; init; }
} }
} }

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

@ -18,11 +18,9 @@ namespace Avalonia.Media
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
/// <param name="sourceRect">The rect in the image to draw.</param> /// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param> /// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
void Draw( void Draw(
DrawingContext context, DrawingContext context,
Rect sourceRect, Rect sourceRect,
Rect destRect, Rect destRect);
BitmapInterpolationMode bitmapInterpolationMode);
} }
} }

8
src/Avalonia.Base/Media/ITileBrush.cs

@ -39,13 +39,5 @@ namespace Avalonia.Media
/// Gets the brush's tile mode. /// Gets the brush's tile mode.
/// </summary> /// </summary>
TileMode TileMode { get; } TileMode TileMode { get; }
/// <summary>
/// Gets the bitmap interpolation mode.
/// </summary>
/// <value>
/// The bitmap interpolation mode.
/// </value>
BitmapInterpolationMode BitmapInterpolationMode { get; }
} }
} }

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

@ -224,15 +224,13 @@ namespace Avalonia.Media.Imaging
void IImage.Draw( void IImage.Draw(
DrawingContext context, DrawingContext context,
Rect sourceRect, Rect sourceRect,
Rect destRect, Rect destRect)
BitmapInterpolationMode bitmapInterpolationMode)
{ {
context.DrawBitmap( context.DrawBitmap(
PlatformImpl, PlatformImpl,
1, 1,
sourceRect, sourceRect,
destRect, destRect);
bitmapInterpolationMode);
} }
private static IPlatformRenderInterface GetFactory() private static IPlatformRenderInterface GetFactory()

6
src/Avalonia.Base/Media/Imaging/BitmapBlendingMode.cs

@ -3,8 +3,10 @@ namespace Avalonia.Media.Imaging
/// <summary> /// <summary>
/// Controls the way the bitmaps are drawn together. /// Controls the way the bitmaps are drawn together.
/// </summary> /// </summary>
public enum BitmapBlendingMode public enum BitmapBlendingMode : byte
{ {
Unspecified,
/// <summary> /// <summary>
/// Source is placed over the destination. /// Source is placed over the destination.
/// </summary> /// </summary>
@ -52,6 +54,6 @@ namespace Avalonia.Media.Imaging
/// <summary> /// <summary>
/// Display the sum of the source image and destination image. /// Display the sum of the source image and destination image.
/// </summary> /// </summary>
Plus, Plus
} }
} }

10
src/Avalonia.Base/Media/Imaging/BitmapInterpolationMode.cs

@ -3,12 +3,14 @@
/// <summary> /// <summary>
/// Controls the performance and quality of bitmap scaling. /// Controls the performance and quality of bitmap scaling.
/// </summary> /// </summary>
public enum BitmapInterpolationMode public enum BitmapInterpolationMode : byte
{ {
Unspecified,
/// <summary> /// <summary>
/// Uses the default behavior of the underling render backend. /// Disable interpolation.
/// </summary> /// </summary>
Default, None,
/// <summary> /// <summary>
/// The best performance but worst image quality. /// The best performance but worst image quality.
@ -18,7 +20,7 @@
/// <summary> /// <summary>
/// Good performance and decent image quality. /// Good performance and decent image quality.
/// </summary> /// </summary>
MediumQuality, MediumQuality,
/// <summary> /// <summary>
/// Highest quality but worst performance. /// Highest quality but worst performance.

4
src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs

@ -83,12 +83,12 @@ namespace Avalonia.Media.Imaging
} }
} }
public void Draw(DrawingContext context, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) public void Draw(DrawingContext context, Rect sourceRect, Rect destRect)
{ {
if (Source is not IBitmap bmp) if (Source is not IBitmap bmp)
return; return;
var topLeft = SourceRect.TopLeft.ToPointWithDpi(bmp.Dpi); var topLeft = SourceRect.TopLeft.ToPointWithDpi(bmp.Dpi);
Source.Draw(context, sourceRect.Translate(new Vector(topLeft.X, topLeft.Y)), destRect, bitmapInterpolationMode); Source.Draw(context, sourceRect.Translate(new Vector(topLeft.X, topLeft.Y)), destRect);
} }
} }
} }

8
src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs

@ -9,7 +9,7 @@ namespace Avalonia.Media.Imaging
/// <summary> /// <summary>
/// A bitmap that holds the rendering of a <see cref="Visual"/>. /// A bitmap that holds the rendering of a <see cref="Visual"/>.
/// </summary> /// </summary>
public class RenderTargetBitmap : Bitmap, IDisposable public class RenderTargetBitmap : Bitmap
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RenderTargetBitmap"/> class. /// Initializes a new instance of the <see cref="RenderTargetBitmap"/> class.
@ -68,5 +68,11 @@ namespace Avalonia.Media.Imaging
platform.Clear(Colors.Transparent); platform.Clear(Colors.Transparent);
return new PlatformDrawingContext(platform); return new PlatformDrawingContext(platform);
} }
public override void Dispose()
{
PlatformImpl.Dispose();
base.Dispose();
}
} }
} }

5
src/Avalonia.Base/Media/ImmediateDrawingContext.cs

@ -79,11 +79,10 @@ namespace Avalonia.Media
/// <param name="source">The bitmap.</param> /// <param name="source">The bitmap.</param>
/// <param name="sourceRect">The rect in the image to draw.</param> /// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param> /// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param> public void DrawBitmap(IBitmap source, Rect sourceRect, Rect destRect)
public void DrawBitmap(IBitmap source, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default)
{ {
_ = source ?? throw new ArgumentNullException(nameof(source)); _ = source ?? throw new ArgumentNullException(nameof(source));
PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect, bitmapInterpolationMode); PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect);
} }
/// <summary> /// <summary>

7
src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs

@ -22,7 +22,6 @@ namespace Avalonia.Media.Immutable
/// How the source rectangle will be stretched to fill the destination rect. /// How the source rectangle will be stretched to fill the destination rect.
/// </param> /// </param>
/// <param name="tileMode">The tile mode.</param> /// <param name="tileMode">The tile mode.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public ImmutableImageBrush( public ImmutableImageBrush(
IBitmap? source, IBitmap? source,
AlignmentX alignmentX = AlignmentX.Center, AlignmentX alignmentX = AlignmentX.Center,
@ -33,8 +32,7 @@ namespace Avalonia.Media.Immutable
RelativePoint transformOrigin = default, RelativePoint transformOrigin = default,
RelativeRect? sourceRect = null, RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform, Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None, TileMode tileMode = TileMode.None)
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
: base( : base(
alignmentX, alignmentX,
alignmentY, alignmentY,
@ -44,8 +42,7 @@ namespace Avalonia.Media.Immutable
transformOrigin, transformOrigin,
sourceRect ?? RelativeRect.Fill, sourceRect ?? RelativeRect.Fill,
stretch, stretch,
tileMode, tileMode)
bitmapInterpolationMode)
{ {
Source = source; Source = source;
} }

11
src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs

@ -21,7 +21,6 @@ namespace Avalonia.Media.Immutable
/// How the source rectangle will be stretched to fill the destination rect. /// How the source rectangle will be stretched to fill the destination rect.
/// </param> /// </param>
/// <param name="tileMode">The tile mode.</param> /// <param name="tileMode">The tile mode.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
protected ImmutableTileBrush( protected ImmutableTileBrush(
AlignmentX alignmentX, AlignmentX alignmentX,
AlignmentY alignmentY, AlignmentY alignmentY,
@ -31,8 +30,7 @@ namespace Avalonia.Media.Immutable
RelativePoint transformOrigin, RelativePoint transformOrigin,
RelativeRect sourceRect, RelativeRect sourceRect,
Stretch stretch, Stretch stretch,
TileMode tileMode, TileMode tileMode)
BitmapInterpolationMode bitmapInterpolationMode)
{ {
AlignmentX = alignmentX; AlignmentX = alignmentX;
AlignmentY = alignmentY; AlignmentY = alignmentY;
@ -43,7 +41,6 @@ namespace Avalonia.Media.Immutable
SourceRect = sourceRect; SourceRect = sourceRect;
Stretch = stretch; Stretch = stretch;
TileMode = tileMode; TileMode = tileMode;
BitmapInterpolationMode = bitmapInterpolationMode;
} }
/// <summary> /// <summary>
@ -60,8 +57,7 @@ namespace Avalonia.Media.Immutable
source.TransformOrigin, source.TransformOrigin,
source.SourceRect, source.SourceRect,
source.Stretch, source.Stretch,
source.TileMode, source.TileMode)
source.BitmapInterpolationMode)
{ {
} }
@ -95,8 +91,5 @@ namespace Avalonia.Media.Immutable
/// <inheritdoc/> /// <inheritdoc/>
public TileMode TileMode { get; } public TileMode TileMode { get; }
/// <inheritdoc/>
public BitmapInterpolationMode BitmapInterpolationMode { get; }
} }
} }

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

@ -26,6 +26,12 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi
_ownsImpl = ownsImpl; _ownsImpl = ownsImpl;
} }
public RenderOptions RenderOptions
{
get => _impl.RenderOptions;
set => _impl.RenderOptions = value;
}
protected override void DrawLineCore(IPen pen, Point p1, Point p2) => protected override void DrawLineCore(IPen pen, Point p1, Point p2) =>
_impl.DrawLine(pen, p1, p2); _impl.DrawLine(pen, p1, p2);
@ -38,9 +44,8 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi
protected override void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect) => _impl.DrawEllipse(brush, pen, rect); protected override void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect) => _impl.DrawEllipse(brush, pen, rect);
internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect) =>
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) => _impl.DrawBitmap(source, opacity, sourceRect, destRect);
_impl.DrawBitmap(source, opacity, sourceRect, destRect, bitmapInterpolationMode);
public override void Custom(ICustomDrawOperation custom) public override void Custom(ICustomDrawOperation custom)
{ {
@ -77,9 +82,6 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi
protected override void PushOpacityMaskCore(IBrush mask, Rect bounds) => protected override void PushOpacityMaskCore(IBrush mask, Rect bounds) =>
_impl.PushOpacityMask(mask, bounds); _impl.PushOpacityMask(mask, bounds);
protected override void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode) =>
_impl.PushBitmapBlendMode(blendingMode);
protected override void PushTransformCore(Matrix matrix) protected override void PushTransformCore(Matrix matrix)
{ {
_transforms ??= TransformStackPool.Get(); _transforms ??= TransformStackPool.Get();
@ -96,8 +98,6 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi
protected override void PopOpacityMaskCore() => _impl.PopOpacityMask(); protected override void PopOpacityMaskCore() => _impl.PopOpacityMask();
protected override void PopBitmapBlendModeCore() => _impl.PopBitmapBlendMode();
protected override void PopTransformCore() => protected override void PopTransformCore() =>
_impl.Transform = _impl.Transform =
(_transforms ?? throw new ObjectDisposedException(nameof(PlatformDrawingContext))).Pop(); (_transforms ?? throw new ObjectDisposedException(nameof(PlatformDrawingContext))).Pop();

127
src/Avalonia.Base/Media/RenderOptions.cs

@ -1,36 +1,131 @@
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
namespace Avalonia.Media namespace Avalonia.Media
{ {
public class RenderOptions public readonly record struct RenderOptions
{ {
public BitmapInterpolationMode BitmapInterpolationMode { get; init; }
public EdgeMode EdgeMode { get; init; }
public TextRenderingMode TextRenderingMode { get; init; }
public BitmapBlendingMode BitmapBlendingMode { get; init; }
/// <summary>
/// Gets the value of the BitmapInterpolationMode attached property for a visual.
/// </summary>
/// <param name="visual">The control.</param>
/// <returns>The control's left coordinate.</returns>
public static BitmapInterpolationMode GetBitmapInterpolationMode(Visual visual)
{
return visual.RenderOptions.BitmapInterpolationMode;
}
/// <summary> /// <summary>
/// Defines the <see cref="BitmapInterpolationMode"/> property. /// Sets the value of the BitmapInterpolationMode attached property for a visual.
/// </summary> /// </summary>
public static readonly StyledProperty<BitmapInterpolationMode> BitmapInterpolationModeProperty = /// <param name="visual">The control.</param>
AvaloniaProperty.RegisterAttached<RenderOptions, AvaloniaObject, BitmapInterpolationMode>( /// <param name="value">The left value.</param>
"BitmapInterpolationMode", public static void SetBitmapInterpolationMode(Visual visual, BitmapInterpolationMode value)
BitmapInterpolationMode.MediumQuality, {
inherits: true); visual.RenderOptions = visual.RenderOptions with { BitmapInterpolationMode = value };
}
/// <summary> /// <summary>
/// Gets the value of the BitmapInterpolationMode attached property for a control. /// Gets the value of the BitmapBlendingMode attached property for a visual.
/// </summary> /// </summary>
/// <param name="element">The control.</param> /// <param name="visual">The control.</param>
/// <returns>The control's left coordinate.</returns> /// <returns>The control's left coordinate.</returns>
public static BitmapInterpolationMode GetBitmapInterpolationMode(AvaloniaObject element) public static BitmapBlendingMode GetBitmapBlendingMode(Visual visual)
{ {
return element.GetValue(BitmapInterpolationModeProperty); return visual.RenderOptions.BitmapBlendingMode;
} }
/// <summary> /// <summary>
/// Sets the value of the BitmapInterpolationMode attached property for a control. /// Sets the value of the BitmapBlendingMode attached property for a visual.
/// </summary> /// </summary>
/// <param name="element">The control.</param> /// <param name="visual">The control.</param>
/// <param name="value">The left value.</param> /// <param name="value">The left value.</param>
public static void SetBitmapInterpolationMode(AvaloniaObject element, BitmapInterpolationMode value) public static void SetBitmapBlendingMode(Visual visual, BitmapBlendingMode value)
{ {
element.SetValue(BitmapInterpolationModeProperty, value); visual.RenderOptions = visual.RenderOptions with { BitmapBlendingMode = value };
}
/// <summary>
/// Gets the value of the EdgeMode attached property for a visual.
/// </summary>
/// <param name="visual">The control.</param>
/// <returns>The control's left coordinate.</returns>
public static EdgeMode GetEdgeMode(Visual visual)
{
return visual.RenderOptions.EdgeMode;
}
/// <summary>
/// Sets the value of the EdgeMode attached property for a visual.
/// </summary>
/// <param name="visual">The control.</param>
/// <param name="value">The left value.</param>
public static void SetEdgeMode(Visual visual, EdgeMode value)
{
visual.RenderOptions = visual.RenderOptions with { EdgeMode = value };
}
/// <summary>
/// Gets the value of the TextRenderingMode attached property for a visual.
/// </summary>
/// <param name="visual">The control.</param>
/// <returns>The control's left coordinate.</returns>
public static TextRenderingMode GetTextRenderingMode(Visual visual)
{
return visual.RenderOptions.TextRenderingMode;
}
/// <summary>
/// Sets the value of the TextRenderingMode attached property for a visual.
/// </summary>
/// <param name="visual">The control.</param>
/// <param name="value">The left value.</param>
public static void SetTextRenderingMode(Visual visual, TextRenderingMode value)
{
visual.RenderOptions = visual.RenderOptions with { TextRenderingMode = value };
}
public RenderOptions MergeWith(RenderOptions other)
{
var bitmapInterpolationMode = BitmapInterpolationMode;
if (bitmapInterpolationMode == BitmapInterpolationMode.Unspecified)
{
bitmapInterpolationMode = other.BitmapInterpolationMode;
}
var edgeMode = EdgeMode;
if (edgeMode == EdgeMode.Unspecified)
{
edgeMode = other.EdgeMode;
}
var textRenderingMode = TextRenderingMode;
if (textRenderingMode == TextRenderingMode.Unspecified)
{
textRenderingMode = other.TextRenderingMode;
}
var bitmapBlendingMode = BitmapBlendingMode;
if (bitmapBlendingMode == BitmapBlendingMode.Unspecified)
{
bitmapBlendingMode = other.BitmapBlendingMode;
}
return new RenderOptions
{
BitmapInterpolationMode = bitmapInterpolationMode,
EdgeMode = edgeMode,
TextRenderingMode = textRenderingMode,
BitmapBlendingMode = bitmapBlendingMode
};
} }
} }
} }

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

@ -1,10 +1,6 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Collections.Pooled;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -218,7 +214,7 @@ namespace Avalonia.Media
{ {
var offsetY = glyphRun.BaselineOrigin.Y - origin.Y; var offsetY = glyphRun.BaselineOrigin.Y - origin.Y;
var intersections = glyphRun.PlatformImpl.Item.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY)); var intersections = glyphRun.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY));
if (intersections.Count > 0) if (intersections.Count > 0)
{ {

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

@ -185,7 +185,9 @@ namespace Avalonia.Media.TextFormatting
} }
//Stop at the first missing glyph //Stop at the first missing glyph
if (!currentCodepoint.IsBreakChar && !glyphTypeface.TryGetGlyph(currentCodepoint, out _)) if (!currentCodepoint.IsBreakChar &&
currentCodepoint.GeneralCategory != GeneralCategory.Control &&
!glyphTypeface.TryGetGlyph(currentCodepoint, out _))
{ {
break; break;
} }

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

@ -83,7 +83,7 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/> /// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point lineOrigin) public override void Draw(DrawingContext drawingContext, Point lineOrigin)
{ {
var (currentX, currentY) = lineOrigin; var (currentX, currentY) = lineOrigin + new Point(Start, 0);
foreach (var textRun in _textRuns) foreach (var textRun in _textRuns)
{ {
@ -698,7 +698,7 @@ namespace Avalonia.Media.TextFormatting
i = lastRunIndex; i = lastRunIndex;
//Possible overlap at runs of different direction //Possible overlap at runs of different direction
if (directionalWidth == 0) if (directionalWidth == 0 && i < _textRuns.Length - 1)
{ {
//In case a run only contains a linebreak we don't want to skip it. //In case a run only contains a linebreak we don't want to skip it.
if (currentRun is ShapedTextRun shaped) if (currentRun is ShapedTextRun shaped)
@ -844,7 +844,7 @@ namespace Avalonia.Media.TextFormatting
i = firstRunIndex; i = firstRunIndex;
//Possible overlap at runs of different direction //Possible overlap at runs of different direction
if (directionalWidth == 0) if (directionalWidth == 0 && i > 0)
{ {
//In case a run only contains a linebreak we don't want to skip it. //In case a run only contains a linebreak we don't want to skip it.
if (currentRun is ShapedTextRun shaped) if (currentRun is ShapedTextRun shaped)
@ -860,8 +860,8 @@ namespace Avalonia.Media.TextFormatting
} }
} }
TextBounds? textBounds = null;
int coveredLength; int coveredLength;
TextBounds? textBounds;
switch (currentDirection) switch (currentDirection)
{ {
@ -942,6 +942,13 @@ namespace Avalonia.Media.TextFormatting
new TextRunBounds( new TextRunBounds(
new Rect(startX, 0, drawableTextRun.Size.Width, Height), currentPosition, currentRun.Length, currentRun)); new Rect(startX, 0, drawableTextRun.Size.Width, Height), currentPosition, currentRun.Length, currentRun));
} }
else
{
//Add potential TextEndOfParagraph
textRunBounds.Add(
new TextRunBounds(
new Rect(endX, 0, 0, Height), currentPosition, currentRun.Length, currentRun));
}
currentPosition += currentRun.Length; currentPosition += currentRun.Length;
@ -1007,6 +1014,13 @@ namespace Avalonia.Media.TextFormatting
endX += drawableTextRun.Size.Width; endX += drawableTextRun.Size.Width;
} }
else
{
//Add potential TextEndOfParagraph
textRunBounds.Add(
new TextRunBounds(
new Rect(endX, 0, 0, Height), currentPosition, currentRun.Length, currentRun));
}
currentPosition += currentRun.Length; currentPosition += currentRun.Length;
@ -1409,8 +1423,6 @@ namespace Avalonia.Media.TextFormatting
var fontMetrics = _paragraphProperties.DefaultTextRunProperties.CachedGlyphTypeface.Metrics; var fontMetrics = _paragraphProperties.DefaultTextRunProperties.CachedGlyphTypeface.Metrics;
var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize; var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight; var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight;
var width = 0d;
var widthIncludingWhitespace = 0d; var widthIncludingWhitespace = 0d;
var trailingWhitespaceLength = 0; var trailingWhitespaceLength = 0;
var newLineLength = 0; var newLineLength = 0;
@ -1422,13 +1434,6 @@ namespace Avalonia.Media.TextFormatting
var lineHeight = _paragraphProperties.LineHeight; var lineHeight = _paragraphProperties.LineHeight;
var lastRunIndex = _textRuns.Length - 1;
if (lastRunIndex > 0 && _textRuns[lastRunIndex] is TextEndOfLine)
{
lastRunIndex--;
}
for (var index = 0; index < _textRuns.Length; index++) for (var index = 0; index < _textRuns.Length; index++)
{ {
switch (_textRuns[index]) switch (_textRuns[index])
@ -1486,7 +1491,7 @@ namespace Avalonia.Media.TextFormatting
} }
} }
width = widthIncludingWhitespace; var width = widthIncludingWhitespace;
for (var i = _textRuns.Length - 1; i >= 0; i--) for (var i = _textRuns.Length - 1; i >= 0; i--)
{ {

11
src/Avalonia.Base/Media/TextRenderingMode.cs

@ -0,0 +1,11 @@
namespace Avalonia.Media
{
public enum TextRenderingMode : byte
{
Unspecified,
SubpixelAntialias,
Antialias,
Alias
}
}

13
src/Avalonia.Base/Media/TileBrush.cs

@ -83,7 +83,6 @@ namespace Avalonia.Media
SourceRectProperty, SourceRectProperty,
StretchProperty, StretchProperty,
TileModeProperty); TileModeProperty);
RenderOptions.BitmapInterpolationModeProperty.OverrideDefaultValue<TileBrush>(BitmapInterpolationMode.Default);
} }
/// <summary> /// <summary>
@ -140,17 +139,5 @@ namespace Avalonia.Media
get { return (TileMode)GetValue(TileModeProperty); } get { return (TileMode)GetValue(TileModeProperty); }
set { SetValue(TileModeProperty, value); } set { SetValue(TileModeProperty, value); }
} }
/// <summary>
/// Gets or sets the bitmap interpolation mode.
/// </summary>
/// <value>
/// The bitmap interpolation mode.
/// </value>
public BitmapInterpolationMode BitmapInterpolationMode
{
get { return RenderOptions.GetBitmapInterpolationMode(this); }
set { RenderOptions.SetBitmapInterpolationMode(this, value); }
}
} }
} }

19
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@ -1,8 +1,8 @@
using System; using System;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Media.Imaging;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Media.Imaging;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
@ -12,6 +12,11 @@ namespace Avalonia.Platform
[Unstable] [Unstable]
public interface IDrawingContextImpl : IDisposable public interface IDrawingContextImpl : IDisposable
{ {
/// <summary>
/// Gets or sets the current render options used to control the rendering behavior of drawing operations.
/// </summary>
RenderOptions RenderOptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the current transform of the drawing context. /// Gets or sets the current transform of the drawing context.
/// </summary> /// </summary>
@ -30,8 +35,7 @@ namespace Avalonia.Platform
/// <param name="opacity">The opacity to draw with.</param> /// <param name="opacity">The opacity to draw with.</param>
/// <param name="sourceRect">The rect in the image to draw.</param> /// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param> /// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param> void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect);
void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
/// <summary> /// <summary>
/// Draws a bitmap image. /// Draws a bitmap image.
@ -157,15 +161,6 @@ namespace Avalonia.Platform
void PopGeometryClip(); void PopGeometryClip();
/// <summary> /// <summary>
/// Pushes a bitmap blending value.
/// </summary>
/// <param name="blendingMode">The bitmap blending mode.</param>
void PushBitmapBlendMode(BitmapBlendingMode blendingMode);
/// <summary>
/// Pops the latest pushed bitmap blending value.
/// </summary>
void PopBitmapBlendMode();
/// <summary> /// <summary>
/// Attempts to get an optional feature from the drawing context implementation /// Attempts to get an optional feature from the drawing context implementation

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

@ -1,22 +0,0 @@
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; }
}
}

19
src/Avalonia.Base/Platform/IGlyphRunImpl.cs

@ -1,25 +1,36 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
/// <summary> /// <summary>
/// Actual implementation of a glyph run that stores platform dependent resources. /// An immutable platform representation of a <see cref="GlyphRun"/>.
/// </summary> /// </summary>
[Unstable] [Unstable]
public interface IGlyphRunImpl : IDisposable public interface IGlyphRunImpl : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the conservative bounding box of the glyph run./>. /// Gets the <see cref="IGlyphTypeface"/> for the <see cref="IGlyphRunImpl"/>.
/// </summary> /// </summary>
Rect Bounds { get; } IGlyphTypeface GlyphTypeface { get; }
/// <summary>
/// Gets the em size used for rendering the <see cref="IGlyphRunImpl"/>.
/// </summary>
double FontRenderingEmSize { get; }
/// <summary> /// <summary>
/// Gets the baseline origin of the glyph run./>. /// Gets the baseline origin of the glyph run./>.
/// </summary> /// </summary>
Point BaselineOrigin { get; } Point BaselineOrigin { get; }
/// <summary>
/// Gets the conservative bounding box of the glyph run./>.
/// </summary>
Rect Bounds { get; }
/// <summary> /// <summary>
/// Gets the intersections of specified upper and lower limit. /// Gets the intersections of specified upper and lower limit.
/// </summary> /// </summary>

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

@ -169,8 +169,9 @@ namespace Avalonia.Platform
/// <param name="fontRenderingEmSize">The font rendering em size.</param> /// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="glyphInfos">The list of glyphs.</param> /// <param name="glyphInfos">The list of glyphs.</param>
/// <param name="baselineOrigin">The baseline origin of the run. Can be null.</param> /// <param name="baselineOrigin">The baseline origin of the run. Can be null.</param>
/// <param name="bounds">the conservative bounding box of the run</param>
/// <returns>An <see cref="IGlyphRunImpl"/>.</returns> /// <returns>An <see cref="IGlyphRunImpl"/>.</returns>
IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin); IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds);
/// <summary> /// <summary>
/// Creates a backend-specific object using a low-level API graphics context /// Creates a backend-specific object using a low-level API graphics context

8
src/Avalonia.Base/Platform/StandardAssetLoader.cs

@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Metadata;
using Avalonia.Platform.Internal; using Avalonia.Platform.Internal;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -12,12 +13,13 @@ namespace Avalonia.Platform;
/// <summary> /// <summary>
/// Loads assets compiled into the application binary. /// Loads assets compiled into the application binary.
/// </summary> /// </summary>
internal class StandardAssetLoader : IAssetLoader [Unstable("StandardAssetLoader is considered unstable. Please use AssetLoader static class instead.")]
public class StandardAssetLoader : IAssetLoader
{ {
private readonly IAssemblyDescriptorResolver _assemblyDescriptorResolver; private readonly IAssemblyDescriptorResolver _assemblyDescriptorResolver;
private AssemblyDescriptor? _defaultResmAssembly; private AssemblyDescriptor? _defaultResmAssembly;
public StandardAssetLoader(IAssemblyDescriptorResolver resolver, Assembly? assembly = null) internal StandardAssetLoader(IAssemblyDescriptorResolver resolver, Assembly? assembly = null)
{ {
if (assembly == null) if (assembly == null)
assembly = Assembly.GetEntryAssembly(); assembly = Assembly.GetEntryAssembly();
@ -153,6 +155,8 @@ internal class StandardAssetLoader : IAssetLoader
return Enumerable.Empty<Uri>(); return Enumerable.Empty<Uri>();
} }
public static void RegisterResUriParsers() => AssetLoader.RegisterResUriParsers();
private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor)
{ {
assetDescriptor = null; assetDescriptor = null;

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

@ -260,6 +260,8 @@ public class CompositingRenderer : IRendererWithCompositor
if (!comp.Effect.EffectEquals(visual.Effect)) if (!comp.Effect.EffectEquals(visual.Effect))
comp.Effect = visual.Effect?.ToImmutable(); comp.Effect = visual.Effect?.ToImmutable();
comp.RenderOptions = visual.RenderOptions;
var renderTransform = Matrix.Identity; var renderTransform = Matrix.Identity;
if (visual.HasMirrorTransform) if (visual.HasMirrorTransform)
@ -272,8 +274,6 @@ public class CompositingRenderer : IRendererWithCompositor
renderTransform *= (-offset) * visual.RenderTransform.Value * (offset); renderTransform *= (-offset) * visual.RenderTransform.Value * (offset);
} }
comp.TransformMatrix = MatrixUtils.ToMatrix4x4(renderTransform); comp.TransformMatrix = MatrixUtils.ToMatrix4x4(renderTransform);
_recorder.BeginUpdate(comp.DrawList); _recorder.BeginUpdate(comp.DrawList);

36
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@ -78,15 +78,14 @@ internal sealed class CompositionDrawingContext : DrawingContext, IDrawingContex
} }
} }
internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{ {
var next = NextDrawAs<ImageNode>(); var next = NextDrawAs<ImageNode>();
if (next == null || if (next == null ||
!next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)) !next.Item.Equals(Transform, source, opacity, sourceRect, destRect))
{ {
Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)); Add(new ImageNode(Transform, source, opacity, sourceRect, destRect));
} }
else else
{ {
@ -227,20 +226,6 @@ internal sealed class CompositionDrawingContext : DrawingContext, IDrawingContex
} }
} }
protected override void PopBitmapBlendModeCore()
{
var next = NextDrawAs<BitmapBlendModeNode>();
if (next == null || !next.Item.Equals(null))
{
Add(new BitmapBlendModeNode());
}
else
{
++_drawOperationIndex;
}
}
protected override void PopOpacityCore() protected override void PopOpacityCore()
{ {
var next = NextDrawAs<OpacityNode>(); var next = NextDrawAs<OpacityNode>();
@ -354,21 +339,6 @@ internal sealed class CompositionDrawingContext : DrawingContext, IDrawingContex
_needsToPopOpacityMask.Push(needsToPop); _needsToPopOpacityMask.Push(needsToPop);
} }
/// <inheritdoc/>
protected override void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode)
{
var next = NextDrawAs<BitmapBlendModeNode>();
if (next == null || !next.Item.Equals(blendingMode))
{
Add(new BitmapBlendModeNode(blendingMode));
}
else
{
++_drawOperationIndex;
}
}
private void Add<T>(T node) where T : class, IDrawOperation private void Add<T>(T node) where T : class, IDrawOperation
{ {
if (_drawOperationIndex < _builder.Count) if (_drawOperationIndex < _builder.Count)

21
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@ -42,15 +42,20 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
set => _impl.Transform = (_transform = value) * PostTransform; set => _impl.Transform = (_transform = value) * PostTransform;
} }
public RenderOptions RenderOptions
{
get => _impl.RenderOptions;
set => _impl.RenderOptions = value;
}
public void Clear(Color color) public void Clear(Color color)
{ {
_impl.Clear(color); _impl.Clear(color);
} }
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{ {
_impl.DrawBitmap(source, opacity, sourceRect, destRect, bitmapInterpolationMode); _impl.DrawBitmap(source, opacity, sourceRect, destRect);
} }
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
@ -133,16 +138,6 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
_impl.PopGeometryClip(); _impl.PopGeometryClip();
} }
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
_impl.PushBitmapBlendMode(blendingMode);
}
public void PopBitmapBlendMode()
{
_impl.PopBitmapBlendMode();
}
public object? GetFeature(Type t) => _impl.GetFeature(t); public object? GetFeature(Type t) => _impl.GetFeature(t);

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

@ -181,7 +181,7 @@ namespace Avalonia.Rendering.Composition.Server
else else
targetContext.DrawBitmap(RefCountable.CreateUnownedNotClonable(_layer), 1, targetContext.DrawBitmap(RefCountable.CreateUnownedNotClonable(_layer), 1,
new Rect(_layerSize), new Rect(_layerSize),
new Rect(Size), BitmapInterpolationMode.LowQuality); new Rect(Size));
if (DebugOverlays != RendererDebugOverlays.None) if (DebugOverlays != RendererDebugOverlays.None)
{ {

7
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs

@ -66,6 +66,8 @@ namespace Avalonia.Rendering.Composition.Server
if (OpacityMaskBrush != null) if (OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect); canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);
canvas.RenderOptions = RenderOptions;
RenderCore(canvas, currentTransformedClip); RenderCore(canvas, currentTransformedClip);
// Hack to force invalidation of SKMatrix // Hack to force invalidation of SKMatrix
@ -122,6 +124,11 @@ namespace Avalonia.Rendering.Composition.Server
var wasVisible = IsVisibleInFrame; var wasVisible = IsVisibleInFrame;
if(Parent != null)
{
RenderOptions = RenderOptions.MergeWith(Parent.RenderOptions);
}
// Calculate new parent-relative transform // Calculate new parent-relative transform
if (_combinedTransformDirty) if (_combinedTransformDirty)
{ {

120
src/Avalonia.Base/Rendering/ImmediateRenderer.cs

@ -44,79 +44,99 @@ namespace Avalonia.Rendering
public static void Render(DrawingContext context, Visual visual, Rect clipRect) public static void Render(DrawingContext context, Visual visual, Rect clipRect)
{ {
var opacity = visual.Opacity; var currentRenderOptions = default(RenderOptions);
var clipToBounds = visual.ClipToBounds; var platformContext = context as PlatformDrawingContext;
var bounds = new Rect(visual.Bounds.Size);
if (visual.IsVisible && opacity > 0) try
{ {
var m = Matrix.CreateTranslation(visual.Bounds.Position); if (platformContext != null)
var renderTransform = Matrix.Identity;
// this should be calculated BEFORE renderTransform
if (visual.HasMirrorTransform)
{ {
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0); currentRenderOptions = platformContext.RenderOptions;
renderTransform *= mirrorMatrix;
}
if (visual.RenderTransform != null) platformContext.RenderOptions = visual.RenderOptions.MergeWith(platformContext.RenderOptions);
{
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin);
var finalTransform = (-offset) * visual.RenderTransform.Value * (offset);
renderTransform *= finalTransform;
} }
m = renderTransform * m; var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
var bounds = new Rect(visual.Bounds.Size);
if (clipToBounds) if (visual.IsVisible && opacity > 0)
{ {
var m = Matrix.CreateTranslation(visual.Bounds.Position);
var renderTransform = Matrix.Identity;
// this should be calculated BEFORE renderTransform
if (visual.HasMirrorTransform)
{
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0);
renderTransform *= mirrorMatrix;
}
if (visual.RenderTransform != null) if (visual.RenderTransform != null)
{ {
clipRect = new Rect(visual.Bounds.Size); var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin);
var finalTransform = (-offset) * visual.RenderTransform.Value * (offset);
renderTransform *= finalTransform;
} }
else
m = renderTransform * m;
if (clipToBounds)
{ {
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); if (visual.RenderTransform != null)
{
clipRect = new Rect(visual.Bounds.Size);
}
else
{
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size));
}
} }
}
using (context.PushTransform(m)) using (context.PushTransform(m))
using (context.PushOpacity(opacity, bounds)) using (context.PushOpacity(opacity, bounds))
using (clipToBounds using (clipToBounds
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
? visual is IVisualWithRoundRectClip roundClipVisual ? visual is IVisualWithRoundRectClip roundClipVisual
? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius)) ? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius))
: context.PushClip(bounds) : context.PushClip(bounds)
: default) : default)
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default) using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default)
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default) using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default)
using (context.PushTransform(Matrix.Identity)) using (context.PushTransform(Matrix.Identity))
{
visual.Render(context);
var childrenEnumerable = visual.HasNonUniformZIndexChildren
? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance)
: (IEnumerable<Visual>)visual.VisualChildren;
foreach (var child in childrenEnumerable)
{ {
var childBounds = GetTransformedBounds(child); visual.Render(context);
var childrenEnumerable = visual.HasNonUniformZIndexChildren
? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance)
: (IEnumerable<Visual>)visual.VisualChildren;
if (!child.ClipToBounds || clipRect.Intersects(childBounds)) foreach (var child in childrenEnumerable)
{ {
var childClipRect = child.RenderTransform == null var childBounds = GetTransformedBounds(child);
? clipRect.Translate(-childBounds.Position)
: clipRect; if (!child.ClipToBounds || clipRect.Intersects(childBounds))
Render(context, child, childClipRect); {
} var childClipRect = child.RenderTransform == null
? clipRect.Translate(-childBounds.Position)
: clipRect;
Render(context, child, childClipRect);
}
}
} }
} }
} }
finally
{
if (platformContext != null)
{
platformContext.RenderOptions = currentRenderOptions;
}
}
} }
} }
} }

68
src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs

@ -1,68 +0,0 @@
using Avalonia.Platform;
using Avalonia.Media.Imaging;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents an bitmap blending mode push or pop.
/// </summary>
internal class BitmapBlendModeNode : IDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="BitmapBlendModeNode"/> class that represents an
/// <see cref="BitmapBlendingMode"/> push.
/// </summary>
/// <param name="bitmapBlend">The <see cref="BitmapBlendingMode"/> to push.</param>
public BitmapBlendModeNode(BitmapBlendingMode bitmapBlend)
{
BlendingMode = bitmapBlend;
}
/// <summary>
/// Initializes a new instance of the <see cref="BitmapBlendModeNode"/> class that represents an
/// <see cref="BitmapBlendingMode"/> pop.
/// </summary>
public BitmapBlendModeNode()
{
}
/// <inheritdoc/>
public Rect Bounds => default;
/// <summary>
/// Gets the BitmapBlend to be pushed or null if the operation represents a pop.
/// </summary>
public BitmapBlendingMode? BlendingMode { get; }
/// <inheritdoc/>
public bool HitTest(Point p) => false;
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="blendingMode">the <see cref="BitmapBlendModeNode"/> how to compare</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(BitmapBlendingMode? blendingMode) => BlendingMode == blendingMode;
/// <inheritdoc/>
public void Render(IDrawingContextImpl context)
{
if (BlendingMode.HasValue)
{
context.PushBitmapBlendMode(BlendingMode.Value);
}
else
{
context.PopBitmapBlendMode();
}
}
public void Dispose()
{
}
}
}

5
src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs

@ -1,5 +1,4 @@
using System; using Avalonia.Media;
using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -18,7 +17,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="glyphRun">The glyph run to draw.</param> /// <param name="glyphRun">The glyph run to draw.</param>
public GlyphRunNode( public GlyphRunNode(
Matrix transform, Matrix transform,
IImmutableBrush foreground, IImmutableBrush? foreground,
IRef<IGlyphRunImpl> glyphRun) IRef<IGlyphRunImpl> glyphRun)
: base(glyphRun.Item.Bounds, transform, foreground) : base(glyphRun.Item.Bounds, transform, foreground)
{ {

21
src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs

@ -1,6 +1,5 @@
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Media.Imaging;
namespace Avalonia.Rendering.SceneGraph namespace Avalonia.Rendering.SceneGraph
{ {
@ -17,15 +16,13 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="opacity">The draw opacity.</param> /// <param name="opacity">The draw opacity.</param>
/// <param name="sourceRect">The source rect.</param> /// <param name="sourceRect">The source rect.</param>
/// <param name="destRect">The destination rect.</param> /// <param name="destRect">The destination rect.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param> public ImageNode(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
public ImageNode(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
: base(destRect, transform) : base(destRect, transform)
{ {
Source = source.Clone(); Source = source.Clone();
Opacity = opacity; Opacity = opacity;
SourceRect = sourceRect; SourceRect = sourceRect;
DestRect = destRect; DestRect = destRect;
BitmapInterpolationMode = bitmapInterpolationMode;
SourceVersion = Source.Item.Version; SourceVersion = Source.Item.Version;
} }
@ -53,14 +50,6 @@ namespace Avalonia.Rendering.SceneGraph
/// Gets the destination rect. /// Gets the destination rect.
/// </summary> /// </summary>
public Rect DestRect { get; } public Rect DestRect { get; }
/// <summary>
/// Gets the bitmap interpolation mode.
/// </summary>
/// <value>
/// The scaling mode.
/// </value>
public BitmapInterpolationMode BitmapInterpolationMode { get; }
/// <summary> /// <summary>
/// Determines if this draw operation equals another. /// Determines if this draw operation equals another.
@ -70,27 +59,25 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="opacity">The opacity of the other draw operation.</param> /// <param name="opacity">The opacity of the other draw operation.</param>
/// <param name="sourceRect">The source rect of the other draw operation.</param> /// <param name="sourceRect">The source rect of the other draw operation.</param>
/// <param name="destRect">The dest rect of the other draw operation.</param> /// <param name="destRect">The dest rect of the other draw operation.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns> /// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks> /// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent /// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object. /// allocation of a not-yet-constructed draw operation object.
/// </remarks> /// </remarks>
public bool Equals(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) public bool Equals(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{ {
return transform == Transform && return transform == Transform &&
Equals(source.Item, Source.Item) && Equals(source.Item, Source.Item) &&
source.Item.Version == SourceVersion && source.Item.Version == SourceVersion &&
opacity == Opacity && opacity == Opacity &&
sourceRect == SourceRect && sourceRect == SourceRect &&
destRect == DestRect && destRect == DestRect;
bitmapInterpolationMode == BitmapInterpolationMode;
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Render(IDrawingContextImpl context) public override void Render(IDrawingContextImpl context)
{ {
context.DrawBitmap(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode); context.DrawBitmap(Source, Opacity, SourceRect, DestRect);
} }
/// <inheritdoc/> /// <inheritdoc/>

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

@ -248,11 +248,11 @@ public partial class Dispatcher
/// An operation representing the queued delegate to be invoked. /// An operation representing the queued delegate to be invoked.
/// </returns> /// </returns>
/// <remarks> /// <remarks>
/// Note that the default priority is DispatcherPriority.Normal. /// Note that the default priority is DispatcherPriority.Default.
/// </remarks> /// </remarks>
public DispatcherOperation InvokeAsync(Action callback) public DispatcherOperation InvokeAsync(Action callback)
{ {
return InvokeAsync(callback, DispatcherPriority.Normal, CancellationToken.None); return InvokeAsync(callback, default, CancellationToken.None);
} }
/// <summary> /// <summary>
@ -326,11 +326,11 @@ public partial class Dispatcher
/// An operation representing the queued delegate to be invoked. /// An operation representing the queued delegate to be invoked.
/// </returns> /// </returns>
/// <remarks> /// <remarks>
/// Note that the default priority is DispatcherPriority.Normal. /// Note that the default priority is DispatcherPriority.Default.
/// </remarks> /// </remarks>
public DispatcherOperation<TResult> InvokeAsync<TResult>(Func<TResult> callback) public DispatcherOperation<TResult> InvokeAsync<TResult>(Func<TResult> callback)
{ {
return InvokeAsync(callback, DispatcherPriority.Normal, CancellationToken.None); return InvokeAsync(callback, DispatcherPriority.Default, CancellationToken.None);
} }
/// <summary> /// <summary>
@ -541,6 +541,18 @@ public partial class Dispatcher
InvokeAsyncImpl(new DispatcherOperation(this, priority, action, true), CancellationToken.None); InvokeAsyncImpl(new DispatcherOperation(this, priority, action, true), CancellationToken.None);
} }
/// <summary>
/// Executes the specified Func&lt;Task&gt; asynchronously on the
/// thread that the Dispatcher was created on
/// </summary>
/// <param name="callback">
/// A Func&lt;Task&gt; delegate to invoke through the dispatcher.
/// </param>
/// <returns>
/// An task that completes after the task returned from callback finishes.
/// </returns>
public Task InvokeAsync(Func<Task> callback) => InvokeAsync(callback, DispatcherPriority.Default);
/// <summary> /// <summary>
/// Executes the specified Func&lt;Task&gt; asynchronously on the /// Executes the specified Func&lt;Task&gt; asynchronously on the
/// thread that the Dispatcher was created on /// thread that the Dispatcher was created on
@ -556,11 +568,29 @@ public partial class Dispatcher
/// <returns> /// <returns>
/// An task that completes after the task returned from callback finishes /// An task that completes after the task returned from callback finishes
/// </returns> /// </returns>
public Task InvokeAsync(Func<Task> callback, DispatcherPriority priority = default) public Task InvokeAsync(Func<Task> callback, DispatcherPriority priority)
{ {
_ = callback ?? throw new ArgumentNullException(nameof(callback)); _ = callback ?? throw new ArgumentNullException(nameof(callback));
return InvokeAsync<Task>(callback, priority).GetTask().Unwrap(); return InvokeAsync<Task>(callback, priority).GetTask().Unwrap();
} }
/// <summary>
/// Executes the specified Func&lt;Task&lt;TResult&gt;&gt; asynchronously on the
/// thread that the Dispatcher was created on
/// </summary>
/// <param name="action">
/// A Func&lt;Task&lt;TResult&gt;&gt; delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
/// callback is invoked relative to the other pending operations
/// in the Dispatcher.
/// </param>
/// <returns>
/// An task that completes after the task returned from callback finishes
/// </returns>
public Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> action) =>
InvokeAsync(action, DispatcherPriority.Default);
/// <summary> /// <summary>
/// Executes the specified Func&lt;Task&lt;TResult&gt;&gt; asynchronously on the /// Executes the specified Func&lt;Task&lt;TResult&gt;&gt; asynchronously on the
@ -577,7 +607,7 @@ public partial class Dispatcher
/// <returns> /// <returns>
/// An task that completes after the task returned from callback finishes /// An task that completes after the task returned from callback finishes
/// </returns> /// </returns>
public Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> action, DispatcherPriority priority = default) public Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> action, DispatcherPriority priority)
{ {
_ = action ?? throw new ArgumentNullException(nameof(action)); _ = action ?? throw new ArgumentNullException(nameof(action));
return InvokeAsync<Task<TResult>>(action, priority).GetTask().Unwrap(); return InvokeAsync<Task<TResult>>(action, priority).GetTask().Unwrap();

57
src/Avalonia.Base/Threading/DispatcherFrame.cs

@ -91,31 +91,44 @@ public class DispatcherFrame
internal void Run(IControlledDispatcherImpl impl) internal void Run(IControlledDispatcherImpl impl)
{ {
// Since the actual platform run loop is controlled by a Cancellation token, we are restarting Dispatcher.VerifyAccess();
// it if frame still needs to run
while (Continue) // Since the actual platform run loop is controlled by a Cancellation token, we have an
RunCore(impl); // outer loop that restarts the platform one in case Continue was set to true after being set to false
} while (true)
private void RunCore(IControlledDispatcherImpl impl)
{
if (_isRunning)
throw new InvalidOperationException("This frame is already running");
_isRunning = true;
try
{
_cancellationTokenSource = new CancellationTokenSource();
// Wake up the dispatcher in case it has pending jobs
Dispatcher.RequestProcessing();
impl.RunLoop(_cancellationTokenSource.Token);
}
finally
{ {
_isRunning = false; // Take the instance lock since `Continue` is changed from one too
_cancellationTokenSource?.Cancel(); lock (Dispatcher.InstanceLock)
_cancellationTokenSource = null; {
if (!Continue)
return;
if (_isRunning)
throw new InvalidOperationException("This frame is already running");
_cancellationTokenSource = new CancellationTokenSource();
_isRunning = true;
}
try
{
// Wake up the dispatcher in case it has pending jobs
Dispatcher.RequestProcessing();
impl.RunLoop(_cancellationTokenSource.Token);
}
finally
{
lock (Dispatcher.InstanceLock)
{
_isRunning = false;
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
}
} }
} }
internal void MaybeExitOnDispatcherRequest() internal void MaybeExitOnDispatcherRequest()
{ {

2
src/Avalonia.Base/Threading/DispatcherOperation.cs

@ -331,6 +331,8 @@ public class DispatcherOperation<T> : DispatcherOperation
private TaskCompletionSource<T> TaskCompletionSource => (TaskCompletionSource<T>)TaskSource!; private TaskCompletionSource<T> TaskCompletionSource => (TaskCompletionSource<T>)TaskSource!;
public new TaskAwaiter<T> GetAwaiter() => GetTask().GetAwaiter();
public new Task<T> GetTask() => TaskCompletionSource!.Task; public new Task<T> GetTask() => TaskCompletionSource!.Task;
protected override Task GetTaskCore() => GetTask(); protected override Task GetTaskCore() => GetTask();

3
src/Avalonia.Base/Threading/DispatcherPriority.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
namespace Avalonia.Threading namespace Avalonia.Threading
{ {
@ -100,7 +101,7 @@ namespace Avalonia.Threading
/// <summary> /// <summary>
/// The job will be processed with the same priority as data binding. /// The job will be processed with the same priority as data binding.
/// </summary> /// </summary>
[Obsolete("WPF compatibility")] public static readonly DispatcherPriority DataBind = new(Layout); [Obsolete("WPF compatibility"), EditorBrowsable(EditorBrowsableState.Never)] public static readonly DispatcherPriority DataBind = new(Layout);
/// <summary> /// <summary>
/// The job will be processed with normal priority. /// The job will be processed with normal priority.

4
src/Avalonia.Base/Visual.cs

@ -318,7 +318,9 @@ namespace Avalonia
internal CompositionDrawListVisual? CompositionVisual { get; private set; } internal CompositionDrawListVisual? CompositionVisual { get; private set; }
internal CompositionVisual? ChildCompositionVisual { get; set; } internal CompositionVisual? ChildCompositionVisual { get; set; }
internal RenderOptions RenderOptions { get; set; }
public bool HasNonUniformZIndexChildren { get; private set; } public bool HasNonUniformZIndexChildren { get; private set; }
/// <summary> /// <summary>

5
src/Avalonia.Base/VisualTree/IVisualWithRoundRectClip.cs

@ -1,14 +1,15 @@
using System; using System;
using System.ComponentModel;
namespace Avalonia.VisualTree namespace Avalonia.VisualTree
{ {
[Obsolete("Internal API, will be removed in future versions, you've been warned")] [Obsolete("Internal API, will be removed in future versions, you've been warned"), EditorBrowsable(EditorBrowsableState.Never)]
public interface IVisualWithRoundRectClip public interface IVisualWithRoundRectClip
{ {
/// <summary> /// <summary>
/// Gets a value indicating the corner radius of control's clip bounds /// Gets a value indicating the corner radius of control's clip bounds
/// </summary> /// </summary>
[Obsolete("Internal API, will be removed in future versions, you've been warned")] [Obsolete("Internal API, will be removed in future versions, you've been warned"), EditorBrowsable(EditorBrowsableState.Never)]
CornerRadius ClipToBoundsRadius { get; } CornerRadius ClipToBoundsRadius { get; }
} }

1
src/Avalonia.Base/composition-schema.xml

@ -30,6 +30,7 @@
<Property Name="AdornerIsClipped" Type="bool" Internal="true" /> <Property Name="AdornerIsClipped" Type="bool" Internal="true" />
<Property Name="OpacityMaskBrush" Type="Avalonia.Media.IImmutableBrush?" Internal="true" /> <Property Name="OpacityMaskBrush" Type="Avalonia.Media.IImmutableBrush?" Internal="true" />
<Property Name="Effect" Type="Avalonia.Media.IImmutableEffect?" Internal="true" /> <Property Name="Effect" Type="Avalonia.Media.IImmutableEffect?" Internal="true" />
<Property Name="RenderOptions" Type="Avalonia.Media.RenderOptions" />
</Object> </Object>
<Object Name="CompositionContainerVisual" Inherits="CompositionVisual"/> <Object Name="CompositionContainerVisual" Inherits="CompositionVisual"/>
<Object Name="CompositionSolidColorVisual" Inherits="CompositionContainerVisual"> <Object Name="CompositionSolidColorVisual" Inherits="CompositionContainerVisual">

17
src/Avalonia.Controls/ComboBox.cs

@ -450,7 +450,22 @@ namespace Avalonia.Controls
} }
else else
{ {
SelectionBoxItem = item; if(ItemTemplate is null && DisplayMemberBinding is { } binding)
{
var template = new FuncDataTemplate<object?>((_, _) =>
new TextBlock
{
[TextBlock.DataContextProperty] = item,
[!TextBlock.TextProperty] = binding,
});
var text = template.Build(item);
SelectionBoxItem = text;
}
else
{
SelectionBoxItem = item;
}
} }
} }

4
src/Avalonia.Controls/ContextMenu.cs

@ -64,7 +64,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="PlacementMode"/> property. /// Defines the <see cref="PlacementMode"/> property.
/// </summary> /// </summary>
[Obsolete("Use the Placement property instead.")] [Obsolete("Use the Placement property instead."), EditorBrowsable(EditorBrowsableState.Never)]
public static readonly StyledProperty<PlacementMode> PlacementModeProperty = PlacementProperty; public static readonly StyledProperty<PlacementMode> PlacementModeProperty = PlacementProperty;
/// <summary> /// <summary>
@ -157,7 +157,7 @@ namespace Avalonia.Controls
} }
/// <inheritdoc cref="Placement"/> /// <inheritdoc cref="Placement"/>
[Obsolete("Use the Placement property instead.")] [Obsolete("Use the Placement property instead."), EditorBrowsable(EditorBrowsableState.Never)]
public PlacementMode PlacementMode public PlacementMode PlacementMode
{ {
get => GetValue(PlacementProperty); get => GetValue(PlacementProperty);

5
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
namespace Avalonia.Controls.Generators namespace Avalonia.Controls.Generators
{ {
@ -156,10 +157,10 @@ namespace Avalonia.Controls.Generators
/// </remarks> /// </remarks>
public void ClearItemContainer(Control container) => _owner.ClearItemContainer(container); public void ClearItemContainer(Control container) => _owner.ClearItemContainer(container);
[Obsolete("Use ItemsControl.ContainerFromIndex")] [Obsolete("Use ItemsControl.ContainerFromIndex"), EditorBrowsable(EditorBrowsableState.Never)]
public Control? ContainerFromIndex(int index) => _owner.ContainerFromIndex(index); public Control? ContainerFromIndex(int index) => _owner.ContainerFromIndex(index);
[Obsolete("Use ItemsControl.IndexFromContainer")] [Obsolete("Use ItemsControl.IndexFromContainer"), EditorBrowsable(EditorBrowsableState.Never)]
public int IndexFromContainer(Control container) => _owner.IndexFromContainer(container); public int IndexFromContainer(Control container) => _owner.IndexFromContainer(container);
} }
} }

7
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
namespace Avalonia.Controls.Generators namespace Avalonia.Controls.Generators
{ {
@ -20,13 +21,13 @@ namespace Avalonia.Controls.Generators
internal TreeContainerIndex(TreeView owner) => _owner = owner; internal TreeContainerIndex(TreeView owner) => _owner = owner;
[Obsolete("Use TreeView.GetRealizedTreeContainers")] [Obsolete("Use TreeView.GetRealizedTreeContainers"), EditorBrowsable(EditorBrowsableState.Never)]
public IEnumerable<Control> Containers => _owner.GetRealizedTreeContainers(); public IEnumerable<Control> Containers => _owner.GetRealizedTreeContainers();
[Obsolete("Use TreeView.TreeContainerFromItem")] [Obsolete("Use TreeView.TreeContainerFromItem"), EditorBrowsable(EditorBrowsableState.Never)]
public Control? ContainerFromItem(object item) => _owner.TreeContainerFromItem(item); public Control? ContainerFromItem(object item) => _owner.TreeContainerFromItem(item);
[Obsolete("Use TreeView.TreeItemFromContainer")] [Obsolete("Use TreeView.TreeItemFromContainer"), EditorBrowsable(EditorBrowsableState.Never)]
public object? ItemFromContainer(Control container) => _owner.TreeItemFromContainer(container); public object? ItemFromContainer(Control container) => _owner.TreeItemFromContainer(container);
} }
} }

4
src/Avalonia.Controls/Image.cs

@ -91,9 +91,7 @@ namespace Avalonia.Controls
Rect sourceRect = new Rect(sourceSize) Rect sourceRect = new Rect(sourceSize)
.CenterRect(new Rect(destRect.Size / scale)); .CenterRect(new Rect(destRect.Size / scale));
var interpolationMode = RenderOptions.GetBitmapInterpolationMode(this); context.DrawImage(source, sourceRect, destRect);
context.DrawImage(source, sourceRect, destRect, interpolationMode);
} }
} }

3
src/Avalonia.Controls/ItemsControl.cs

@ -2,6 +2,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
@ -676,7 +677,7 @@ namespace Avalonia.Controls
/// TreeView to be able to create a <see cref="TreeItemContainerGenerator"/>. Can be /// TreeView to be able to create a <see cref="TreeItemContainerGenerator"/>. Can be
/// removed in 12.0. /// removed in 12.0.
/// </remarks> /// </remarks>
[Obsolete] [Obsolete, EditorBrowsable(EditorBrowsableState.Never)]
private protected virtual ItemContainerGenerator CreateItemContainerGenerator() private protected virtual ItemContainerGenerator CreateItemContainerGenerator()
{ {
return new ItemContainerGenerator(this); return new ItemContainerGenerator(this);

3
src/Avalonia.Controls/Platform/Dialogs/ISystemDialogImpl.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -7,7 +8,7 @@ namespace Avalonia.Controls.Platform
/// <summary> /// <summary>
/// Defines a platform-specific system dialog implementation. /// Defines a platform-specific system dialog implementation.
/// </summary> /// </summary>
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API"), EditorBrowsable(EditorBrowsableState.Never)]
[Unstable] [Unstable]
public interface ISystemDialogImpl public interface ISystemDialogImpl
{ {

3
src/Avalonia.Controls/Platform/Dialogs/SystemDialogImpl.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
@ -10,7 +11,7 @@ namespace Avalonia.Controls.Platform
/// <summary> /// <summary>
/// Defines a platform-specific system dialog implementation. /// Defines a platform-specific system dialog implementation.
/// </summary> /// </summary>
[Obsolete] [Obsolete, EditorBrowsable(EditorBrowsableState.Never)]
internal class SystemDialogImpl : ISystemDialogImpl internal class SystemDialogImpl : ISystemDialogImpl
{ {
public async Task<string[]?> ShowFileDialogAsync(FileDialog dialog, Window parent) public async Task<string[]?> ShowFileDialogAsync(FileDialog dialog, Window parent)

6
src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs

@ -58,6 +58,10 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl
public void RunLoop(CancellationToken token) public void RunLoop(CancellationToken token)
{ {
CancellationTokenRegistration registration = default;
if (token.CanBeCanceled)
registration = token.Register(() => _wakeup.Set());
while (!token.IsCancellationRequested) while (!token.IsCancellationRequested)
{ {
bool signaled; bool signaled;
@ -105,5 +109,7 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl
else else
_wakeup.WaitOne(); _wakeup.WaitOne();
} }
registration.Dispose();
} }
} }

5
src/Avalonia.Controls/Platform/Screen.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
@ -17,7 +18,7 @@ namespace Avalonia.Platform
public double Scaling { get; } public double Scaling { get; }
/// <inheritdoc cref="Scaling"/> /// <inheritdoc cref="Scaling"/>
[Obsolete("Use the Scaling property instead.")] [Obsolete("Use the Scaling property instead."), EditorBrowsable(EditorBrowsableState.Never)]
public double PixelDensity => Scaling; public double PixelDensity => Scaling;
/// <summary> /// <summary>
@ -43,7 +44,7 @@ namespace Avalonia.Platform
public bool IsPrimary { get; } public bool IsPrimary { get; }
/// <inheritdoc cref="IsPrimary"/> /// <inheritdoc cref="IsPrimary"/>
[Obsolete("Use the IsPrimary property instead.")] [Obsolete("Use the IsPrimary property instead."), EditorBrowsable(EditorBrowsableState.Never)]
public bool Primary => IsPrimary; public bool Primary => IsPrimary;
/// <summary> /// <summary>

4
src/Avalonia.Controls/Primitives/Popup.cs

@ -74,7 +74,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Defines the <see cref="PlacementMode"/> property. /// Defines the <see cref="PlacementMode"/> property.
/// </summary> /// </summary>
[Obsolete("Use the Placement property instead.")] [Obsolete("Use the Placement property instead."), EditorBrowsable(EditorBrowsableState.Never)]
public static readonly StyledProperty<PlacementMode> PlacementModeProperty = PlacementProperty; public static readonly StyledProperty<PlacementMode> PlacementModeProperty = PlacementProperty;
/// <summary> /// <summary>
@ -241,7 +241,7 @@ namespace Avalonia.Controls.Primitives
} }
/// <inheritdoc cref="Placement"/> /// <inheritdoc cref="Placement"/>
[Obsolete("Use the Placement property instead.")] [Obsolete("Use the Placement property instead."), EditorBrowsable(EditorBrowsableState.Never)]
public PlacementMode PlacementMode public PlacementMode PlacementMode
{ {
get => GetValue(PlacementProperty); get => GetValue(PlacementProperty);

15
src/Avalonia.Controls/Primitives/Thumb.cs

@ -4,7 +4,6 @@ using Avalonia.Controls.Automation.Peers;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -47,6 +46,12 @@ namespace Avalonia.Controls.Primitives
remove { RemoveHandler(DragCompletedEvent, value); } remove { RemoveHandler(DragCompletedEvent, value); }
} }
internal void AdjustDrag(Vector v)
{
if (_lastPoint.HasValue)
_lastPoint = _lastPoint.Value + v;
}
protected override AutomationPeer OnCreateAutomationPeer() => new ThumbAutomationPeer(this); protected override AutomationPeer OnCreateAutomationPeer() => new ThumbAutomationPeer(this);
protected virtual void OnDragStarted(VectorEventArgs e) protected virtual void OnDragStarted(VectorEventArgs e)
@ -85,22 +90,20 @@ namespace Avalonia.Controls.Primitives
{ {
if (_lastPoint.HasValue) if (_lastPoint.HasValue)
{ {
var point = e.GetPosition(null);
var ev = new VectorEventArgs var ev = new VectorEventArgs
{ {
RoutedEvent = DragDeltaEvent, RoutedEvent = DragDeltaEvent,
Vector = point - _lastPoint.Value, Vector = e.GetPosition(this) - _lastPoint.Value,
}; };
RaiseEvent(ev); RaiseEvent(ev);
_lastPoint = point;
} }
} }
protected override void OnPointerPressed(PointerPressedEventArgs e) protected override void OnPointerPressed(PointerPressedEventArgs e)
{ {
e.Handled = true; e.Handled = true;
_lastPoint = e.GetPosition(null); _lastPoint = e.GetPosition(this);
var ev = new VectorEventArgs var ev = new VectorEventArgs
{ {
@ -123,7 +126,7 @@ namespace Avalonia.Controls.Primitives
var ev = new VectorEventArgs var ev = new VectorEventArgs
{ {
RoutedEvent = DragCompletedEvent, RoutedEvent = DragCompletedEvent,
Vector = (Vector)e.GetPosition(null), Vector = (Vector)e.GetPosition(this),
}; };
RaiseEvent(ev); RaiseEvent(ev);

19
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Data; using Avalonia.Data;
@ -28,7 +29,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Defines the <see cref="Checked"/> event. /// Defines the <see cref="Checked"/> event.
/// </summary> /// </summary>
[Obsolete("Use IsCheckedChangedEvent instead.")] [Obsolete("Use IsCheckedChangedEvent instead."), EditorBrowsable(EditorBrowsableState.Never)]
public static readonly RoutedEvent<RoutedEventArgs> CheckedEvent = public static readonly RoutedEvent<RoutedEventArgs> CheckedEvent =
RoutedEvent.Register<ToggleButton, RoutedEventArgs>( RoutedEvent.Register<ToggleButton, RoutedEventArgs>(
nameof(Checked), nameof(Checked),
@ -37,7 +38,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Defines the <see cref="Unchecked"/> event. /// Defines the <see cref="Unchecked"/> event.
/// </summary> /// </summary>
[Obsolete("Use IsCheckedChangedEvent instead.")] [Obsolete("Use IsCheckedChangedEvent instead."), EditorBrowsable(EditorBrowsableState.Never)]
public static readonly RoutedEvent<RoutedEventArgs> UncheckedEvent = public static readonly RoutedEvent<RoutedEventArgs> UncheckedEvent =
RoutedEvent.Register<ToggleButton, RoutedEventArgs>( RoutedEvent.Register<ToggleButton, RoutedEventArgs>(
nameof(Unchecked), nameof(Unchecked),
@ -46,7 +47,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Defines the <see cref="Unchecked"/> event. /// Defines the <see cref="Unchecked"/> event.
/// </summary> /// </summary>
[Obsolete("Use IsCheckedChangedEvent instead.")] [Obsolete("Use IsCheckedChangedEvent instead."), EditorBrowsable(EditorBrowsableState.Never)]
public static readonly RoutedEvent<RoutedEventArgs> IndeterminateEvent = public static readonly RoutedEvent<RoutedEventArgs> IndeterminateEvent =
RoutedEvent.Register<ToggleButton, RoutedEventArgs>( RoutedEvent.Register<ToggleButton, RoutedEventArgs>(
nameof(Indeterminate), nameof(Indeterminate),
@ -72,7 +73,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Raised when a <see cref="ToggleButton"/> is checked. /// Raised when a <see cref="ToggleButton"/> is checked.
/// </summary> /// </summary>
[Obsolete("Use IsCheckedChanged instead.")] [Obsolete("Use IsCheckedChanged instead."), EditorBrowsable(EditorBrowsableState.Never)]
public event EventHandler<RoutedEventArgs>? Checked public event EventHandler<RoutedEventArgs>? Checked
{ {
add => AddHandler(CheckedEvent, value); add => AddHandler(CheckedEvent, value);
@ -82,7 +83,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Raised when a <see cref="ToggleButton"/> is unchecked. /// Raised when a <see cref="ToggleButton"/> is unchecked.
/// </summary> /// </summary>
[Obsolete("Use IsCheckedChanged instead.")] [Obsolete("Use IsCheckedChanged instead."), EditorBrowsable(EditorBrowsableState.Never)]
public event EventHandler<RoutedEventArgs>? Unchecked public event EventHandler<RoutedEventArgs>? Unchecked
{ {
add => AddHandler(UncheckedEvent, value); add => AddHandler(UncheckedEvent, value);
@ -92,7 +93,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Raised when a <see cref="ToggleButton"/> is neither checked nor unchecked. /// Raised when a <see cref="ToggleButton"/> is neither checked nor unchecked.
/// </summary> /// </summary>
[Obsolete("Use IsCheckedChanged instead.")] [Obsolete("Use IsCheckedChanged instead."), EditorBrowsable(EditorBrowsableState.Never)]
public event EventHandler<RoutedEventArgs>? Indeterminate public event EventHandler<RoutedEventArgs>? Indeterminate
{ {
add => AddHandler(IndeterminateEvent, value); add => AddHandler(IndeterminateEvent, value);
@ -168,7 +169,7 @@ namespace Avalonia.Controls.Primitives
/// Called when <see cref="IsChecked"/> becomes true. /// Called when <see cref="IsChecked"/> becomes true.
/// </summary> /// </summary>
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param> /// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
[Obsolete("Use OnIsCheckedChanged instead.")] [Obsolete("Use OnIsCheckedChanged instead."), EditorBrowsable(EditorBrowsableState.Never)]
protected virtual void OnChecked(RoutedEventArgs e) protected virtual void OnChecked(RoutedEventArgs e)
{ {
RaiseEvent(e); RaiseEvent(e);
@ -178,7 +179,7 @@ namespace Avalonia.Controls.Primitives
/// Called when <see cref="IsChecked"/> becomes false. /// Called when <see cref="IsChecked"/> becomes false.
/// </summary> /// </summary>
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param> /// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
[Obsolete("Use OnIsCheckedChanged instead.")] [Obsolete("Use OnIsCheckedChanged instead."), EditorBrowsable(EditorBrowsableState.Never)]
protected virtual void OnUnchecked(RoutedEventArgs e) protected virtual void OnUnchecked(RoutedEventArgs e)
{ {
RaiseEvent(e); RaiseEvent(e);
@ -188,7 +189,7 @@ namespace Avalonia.Controls.Primitives
/// Called when <see cref="IsChecked"/> becomes null. /// Called when <see cref="IsChecked"/> becomes null.
/// </summary> /// </summary>
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param> /// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
[Obsolete("Use OnIsCheckedChanged instead.")] [Obsolete("Use OnIsCheckedChanged instead."), EditorBrowsable(EditorBrowsableState.Never)]
protected virtual void OnIndeterminate(RoutedEventArgs e) protected virtual void OnIndeterminate(RoutedEventArgs e)
{ {
RaiseEvent(e); RaiseEvent(e);

28
src/Avalonia.Controls/Primitives/Track.cs

@ -45,6 +45,8 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<bool> IgnoreThumbDragProperty = public static readonly StyledProperty<bool> IgnoreThumbDragProperty =
AvaloniaProperty.Register<Track, bool>(nameof(IgnoreThumbDrag)); AvaloniaProperty.Register<Track, bool>(nameof(IgnoreThumbDrag));
private Vector _lastDrag;
static Track() static Track()
{ {
ThumbProperty.Changed.AddClassHandler<Track>((x, e) => x.ThumbChanged(e)); ThumbProperty.Changed.AddClassHandler<Track>((x, e) => x.ThumbChanged(e));
@ -245,7 +247,10 @@ namespace Avalonia.Controls.Primitives
if (Thumb != null) if (Thumb != null)
{ {
Thumb.Arrange(new Rect(offset, pieceSize)); var bounds = new Rect(offset, pieceSize);
var adjust = CalculateThumbAdjustment(Thumb, bounds);
Thumb.Arrange(bounds);
Thumb.AdjustDrag(adjust);
} }
ThumbCenterOffset = offset.Y + (thumbLength * 0.5); ThumbCenterOffset = offset.Y + (thumbLength * 0.5);
@ -277,12 +282,16 @@ namespace Avalonia.Controls.Primitives
if (Thumb != null) if (Thumb != null)
{ {
Thumb.Arrange(new Rect(offset, pieceSize)); var bounds = new Rect(offset, pieceSize);
var adjust = CalculateThumbAdjustment(Thumb, bounds);
Thumb.Arrange(bounds);
Thumb.AdjustDrag(adjust);
} }
ThumbCenterOffset = offset.X + (thumbLength * 0.5); ThumbCenterOffset = offset.X + (thumbLength * 0.5);
} }
_lastDrag = default;
return arrangeSize; return arrangeSize;
} }
@ -296,6 +305,12 @@ namespace Avalonia.Controls.Primitives
} }
} }
private Vector CalculateThumbAdjustment(Thumb thumb, Rect newThumbBounds)
{
var thumbDelta = newThumbBounds.Position - thumb.Bounds.Position;
return _lastDrag - thumbDelta;
}
private static void CoerceLength(ref double componentLength, double trackLength) private static void CoerceLength(ref double componentLength, double trackLength)
{ {
if (componentLength < 0) if (componentLength < 0)
@ -440,10 +455,17 @@ namespace Avalonia.Controls.Primitives
if (IgnoreThumbDrag) if (IgnoreThumbDrag)
return; return;
var value = Value;
var delta = ValueFromDistance(e.Vector.X, e.Vector.Y);
var factor = e.Vector / delta;
SetCurrentValue(ValueProperty, MathUtilities.Clamp( SetCurrentValue(ValueProperty, MathUtilities.Clamp(
Value + ValueFromDistance(e.Vector.X, e.Vector.Y), value + delta,
Minimum, Minimum,
Maximum)); Maximum));
// Record the part of the drag that actually had effect as the last drag delta.
_lastDrag = (Value - value) * factor;
} }
private void ShowChildren(bool visible) private void ShowChildren(bool visible)

3
src/Avalonia.Controls/Screens.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using Avalonia.Platform; using Avalonia.Platform;
@ -68,7 +69,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="window">The window impl for which to retrieve the Screen.</param> /// <param name="window">The window impl for which to retrieve the Screen.</param>
/// <returns>The <see cref="Screen"/>.</returns> /// <returns>The <see cref="Screen"/>.</returns>
[Obsolete("Use ScreenFromWindow(WindowBase) overload.")] [Obsolete("Use ScreenFromWindow(WindowBase) overload."), EditorBrowsable(EditorBrowsableState.Never)]
public Screen? ScreenFromWindow(IWindowBaseImpl window) public Screen? ScreenFromWindow(IWindowBaseImpl window)
{ {
return _iScreenImpl.ScreenFromWindow(window); return _iScreenImpl.ScreenFromWindow(window);

15
src/Avalonia.Controls/SystemDialog.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
@ -11,7 +12,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Base class for system file dialogs. /// Base class for system file dialogs.
/// </summary> /// </summary>
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API"), EditorBrowsable(EditorBrowsableState.Never)]
public abstract class FileDialog : FileSystemDialog public abstract class FileDialog : FileSystemDialog
{ {
/// <summary> /// <summary>
@ -29,7 +30,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Base class for system file and directory dialogs. /// Base class for system file and directory dialogs.
/// </summary> /// </summary>
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API"), EditorBrowsable(EditorBrowsableState.Never)]
public abstract class FileSystemDialog : SystemDialog public abstract class FileSystemDialog : SystemDialog
{ {
/// <summary> /// <summary>
@ -42,7 +43,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Represents a system dialog that prompts the user to select a location for saving a file. /// Represents a system dialog that prompts the user to select a location for saving a file.
/// </summary> /// </summary>
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API"), EditorBrowsable(EditorBrowsableState.Never)]
public class SaveFileDialog : FileDialog public class SaveFileDialog : FileDialog
{ {
/// <summary> /// <summary>
@ -91,7 +92,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Represents a system dialog that allows the user to select one or more files to open. /// Represents a system dialog that allows the user to select one or more files to open.
/// </summary> /// </summary>
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API"), EditorBrowsable(EditorBrowsableState.Never)]
public class OpenFileDialog : FileDialog public class OpenFileDialog : FileDialog
{ {
/// <summary> /// <summary>
@ -132,7 +133,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Represents a system dialog that allows the user to select a directory. /// Represents a system dialog that allows the user to select a directory.
/// </summary> /// </summary>
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API"), EditorBrowsable(EditorBrowsableState.Never)]
public class OpenFolderDialog : FileSystemDialog public class OpenFolderDialog : FileSystemDialog
{ {
/// <summary> /// <summary>
@ -167,7 +168,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Base class for system dialogs. /// Base class for system dialogs.
/// </summary> /// </summary>
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API"), EditorBrowsable(EditorBrowsableState.Never)]
public abstract class SystemDialog public abstract class SystemDialog
{ {
static SystemDialog() static SystemDialog()
@ -188,7 +189,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Represents a filter in an <see cref="OpenFileDialog"/> or an <see cref="SaveFileDialog"/>. /// Represents a filter in an <see cref="OpenFileDialog"/> or an <see cref="SaveFileDialog"/>.
/// </summary> /// </summary>
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API"), EditorBrowsable(EditorBrowsableState.Never)]
public class FileDialogFilter public class FileDialogFilter
{ {
/// <summary> /// <summary>

26
src/Avalonia.Controls/TextBlock.cs

@ -7,7 +7,6 @@ using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Utilities;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -565,7 +564,8 @@ namespace Avalonia.Controls
context.FillRectangle(background, new Rect(Bounds.Size)); context.FillRectangle(background, new Rect(Bounds.Size));
} }
var padding = Padding; var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
var top = padding.Top; var top = padding.Top;
var textHeight = TextLayout.Bounds.Height; var textHeight = TextLayout.Bounds.Height;
@ -659,7 +659,6 @@ namespace Avalonia.Controls
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
var scale = LayoutHelper.GetLayoutScale(this); var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
_constraint = availableSize.Deflate(padding); _constraint = availableSize.Deflate(padding);
@ -703,19 +702,24 @@ namespace Avalonia.Controls
} }
} }
var measuredSize = TextLayout.Bounds.Size.Inflate(padding); return TextLayout.Bounds.Size.Inflate(padding);
return measuredSize;
} }
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
if (HasComplexContent) var scale = LayoutHelper.GetLayoutScale(this);
{ var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); //Fixes: #11019
if (finalSize.Width < _constraint.Width)
{
_textLayout?.Dispose();
_textLayout = null;
_constraint = finalSize.Deflate(padding);
}
if (HasComplexContent)
{
var currentY = padding.Top; var currentY = padding.Top;
foreach (var textLine in TextLayout.TextLines) foreach (var textLine in TextLayout.TextLines)
@ -730,7 +734,7 @@ namespace Avalonia.Controls
&& controlRun.Control is Control control) && controlRun.Control is Control control)
{ {
control.Arrange( control.Arrange(
new Rect(new Point(currentX, currentY), new Rect(new Point(currentX, currentY),
new Size(control.DesiredSize.Width, textLine.Height))); new Size(control.DesiredSize.Width, textLine.Height)));
} }

3
src/Avalonia.Controls/TreeView.cs

@ -3,6 +3,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
@ -722,7 +723,7 @@ namespace Avalonia.Controls
} }
} }
[Obsolete] [Obsolete, EditorBrowsable(EditorBrowsableState.Never)]
private protected override ItemContainerGenerator CreateItemContainerGenerator() private protected override ItemContainerGenerator CreateItemContainerGenerator()
{ {
return new TreeItemContainerGenerator(this); return new TreeItemContainerGenerator(this);

2
src/Avalonia.Controls/TreeViewItem.cs

@ -165,7 +165,7 @@ namespace Avalonia.Controls
{ {
Key.Left => ApplyToItemOrRecursivelyIfCtrl(FocusAwareCollapseItem, e.KeyModifiers), Key.Left => ApplyToItemOrRecursivelyIfCtrl(FocusAwareCollapseItem, e.KeyModifiers),
Key.Right => ApplyToItemOrRecursivelyIfCtrl(ExpandItem, e.KeyModifiers), Key.Right => ApplyToItemOrRecursivelyIfCtrl(ExpandItem, e.KeyModifiers),
Key.Enter or Key.Space => ApplyToItemOrRecursivelyIfCtrl(IsExpanded ? CollapseItem : ExpandItem, e.KeyModifiers), Key.Enter => ApplyToItemOrRecursivelyIfCtrl(IsExpanded ? CollapseItem : ExpandItem, e.KeyModifiers),
// do not handle CTRL with numpad keys // do not handle CTRL with numpad keys
Key.Subtract => FocusAwareCollapseItem, Key.Subtract => FocusAwareCollapseItem,

3
src/Avalonia.Controls/Utils/StringUtils.cs

@ -1,3 +1,4 @@
using System;
using System.Globalization; using System.Globalization;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
@ -122,6 +123,8 @@ namespace Avalonia.Controls.Utils
{ {
return 0; return 0;
} }
cursor = Math.Min(cursor, text.Length);
int begin; int begin;
int i; int i;

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

@ -522,7 +522,6 @@ namespace Avalonia.Diagnostics.ViewModels
.GroupBy(x => x.Key) .GroupBy(x => x.Key)
.ToDictionary(x => x.Key, x => x.ToArray()); .ToDictionary(x => x.Key, x => x.ToArray());
TreePage.PropertiesFilter.FilterString = string.Empty;
var view = new DataGridCollectionView(properties); var view = new DataGridCollectionView(properties);
view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group))); view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group)));

5
src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs

@ -1,6 +1,7 @@
#nullable enable #nullable enable
using System; using System;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
@ -39,11 +40,11 @@ namespace Avalonia.Dialogs
return builder; return builder;
} }
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API"), EditorBrowsable(EditorBrowsableState.Never)]
public static Task<string[]> ShowManagedAsync(this OpenFileDialog dialog, Window parent, public static Task<string[]> ShowManagedAsync(this OpenFileDialog dialog, Window parent,
ManagedFileDialogOptions? options = null) => ShowManagedAsync<Window>(dialog, parent, options); ManagedFileDialogOptions? options = null) => ShowManagedAsync<Window>(dialog, parent, options);
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")] [Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API"), EditorBrowsable(EditorBrowsableState.Never)]
public static async Task<string[]> ShowManagedAsync<TWindow>(this OpenFileDialog dialog, Window parent, public static async Task<string[]> ShowManagedAsync<TWindow>(this OpenFileDialog dialog, Window parent,
ManagedFileDialogOptions? options = null) where TWindow : Window, new() ManagedFileDialogOptions? options = null) where TWindow : Window, new()
{ {

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

@ -13,7 +13,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Tmds.DBus.Protocol" Version="0.15.0" /> <PackageReference Include="Tmds.DBus.Protocol" Version="0.15.0" />
<PackageReference Include="Tmds.DBus.SourceGenerator" Version="0.0.5" PrivateAssets="All" /> <PackageReference Include="Tmds.DBus.SourceGenerator" Version="0.0.6" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

13
src/Avalonia.Native/AvaloniaNativeDragSource.cs

@ -4,7 +4,6 @@ using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
namespace Avalonia.Native namespace Avalonia.Native
@ -18,16 +17,6 @@ namespace Avalonia.Native
_factory = factory; _factory = factory;
} }
private static TopLevel FindRoot(object? element)
{
while (element is Interactive interactive && element is not Visual)
element = interactive.GetInteractiveParent();
if (element == null)
return null;
var visual = (Visual)element;
return TopLevel.GetTopLevel(visual);
}
class DndCallback : NativeCallbackBase, IAvnDndResultCallback class DndCallback : NativeCallbackBase, IAvnDndResultCallback
{ {
private TaskCompletionSource<DragDropEffects> _tcs; private TaskCompletionSource<DragDropEffects> _tcs;
@ -46,7 +35,7 @@ namespace Avalonia.Native
public Task<DragDropEffects> DoDragDrop(PointerEventArgs triggerEvent, IDataObject data, DragDropEffects allowedEffects) public Task<DragDropEffects> DoDragDrop(PointerEventArgs triggerEvent, IDataObject data, DragDropEffects allowedEffects)
{ {
// Sanity check // Sanity check
var tl = FindRoot(triggerEvent.Source); var tl = TopLevel.GetTopLevel(triggerEvent.Source as Visual);
var view = tl?.PlatformImpl as WindowBaseImpl; var view = tl?.PlatformImpl as WindowBaseImpl;
if (view == null) if (view == null)
throw new ArgumentException(); throw new ArgumentException();

12
src/Avalonia.Native/WindowImplBase.cs

@ -220,17 +220,17 @@ namespace Avalonia.Native
_parent.PositionChanged?.Invoke(position.ToAvaloniaPixelPoint()); _parent.PositionChanged?.Invoke(position.ToAvaloniaPixelPoint());
} }
void IAvnWindowBaseEvents.RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta) void IAvnWindowBaseEvents.RawMouseEvent(AvnRawMouseEventType type, ulong timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta)
{ {
_parent.RawMouseEvent(type, timeStamp, modifiers, point, delta); _parent.RawMouseEvent(type, timeStamp, modifiers, point, delta);
} }
int IAvnWindowBaseEvents.RawKeyEvent(AvnRawKeyEventType type, uint timeStamp, AvnInputModifiers modifiers, uint key) int IAvnWindowBaseEvents.RawKeyEvent(AvnRawKeyEventType type, ulong timeStamp, AvnInputModifiers modifiers, uint key)
{ {
return _parent.RawKeyEvent(type, timeStamp, modifiers, key).AsComBool(); return _parent.RawKeyEvent(type, timeStamp, modifiers, key).AsComBool();
} }
int IAvnWindowBaseEvents.RawTextInputEvent(uint timeStamp, string text) int IAvnWindowBaseEvents.RawTextInputEvent(ulong timeStamp, string text)
{ {
return _parent.RawTextInputEvent(timeStamp, text).AsComBool(); return _parent.RawTextInputEvent(timeStamp, text).AsComBool();
} }
@ -286,7 +286,7 @@ namespace Avalonia.Native
_native?.Activate(); _native?.Activate();
} }
public bool RawTextInputEvent(uint timeStamp, string text) public bool RawTextInputEvent(ulong timeStamp, string text)
{ {
if (_inputRoot is null) if (_inputRoot is null)
return false; return false;
@ -300,7 +300,7 @@ namespace Avalonia.Native
return args.Handled; return args.Handled;
} }
public bool RawKeyEvent(AvnRawKeyEventType type, uint timeStamp, AvnInputModifiers modifiers, uint key) public bool RawKeyEvent(AvnRawKeyEventType type, ulong timeStamp, AvnInputModifiers modifiers, uint key)
{ {
if (_inputRoot is null) if (_inputRoot is null)
return false; return false;
@ -319,7 +319,7 @@ namespace Avalonia.Native
return false; return false;
} }
public void RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta) public void RawMouseEvent(AvnRawMouseEventType type, ulong timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta)
{ {
if (_inputRoot is null) if (_inputRoot is null)
return; return;

7
src/Avalonia.Native/avn.idl

@ -1,6 +1,7 @@
@clr-namespace Avalonia.Native.Interop @clr-namespace Avalonia.Native.Interop
@clr-access internal @clr-access internal
@clr-map bool int @clr-map bool int
@clr-map u_int64_t ulong
@cpp-preamble @@ @cpp-preamble @@
#pragma once #pragma once
#include "com.h" #include "com.h"
@ -583,12 +584,12 @@ interface IAvnWindowBaseEvents : IUnknown
void Resized([const] AvnSize& size, AvnPlatformResizeReason reason); void Resized([const] AvnSize& size, AvnPlatformResizeReason reason);
void PositionChanged(AvnPoint position); void PositionChanged(AvnPoint position);
void RawMouseEvent(AvnRawMouseEventType type, void RawMouseEvent(AvnRawMouseEventType type,
uint timeStamp, u_int64_t timeStamp,
AvnInputModifiers modifiers, AvnInputModifiers modifiers,
AvnPoint point, AvnPoint point,
AvnVector delta); AvnVector delta);
bool RawKeyEvent(AvnRawKeyEventType type, uint timeStamp, AvnInputModifiers modifiers, uint key); bool RawKeyEvent(AvnRawKeyEventType type, u_int64_t timeStamp, AvnInputModifiers modifiers, uint key);
bool RawTextInputEvent(uint timeStamp, [const] char* text); bool RawTextInputEvent(u_int64_t timeStamp, [const] char* text);
void ScalingChanged(double scaling); void ScalingChanged(double scaling);
void RunRenderPriorityJobs(); void RunRenderPriorityJobs();
void LostFocus(); void LostFocus();

4
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@ -8,6 +8,8 @@ using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Platform; using Avalonia.Platform;
using System.ComponentModel;
namespace Avalonia.OpenGL.Controls namespace Avalonia.OpenGL.Controls
{ {
public abstract class OpenGlControlBase : Control public abstract class OpenGlControlBase : Control
@ -217,7 +219,7 @@ namespace Avalonia.OpenGL.Controls
return true; return true;
} }
[Obsolete("Use RequestNextFrameRendering()")] [Obsolete("Use RequestNextFrameRendering()"), EditorBrowsable(EditorBrowsableState.Never)]
// ReSharper disable once MemberCanBeProtected.Global // ReSharper disable once MemberCanBeProtected.Global
public new void InvalidateVisual() => RequestNextFrameRendering(); public new void InvalidateVisual() => RequestNextFrameRendering();

2
src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts

@ -266,7 +266,7 @@ export class InputHelper {
} }
public static setCursor(inputElement: HTMLInputElement, kind: string) { public static setCursor(inputElement: HTMLInputElement, kind: string) {
if (kind === "pointer") { if (kind === "default") {
inputElement.style.removeProperty("cursor"); inputElement.style.removeProperty("cursor");
} else { } else {
inputElement.style.cursor = kind; inputElement.style.cursor = kind;

7
src/Directory.Build.props

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

13
src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -2,11 +2,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
@ -127,7 +125,8 @@ namespace Avalonia.Headless
IGlyphTypeface glyphTypeface, IGlyphTypeface glyphTypeface,
double fontRenderingEmSize, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, IReadOnlyList<GlyphInfo> glyphInfos,
Point baselineOrigin) Point baselineOrigin,
Rect bounds)
{ {
return new HeadlessGlyphRunStub(); return new HeadlessGlyphRunStub();
} }
@ -138,6 +137,10 @@ namespace Avalonia.Headless
public Point BaselineOrigin => new Point(0, 8); public Point BaselineOrigin => new Point(0, 8);
public IGlyphTypeface GlyphTypeface => new HeadlessGlyphTypefaceImpl();
public double FontRenderingEmSize => 12;
public void Dispose() public void Dispose()
{ {
} }
@ -374,6 +377,8 @@ namespace Avalonia.Headless
public Matrix Transform { get; set; } public Matrix Transform { get; set; }
public RenderOptions RenderOptions { get; set; }
public void Clear(Color color) public void Clear(Color color)
{ {
@ -451,7 +456,7 @@ namespace Avalonia.Headless
{ {
} }
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{ {
} }

39
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -24,10 +24,8 @@ namespace Avalonia.Skia
private readonly Vector _dpi; private readonly Vector _dpi;
private readonly Stack<PaintWrapper> _maskStack = new(); private readonly Stack<PaintWrapper> _maskStack = new();
private readonly Stack<double> _opacityStack = new(); private readonly Stack<double> _opacityStack = new();
private readonly Stack<BitmapBlendingMode> _blendingModeStack = new();
private readonly Matrix? _postTransform; private readonly Matrix? _postTransform;
private double _currentOpacity = 1.0f; private double _currentOpacity = 1.0f;
private BitmapBlendingMode _currentBlendingMode = BitmapBlendingMode.SourceOver;
private readonly bool _canTextUseLcdRendering; private readonly bool _canTextUseLcdRendering;
private Matrix _currentTransform; private Matrix _currentTransform;
private bool _disposed; private bool _disposed;
@ -168,6 +166,8 @@ namespace Avalonia.Skia
public SKCanvas Canvas { get; } public SKCanvas Canvas { get; }
public SKSurface? Surface { get; } public SKSurface? Surface { get; }
public RenderOptions RenderOptions { get; set; }
private void CheckLease() private void CheckLease()
{ {
if (_leased) if (_leased)
@ -182,7 +182,7 @@ namespace Avalonia.Skia
} }
/// <inheritdoc /> /// <inheritdoc />
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{ {
CheckLease(); CheckLease();
var drawableImage = (IDrawableBitmapImpl)source.Item; var drawableImage = (IDrawableBitmapImpl)source.Item;
@ -191,8 +191,8 @@ namespace Avalonia.Skia
var paint = SKPaintCache.Shared.Get(); var paint = SKPaintCache.Shared.Get();
paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * (_useOpacitySaveLayer ? 1 : _currentOpacity))); paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * (_useOpacitySaveLayer ? 1 : _currentOpacity)));
paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality(); paint.FilterQuality = RenderOptions.BitmapInterpolationMode.ToSKFilterQuality();
paint.BlendMode = _currentBlendingMode.ToSKBlendMode(); paint.BlendMode = RenderOptions.BitmapBlendingMode.ToSKBlendMode();
drawableImage.Draw(this, s, d, paint); drawableImage.Draw(this, s, d, paint);
SKPaintCache.Shared.ReturnReset(paint); SKPaintCache.Shared.ReturnReset(paint);
@ -203,7 +203,7 @@ namespace Avalonia.Skia
{ {
CheckLease(); CheckLease();
PushOpacityMask(opacityMask, opacityMaskRect); PushOpacityMask(opacityMask, opacityMaskRect);
DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default); DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect);
PopOpacityMask(); PopOpacityMask();
} }
@ -519,7 +519,9 @@ namespace Avalonia.Skia
{ {
var glyphRunImpl = (GlyphRunImpl)glyphRun.Item; var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;
Canvas.DrawText(glyphRunImpl.TextBlob, (float)glyphRun.Item.BaselineOrigin.X, var textBlob = glyphRunImpl.GetTextBlob(RenderOptions);
Canvas.DrawText(textBlob, (float)glyphRun.Item.BaselineOrigin.X,
(float)glyphRun.Item.BaselineOrigin.Y, paintWrapper.Paint); (float)glyphRun.Item.BaselineOrigin.Y, paintWrapper.Paint);
} }
} }
@ -649,21 +651,6 @@ namespace Avalonia.Skia
Canvas.Restore(); Canvas.Restore();
} }
/// <inheritdoc />
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
CheckLease();
_blendingModeStack.Push(_currentBlendingMode);
_currentBlendingMode = blendingMode;
}
/// <inheritdoc />
public void PopBitmapBlendMode()
{
CheckLease();
_currentBlendingMode = _blendingModeStack.Pop();
}
/// <inheritdoc /> /// <inheritdoc />
public void PushOpacityMask(IBrush mask, Rect bounds) public void PushOpacityMask(IBrush mask, Rect bounds)
{ {
@ -905,12 +892,14 @@ namespace Avalonia.Skia
context.Clear(Colors.Transparent); context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip); context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform; context.Transform = calc.IntermediateTransform;
context.RenderOptions = RenderOptions;
context.DrawBitmap( context.DrawBitmap(
RefCountable.CreateUnownedNotClonable(tileBrushImage), RefCountable.CreateUnownedNotClonable(tileBrushImage),
1, 1,
sourceRect, sourceRect,
targetRect, targetRect);
tileBrush.BitmapInterpolationMode);
context.PopClip(); context.PopClip();
} }
@ -1143,7 +1132,7 @@ namespace Avalonia.Skia
{ {
var paintWrapper = new PaintWrapper(paint); var paintWrapper = new PaintWrapper(paint);
paint.IsAntialias = true; paint.IsAntialias = RenderOptions.EdgeMode != EdgeMode.Aliased;
double opacity = brush.Opacity * (_useOpacitySaveLayer ? 1 :_currentOpacity); double opacity = brush.Opacity * (_useOpacitySaveLayer ? 1 :_currentOpacity);

118
src/Skia/Avalonia.Skia/GlyphRunImpl.cs

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform; using Avalonia.Platform;
using SkiaSharp; using SkiaSharp;
@ -7,30 +9,122 @@ namespace Avalonia.Skia
{ {
internal class GlyphRunImpl : IGlyphRunImpl internal class GlyphRunImpl : IGlyphRunImpl
{ {
public GlyphRunImpl(SKTextBlob textBlob, Size size, Point baselineOrigin) private readonly GlyphTypefaceImpl _glyphTypefaceImpl;
private readonly ushort[] _glyphIndices;
private readonly SKPoint[] _glyphPositions;
private readonly Dictionary<SKFontEdging, SKTextBlob> _textBlobCache = new(1);
public GlyphRunImpl(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{ {
TextBlob = textBlob ?? throw new ArgumentNullException(nameof(textBlob)); if (glyphTypeface == null)
{
throw new ArgumentNullException(nameof(glyphTypeface));
}
_glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
if (glyphInfos == null)
{
throw new ArgumentNullException(nameof(glyphInfos));
}
var count = glyphInfos.Count;
_glyphIndices = new ushort[count];
_glyphPositions = new SKPoint[count];
var currentX = 0.0;
for (int i = 0; i < count; i++)
{
var glyphInfo = glyphInfos[i];
var offset = glyphInfo.GlyphOffset;
_glyphIndices[i] = glyphInfo.GlyphIndex;
Bounds = new Rect(new Point(baselineOrigin.X, 0), size); _glyphPositions[i] = new SKPoint((float)(currentX + offset.X), (float)offset.Y);
currentX += glyphInfos[i].GlyphAdvance;
}
FontRenderingEmSize = fontRenderingEmSize;
BaselineOrigin = baselineOrigin; BaselineOrigin = baselineOrigin;
Bounds = bounds;
} }
/// <summary> public IGlyphTypeface GlyphTypeface => _glyphTypefaceImpl;
/// Gets the text blob to draw.
/// </summary>
public SKTextBlob TextBlob { get; }
public Rect Bounds { get; } public double FontRenderingEmSize { get; }
public Point BaselineOrigin { get; } public Point BaselineOrigin { get; }
public IReadOnlyList<float> GetIntersections(float upperBound, float lowerBound) => public Rect Bounds { get; }
TextBlob.GetIntercepts(lowerBound, upperBound);
public SKTextBlob GetTextBlob(RenderOptions renderOptions)
{
var edging = SKFontEdging.SubpixelAntialias;
switch (renderOptions.TextRenderingMode)
{
case TextRenderingMode.Alias:
edging = SKFontEdging.Alias;
break;
case TextRenderingMode.Antialias:
edging = SKFontEdging.Antialias;
break;
case TextRenderingMode.Unspecified:
edging = renderOptions.EdgeMode == EdgeMode.Aliased ? SKFontEdging.Alias : SKFontEdging.SubpixelAntialias;
break;
}
if (_textBlobCache.TryGetValue(edging, out var textBlob))
{
return textBlob;
}
void IDisposable.Dispose() var font = SKFontCache.Shared.Get();
font.LinearMetrics = true;
font.Subpixel = edging == SKFontEdging.SubpixelAntialias;
font.Edging = edging;
font.Hinting = SKFontHinting.Full;
font.Size = (float)FontRenderingEmSize;
font.Typeface = _glyphTypefaceImpl.Typeface;
font.Embolden = (_glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0;
font.SkewX = (_glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0;
var builder = SKTextBlobBuilderCache.Shared.Get();
var runBuffer = builder.AllocatePositionedRun(font, _glyphIndices.Length);
runBuffer.SetPositions(_glyphPositions);
runBuffer.SetGlyphs(_glyphIndices);
SKFontCache.Shared.Return(font);
textBlob = builder.Build();
SKTextBlobBuilderCache.Shared.Return(builder);
_textBlobCache.Add(edging, textBlob);
return textBlob;
}
public void Dispose()
{ {
TextBlob.Dispose(); foreach (var pair in _textBlobCache)
{
pair.Value.Dispose();
}
}
public IReadOnlyList<float> GetIntersections(float lowerLimit, float upperLimit)
{
var textBlob = GetTextBlob(default);
return textBlob.GetIntercepts(lowerLimit, upperLimit);
} }
} }
} }

30
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

@ -27,24 +27,24 @@ namespace Avalonia.Skia
const double defaultFontRenderingEmSize = 12.0; const double defaultFontRenderingEmSize = 12.0;
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.HorizontalAscender, out var ascent);
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.HorizontalDescender, out var descent);
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.HorizontalLineGap, out var lineGap);
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughOffset);
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughSize);
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlineOffset);
Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineSize);
Metrics = new FontMetrics Metrics = new FontMetrics
{ {
DesignEmHeight = (short)Typeface.UnitsPerEm, DesignEmHeight = (short)Typeface.UnitsPerEm,
Ascent = (int)(metrics.Ascent / defaultFontRenderingEmSize * Typeface.UnitsPerEm), Ascent = -ascent,
Descent = (int)(metrics.Descent / defaultFontRenderingEmSize * Typeface.UnitsPerEm), Descent = -descent,
LineGap = (int)(metrics.Leading / defaultFontRenderingEmSize * Typeface.UnitsPerEm), LineGap = lineGap,
UnderlinePosition = metrics.UnderlinePosition != null ? UnderlinePosition = -underlineOffset,
(int)(metrics.UnderlinePosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) : UnderlineThickness = underlineSize,
0, StrikethroughPosition = -strikethroughOffset,
UnderlineThickness = metrics.UnderlineThickness != null ? StrikethroughThickness = strikethroughSize,
(int)(metrics.UnderlineThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
0,
StrikethroughPosition = metrics.StrikeoutPosition != null ?
(int)(metrics.StrikeoutPosition / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
0,
StrikethroughThickness = metrics.StrikeoutThickness != null ?
(int)(metrics.StrikeoutThickness / defaultFontRenderingEmSize * Typeface.UnitsPerEm) :
0,
IsFixedPitch = Typeface.IsFixedPitch IsFixedPitch = Typeface.IsFixedPitch
}; };

1
src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs

@ -1,5 +1,4 @@
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {

76
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -5,8 +5,8 @@ using Avalonia.Media;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Media.TextFormatting;
using SkiaSharp; using SkiaSharp;
using Avalonia.Media.TextFormatting;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {
@ -191,16 +191,7 @@ namespace Avalonia.Skia
throw new ArgumentException("Height can't be less than 1", nameof(size)); throw new ArgumentException("Height can't be less than 1", nameof(size));
} }
var createInfo = new SurfaceRenderTarget.CreateInfo return new RenderTargetBitmapImpl(size, dpi);
{
Width = size.Width,
Height = size.Height,
Dpi = dpi,
DisableTextLcdRendering = false,
DisableManualFbo = true,
};
return new SurfaceRenderTarget(createInfo);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -209,67 +200,10 @@ namespace Avalonia.Skia
return new WriteableBitmapImpl(size, dpi, format, alphaFormat); return new WriteableBitmapImpl(size, dpi, format, alphaFormat);
} }
public IGlyphRunImpl CreateGlyphRun( public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IGlyphTypeface glyphTypeface, IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos,
Point baselineOrigin)
{ {
if (glyphTypeface == null) return new GlyphRunImpl(glyphTypeface, fontRenderingEmSize, glyphInfos, baselineOrigin, bounds);
{
throw new ArgumentNullException(nameof(glyphTypeface));
}
if (glyphInfos == null)
{
throw new ArgumentNullException(nameof(glyphInfos));
}
var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
var font = SKFontCache.Shared.Get();
font.LinearMetrics = true;
font.Subpixel = true;
font.Edging = SKFontEdging.SubpixelAntialias;
font.Hinting = SKFontHinting.Full;
font.Size = (float)fontRenderingEmSize;
font.Typeface = glyphTypefaceImpl.Typeface;
font.Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0;
font.SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0;
var builder = SKTextBlobBuilderCache.Shared.Get();
var count = glyphInfos.Count;
var runBuffer = builder.AllocatePositionedRun(font, count);
var glyphSpan = runBuffer.GetGlyphSpan();
var positionSpan = runBuffer.GetPositionSpan();
SKFontCache.Shared.Return(font);
var width = 0.0;
for (int i = 0; i < count; i++)
{
var glyphInfo = glyphInfos[i];
var offset = glyphInfo.GlyphOffset;
glyphSpan[i] = glyphInfo.GlyphIndex;
positionSpan[i] = new SKPoint((float)(width + offset.X), (float)offset.Y);
width += glyphInfo.GlyphAdvance;
}
var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight;
var height = glyphTypeface.Metrics.LineSpacing * scale;
var skTextBlob = builder.Build();
SKTextBlobBuilderCache.Shared.Return(builder);
return new GlyphRunImpl(skTextBlob, new Size(width, height), baselineOrigin);
} }
} }
} }

31
src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs

@ -0,0 +1,31 @@
using System.IO;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using SkiaSharp;
namespace Avalonia.Skia;
internal class RenderTargetBitmapImpl : WriteableBitmapImpl,
IRenderTargetBitmapImpl,
IFramebufferPlatformSurface
{
private readonly FramebufferRenderTarget _renderTarget;
public RenderTargetBitmapImpl(PixelSize size, Vector dpi) : base(size, dpi,
SKImageInfo.PlatformColorType == SKColorType.Rgba8888 ? PixelFormats.Rgba8888 : PixelFormat.Bgra8888,
AlphaFormat.Premul)
{
_renderTarget = new FramebufferRenderTarget(this);
}
public IDrawingContextImpl CreateDrawingContext() => _renderTarget.CreateDrawingContext();
public bool IsCorrupted => false;
public override void Dispose()
{
_renderTarget.Dispose();
base.Dispose();
}
}

4
src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

@ -19,7 +19,8 @@ namespace Avalonia.Skia
return SKFilterQuality.Medium; return SKFilterQuality.Medium;
case BitmapInterpolationMode.HighQuality: case BitmapInterpolationMode.HighQuality:
return SKFilterQuality.High; return SKFilterQuality.High;
case BitmapInterpolationMode.Default: case BitmapInterpolationMode.None:
case BitmapInterpolationMode.Unspecified:
return SKFilterQuality.None; return SKFilterQuality.None;
default: default:
throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null);
@ -30,6 +31,7 @@ namespace Avalonia.Skia
{ {
switch (blendingMode) switch (blendingMode)
{ {
case BitmapBlendingMode.Unspecified:
case BitmapBlendingMode.SourceOver: case BitmapBlendingMode.SourceOver:
return SKBlendMode.SrcOver; return SKBlendMode.SrcOver;
case BitmapBlendingMode.Source: case BitmapBlendingMode.Source:

2
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@ -131,7 +131,7 @@ namespace Avalonia.Skia
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public virtual void Dispose()
{ {
_bitmap.Dispose(); _bitmap.Dispose();
} }

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

Loading…
Cancel
Save