Browse Source

Merge branch 'master' into poker/wrappanel-enhance

pull/20549/head
Poker 1 week ago
committed by GitHub
parent
commit
913babaaae
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 264
      api/Avalonia.nupkg.xml
  2. 3
      packages/Avalonia/AvaloniaBuildTasks.targets
  3. 5
      packages/Avalonia/AvaloniaRules.Project.xml
  4. 7
      samples/TextTestApp/InteractiveLineControl.cs
  5. 7
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  6. 6
      src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs
  7. 22
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  8. 7
      src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs
  9. 37
      src/Avalonia.Base/Media/FormattedText.cs
  10. 14
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  11. 4
      src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
  12. 23
      src/Avalonia.Base/Media/TextFormatting/GenericTextRunProperties.cs
  13. 4
      src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs
  14. 4
      src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs
  15. 4
      src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs
  16. 10
      src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs
  17. 51
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  18. 11
      src/Avalonia.Base/Media/TextFormatting/TextRunProperties.cs
  19. 19
      src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs
  20. 5
      src/Avalonia.Base/Platform/ILockedFramebuffer.cs
  21. 11
      src/Avalonia.Base/Platform/IReadableBitmapImpl.cs
  22. 4
      src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
  23. 4
      src/Avalonia.Base/Platform/LockedFramebuffer.cs
  24. 14
      src/Avalonia.Base/Platform/RetainedFramebuffer.cs
  25. 245
      src/Avalonia.Base/Utilities/StringTokenizer.cs
  26. 3
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  27. 4
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  28. 8
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  29. 16
      src/Avalonia.Controls/Design.cs
  30. 10
      src/Avalonia.Controls/Documents/Inline.cs
  31. 37
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  32. 6
      src/Avalonia.Controls/Primitives/TextSearch.cs
  33. 1
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs
  34. 15
      src/Avalonia.Controls/SelectableTextBlock.cs
  35. 4
      src/Avalonia.Controls/TextBlock.cs
  36. 1
      src/Avalonia.Native/DeferredFramebuffer.cs
  37. 2
      src/Avalonia.X11/X11CursorFactory.cs
  38. 2
      src/Avalonia.X11/X11FramebufferSurface.cs
  39. 2
      src/Avalonia.X11/X11IconLoader.cs
  40. 4
      src/Browser/Avalonia.Browser/Rendering/BrowserSoftwareRenderTarget.cs
  41. 111
      src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs
  42. 1
      src/Headless/Avalonia.Headless/Avalonia.Headless.csproj
  43. 6
      src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  44. 4
      src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs
  45. 1
      src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
  46. 11
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs
  47. 7
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
  48. 15
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  49. 33
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
  50. 69
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAddSourceInfoTransformer.cs
  51. 191
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs
  52. 343
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResourceTransformer.cs
  53. 12
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  54. 26
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlAstNewClrObjectHelper.cs
  55. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  56. 151
      src/Markup/Avalonia.Markup.Xaml/Diagnostics/XamlSourceInfo.cs
  57. 6
      src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs
  58. 5
      src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs
  59. 2
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  60. 6
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  61. 2
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  62. 2
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  63. 14
      src/Windows/Avalonia.Win32/Interop/Win32Icon.cs
  64. 81
      tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs
  65. 10
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs
  66. 11
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs
  67. 4
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs
  68. 583
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlSourceInfoTests.cs
  69. 2
      tests/Avalonia.RenderTests/Composition/DirectFbCompositionTests.cs
  70. 7
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  71. 2
      tests/Avalonia.UnitTests/CompositorTestServices.cs

264
api/Avalonia.nupkg.xml

@ -19,6 +19,18 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Platform.IReadableBitmapWithAlphaImpl</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Utilities.StringTokenizer</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Controls.Primitives.IScrollable</Target>
@ -49,6 +61,18 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Platform.IReadableBitmapWithAlphaImpl</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Utilities.StringTokenizer</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Controls.Primitives.IScrollable</Target>
@ -73,6 +97,24 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.KeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.DrawingImage.get_Viewbox</Target>
@ -97,6 +139,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.Imaging.Bitmap.CopyPixels(Avalonia.Platform.ILockedFramebuffer,Avalonia.Platform.AlphaFormat)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.StreamGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection)</Target>
@ -121,6 +169,48 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.TextCollapsingProperties.CreateCollapsedRuns(Avalonia.Media.TextFormatting.TextLine,System.Int32,Avalonia.Media.FlowDirection,Avalonia.Media.TextFormatting.TextRun)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}})</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}})</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.GlyphTypeface,System.Collections.Generic.IReadOnlyList{Avalonia.Media.FontFeature},System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.GlyphTypeface,System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect)</Target>
@ -157,6 +247,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Platform.LockedFramebuffer.#ctor(System.IntPtr,Avalonia.PixelSize,System.Int32,Avalonia.Vector,Avalonia.Platform.PixelFormat,System.Action)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection)</Target>
@ -181,6 +277,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Documents.Inline.TextDecorationsProperty</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TextBlock.LetterSpacingProperty</Target>
@ -223,6 +325,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.Control)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.ITemplate{Avalonia.Controls.Control})</Target>
@ -259,6 +367,18 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.SetText(Avalonia.Controls.Control,System.String)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Platform.Screen.#ctor(System.Double,Avalonia.PixelRect,Avalonia.PixelRect,System.Boolean)</Target>
@ -343,6 +463,24 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.KeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.DrawingImage.get_Viewbox</Target>
@ -367,6 +505,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.Imaging.Bitmap.CopyPixels(Avalonia.Platform.ILockedFramebuffer,Avalonia.Platform.AlphaFormat)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.StreamGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection)</Target>
@ -391,6 +535,48 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.GenericTextRunProperties.#ctor(Avalonia.Media.Typeface,System.Double,Avalonia.Media.TextDecorationCollection,Avalonia.Media.IBrush,Avalonia.Media.IBrush,Avalonia.Media.BaselineAlignment,System.Globalization.CultureInfo)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.TextCollapsingProperties.CreateCollapsedRuns(Avalonia.Media.TextFormatting.TextLine,System.Int32,Avalonia.Media.FlowDirection,Avalonia.Media.TextFormatting.TextRun)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,Avalonia.Media.FontFeatureCollection,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}})</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.TextLayout.#ctor(System.String,Avalonia.Media.Typeface,System.Double,Avalonia.Media.IBrush,Avalonia.Media.TextAlignment,Avalonia.Media.TextWrapping,Avalonia.Media.TextTrimming,Avalonia.Media.TextDecorationCollection,Avalonia.Media.FlowDirection,System.Double,System.Double,System.Double,System.Double,System.Int32,System.Collections.Generic.IReadOnlyList{Avalonia.Utilities.ValueSpan{Avalonia.Media.TextFormatting.TextRunProperties}})</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.GlyphTypeface,System.Collections.Generic.IReadOnlyList{Avalonia.Media.FontFeature},System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.TextFormatting.TextShaperOptions.#ctor(Avalonia.Media.GlyphTypeface,System.Double,System.SByte,System.Globalization.CultureInfo,System.Double,System.Double)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect)</Target>
@ -427,6 +613,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Platform.LockedFramebuffer.#ctor(System.IntPtr,Avalonia.PixelSize,System.Int32,Avalonia.Vector,Avalonia.Platform.PixelFormat,System.Action)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection)</Target>
@ -451,6 +643,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Documents.Inline.TextDecorationsProperty</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TextBlock.LetterSpacingProperty</Target>
@ -493,6 +691,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.Control)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.ITemplate{Avalonia.Controls.Control})</Target>
@ -529,6 +733,18 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.SetText(Avalonia.Controls.Control,System.String)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Platform.Screen.#ctor(System.Double,Avalonia.PixelRect,Avalonia.PixelRect,System.Boolean)</Target>
@ -619,6 +835,12 @@
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers,System.Nullable{Avalonia.Input.KeyDeviceType})</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.IDrawingContextImpl.PopTextOptions</Target>
@ -667,6 +889,18 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.Platform.ILockedFramebuffer.AlphaFormat</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.Platform.IReadableBitmapImpl.AlphaFormat</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer)</Target>
@ -727,6 +961,12 @@
<Left>baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers,System.Nullable{Avalonia.Input.KeyDeviceType})</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer)</Target>
@ -811,6 +1051,18 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.Platform.ILockedFramebuffer.AlphaFormat</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.Platform.IReadableBitmapImpl.AlphaFormat</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)</Target>
@ -895,12 +1147,24 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Platform.IWriteableBitmapImpl</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Media.StreamGeometryContext</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Platform.IWriteableBitmapImpl</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Platform.Screen</Target>

3
packages/Avalonia/AvaloniaBuildTasks.targets

@ -134,6 +134,8 @@
<AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
<AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
<AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">false</AvaloniaXamlVerboseExceptions>
<AvaloniaXamlCreateSourceInfo Condition="'$(AvaloniaXamlCreateSourceInfo)' == '' AND '$(Configuration)' == 'Debug'">true</AvaloniaXamlCreateSourceInfo>
<AvaloniaXamlCreateSourceInfo Condition="'$(AvaloniaXamlCreateSourceInfo)' == '' AND '$(Configuration)' != 'Debug'">false</AvaloniaXamlCreateSourceInfo>
</PropertyGroup>
<ItemGroup>
@ -162,6 +164,7 @@
DelaySign="$(DelaySign)"
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
CreateSourceInfo="$(AvaloniaXamlCreateSourceInfo)"
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
VerboseExceptions="$(AvaloniaXamlVerboseExceptions)"
AnalyzerConfigFiles="@(EditorConfigFiles)"/>

5
packages/Avalonia/AvaloniaRules.Project.xml

@ -31,6 +31,11 @@
Description="Allow debug XAML compilation"
Category="Debug" />
<BoolProperty Name="AvaloniaXamlCreateSourceInfo"
DisplayName="Generate XAML Source Info"
Description="When enabled, the XAML compiler embeds SourceInfo metadata (file path, line, and column) into generated code. This allows tools and debuggers to locate the original .axaml source position of a selected element at runtime or in the designer."
Category="Debug" />
<BoolProperty Name="AvaloniaXamlVerboseExceptions"
DisplayName="Report verbose internal exceptions with stack traces"
Description="Also includes inner exceptions"

7
samples/TextTestApp/InteractiveLineControl.cs

@ -255,12 +255,15 @@ namespace TextTestApp
private GenericTextRunProperties CreateTextRunProperties()
{
Typeface typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
return new GenericTextRunProperties(typeface, FontFeatures, FontSize,
return new GenericTextRunProperties(
typeface,
FontSize,
textDecorations: null,
foregroundBrush: Brushes.Black,
backgroundBrush: null,
baselineAlignment: BaselineAlignment.Baseline,
cultureInfo: null);
cultureInfo: null,
fontFeatures: FontFeatures);
}
// TextParagraphProperties

7
src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

@ -25,10 +25,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
Size = new PixelSize(rc.right, rc.bottom);
ANativeWindow_lock(_window, &buffer, &rc);
Format = buffer.format == AndroidPixelFormat.WINDOW_FORMAT_RGB_565
? PixelFormat.Rgb565 : PixelFormat.Rgba8888;
(Format, AlphaFormat, RowBytes) = buffer.format == AndroidPixelFormat.WINDOW_FORMAT_RGB_565 ?
(PixelFormat.Rgb565, AlphaFormat.Opaque, buffer.stride * 2) :
(PixelFormat.Rgba8888, AlphaFormat.Premul, buffer.stride * 4);
RowBytes = buffer.stride * (Format == PixelFormat.Rgb565 ? 2 : 4);
Address = buffer.bits;
Dpi = new Vector(96, 96) * scaling;
@ -46,6 +46,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public int RowBytes { get; }
public Vector Dpi { get; }
public PixelFormat Format { get; }
public AlphaFormat AlphaFormat { get; }
[DllImport("android")]
internal static extern IntPtr ANativeWindow_fromSurface(IntPtr jniEnv, IntPtr handle);

6
src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs

@ -24,9 +24,11 @@ namespace Avalonia.Input
/// <param name="element">The current element.</param>
/// <param name="direction">The direction to move.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
void Move(
/// <param name="deviceType">The device type used to move the focus.</param>
bool Move(
IInputElement element,
NavigationDirection direction,
KeyModifiers keyModifiers = KeyModifiers.None);
KeyModifiers keyModifiers = KeyModifiers.None,
KeyDeviceType? deviceType = null);
}
}

22
src/Avalonia.Base/Input/KeyboardNavigationHandler.cs

@ -98,22 +98,12 @@ namespace Avalonia.Input
return result;
}
/// <summary>
/// Moves the focus in the specified direction.
/// </summary>
/// <param name="element">The current element.</param>
/// <param name="direction">The direction to move.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
public void Move(
/// <inheritdoc />
public bool Move(
IInputElement? element,
NavigationDirection direction,
KeyModifiers keyModifiers = KeyModifiers.None)
{
MovePrivate(element, direction, keyModifiers, null);
}
// TODO12: remove MovePrivate, and make Move return boolean. Or even remove whole KeyboardNavigationHandler.
private bool MovePrivate(IInputElement? element, NavigationDirection direction, KeyModifiers keyModifiers, KeyDeviceType? deviceType)
KeyModifiers keyModifiers = KeyModifiers.None,
KeyDeviceType? deviceType = null)
{
var next = GetNextPrivate(element, _owner, direction, deviceType);
@ -140,7 +130,7 @@ namespace Avalonia.Input
var current = FocusManager.GetFocusManager(e.Source as IInputElement)?.GetFocusedElement();
var direction = (e.KeyModifiers & KeyModifiers.Shift) == 0 ?
NavigationDirection.Next : NavigationDirection.Previous;
e.Handled = MovePrivate(current, direction, e.KeyModifiers, e.KeyDeviceType);
e.Handled = Move(current, direction, e.KeyModifiers, e.KeyDeviceType);
}
else if (e.Key is Key.Left or Key.Right or Key.Up or Key.Down)
{
@ -153,7 +143,7 @@ namespace Avalonia.Input
Key.Down => NavigationDirection.Down,
_ => throw new ArgumentOutOfRangeException()
};
e.Handled = MovePrivate(current, direction, e.KeyModifiers, e.KeyDeviceType);
e.Handled = Move(current, direction, e.KeyModifiers, e.KeyDeviceType);
}
}

7
src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs

@ -82,13 +82,6 @@ namespace Avalonia.Input.TextInput
{
SetPreeditText(preeditText);
}
//TODO12: remove
[Obsolete]
public virtual void ShowInputPanel()
{
RaiseInputPaneActivationRequested();
}
protected virtual void RaiseTextViewVisualChanged()
{

37
src/Avalonia.Base/Media/FormattedText.cs

@ -184,13 +184,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
foregroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
runProps.CultureInfo
runProps.CultureInfo,
runProps.FontFeatures
);
#pragma warning restore 6506
@ -240,13 +240,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
fontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
runProps.CultureInfo
runProps.CultureInfo,
fontFeatures
);
#pragma warning restore 6506
@ -328,14 +328,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
new Typeface(fontFamily, oldTypeface.Style, oldTypeface.Weight),
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
runProps.CultureInfo
);
runProps.CultureInfo,
runProps.FontFeatures);
#pragma warning restore 6506
_latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition,
@ -388,13 +387,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
runProps.FontFeatures,
emSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
runProps.CultureInfo
runProps.CultureInfo,
runProps.FontFeatures
);
_latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition,
@ -451,13 +450,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
culture
culture,
runProps.FontFeatures
);
#pragma warning restore 6506
@ -511,13 +510,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
new Typeface(oldTypeface.FontFamily, oldTypeface.Style, weight),
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
runProps.CultureInfo
runProps.CultureInfo,
runProps.FontFeatures
);
#pragma warning restore 6506
_latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition);
@ -568,13 +567,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
new Typeface(oldTypeface.FontFamily, style, oldTypeface.Weight),
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
runProps.CultureInfo
runProps.CultureInfo,
runProps.FontFeatures
);
#pragma warning restore 6506
@ -625,13 +624,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
typeface,
runProps.FontFeatures,
runProps.FontRenderingEmSize,
runProps.TextDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
runProps.CultureInfo
runProps.CultureInfo,
runProps.FontFeatures
);
#pragma warning restore 6506
@ -683,13 +682,13 @@ namespace Avalonia.Media
var newProps = new GenericTextRunProperties(
runProps.Typeface,
runProps.FontFeatures,
runProps.FontRenderingEmSize,
textDecorations,
runProps.ForegroundBrush,
runProps.BackgroundBrush,
runProps.BaselineAlignment,
runProps.CultureInfo
runProps.CultureInfo,
runProps.FontFeatures
);
#pragma warning restore 6506

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

@ -1,5 +1,4 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using Avalonia.Platform;
@ -177,7 +176,7 @@ namespace Avalonia.Media.Imaging
public virtual PixelFormat? Format => (PlatformImpl.Item as IReadableBitmapImpl)?.Format;
public virtual AlphaFormat? AlphaFormat => (PlatformImpl.Item as IReadableBitmapWithAlphaImpl)?.AlphaFormat;
public virtual AlphaFormat? AlphaFormat => (PlatformImpl.Item as IReadableBitmapImpl)?.AlphaFormat;
private protected unsafe void CopyPixelsCore(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride,
ILockedFramebuffer fb)
@ -237,16 +236,15 @@ namespace Avalonia.Media.Imaging
/// Copies pixels to the target buffer and transcodes the pixel and alpha format if needed.
/// </summary>
/// <param name="buffer">The target buffer.</param>
/// <param name="alphaFormat">The alpha format.</param>
/// <exception cref="NotSupportedException"></exception>
public void CopyPixels(ILockedFramebuffer buffer, AlphaFormat alphaFormat)
public void CopyPixels(ILockedFramebuffer buffer)
{
if (PlatformImpl.Item is not IReadableBitmapWithAlphaImpl readable || readable.Format == null || readable.AlphaFormat == null)
if (PlatformImpl.Item is not IReadableBitmapImpl readable || readable.Format == null || readable.AlphaFormat == null)
{
throw new NotSupportedException("CopyPixels is not supported for this bitmap type");
}
if (buffer.Format != readable.Format || alphaFormat != readable.AlphaFormat)
if (buffer.Format != readable.Format || buffer.AlphaFormat != readable.AlphaFormat)
{
using (var fb = readable.Lock())
{
@ -255,11 +253,11 @@ namespace Avalonia.Media.Imaging
fb.Size,
fb.RowBytes,
fb.Format,
readable.AlphaFormat.Value,
fb.AlphaFormat,
buffer.Address,
buffer.RowBytes,
buffer.Format,
alphaFormat);
buffer.AlphaFormat);
}
}
else

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

@ -71,10 +71,10 @@ namespace Avalonia.Media.Imaging
return new LockedFramebuffer(_pixelFormatMemory.Address, _pixelFormatMemory.Size,
_pixelFormatMemory.RowBytes,
Dpi, _pixelFormatMemory.Format, () =>
Dpi, _pixelFormatMemory.Format, _pixelFormatMemory.AlphaFormat, () =>
{
using var inner = ((IWriteableBitmapImpl)PlatformImpl.Item).Lock();
_pixelFormatMemory.CopyToRgba(Platform.AlphaFormat.Unpremul, inner.Address, inner.RowBytes);
_pixelFormatMemory.CopyToRgba(inner.AlphaFormat, inner.Address, inner.RowBytes);
});
}

23
src/Avalonia.Base/Media/TextFormatting/GenericTextRunProperties.cs

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Globalization;
namespace Avalonia.Media.TextFormatting
{
@ -9,28 +7,17 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
public class GenericTextRunProperties : TextRunProperties
{
private const double DefaultFontRenderingEmSize = 12;
internal const double DefaultFontRenderingEmSize = 12;
// TODO12: Remove in 12.0.0 and make fontFeatures parameter in main ctor optional
public GenericTextRunProperties(Typeface typeface, double fontRenderingEmSize = DefaultFontRenderingEmSize,
TextDecorationCollection? textDecorations = null, IBrush? foregroundBrush = null,
IBrush? backgroundBrush = null, BaselineAlignment baselineAlignment = BaselineAlignment.Baseline,
CultureInfo? cultureInfo = null) :
this(typeface, null, fontRenderingEmSize, textDecorations, foregroundBrush,
backgroundBrush, baselineAlignment, cultureInfo)
{
}
// TODO12:Change signature in 12.0.0
public GenericTextRunProperties(
Typeface typeface,
FontFeatureCollection? fontFeatures,
Typeface typeface,
double fontRenderingEmSize = DefaultFontRenderingEmSize,
TextDecorationCollection? textDecorations = null,
IBrush? foregroundBrush = null,
IBrush? backgroundBrush = null,
BaselineAlignment baselineAlignment = BaselineAlignment.Baseline,
CultureInfo? cultureInfo = null)
CultureInfo? cultureInfo = null,
FontFeatureCollection? fontFeatures = null)
{
Typeface = typeface;
FontRenderingEmSize = fontRenderingEmSize;

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

@ -26,16 +26,14 @@
/// <param name="textLine">Text line to collapse.</param>
public abstract TextRun[]? Collapse(TextLine textLine);
// TODO12: Remove the flowDirection parameter
/// <summary>
/// Creates a list of runs for given collapsed length which includes specified symbol at the end.
/// </summary>
/// <param name="textLine">The text line.</param>
/// <param name="collapsedLength">The collapsed length.</param>
/// <param name="flowDirection">The flow direction.</param>
/// <param name="shapedSymbol">The symbol.</param>
/// <returns>List of remaining runs.</returns>
public static TextRun[] CreateCollapsedRuns(TextLine textLine, int collapsedLength, FlowDirection flowDirection, TextRun shapedSymbol)
public static TextRun[] CreateCollapsedRuns(TextLine textLine, int collapsedLength, TextRun shapedSymbol)
{
if (collapsedLength <= 0)
{

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

@ -71,7 +71,7 @@ namespace Avalonia.Media.TextFormatting
collapsedLength += measuredLength;
return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, properties.FlowDirection, shapedSymbol);
return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, shapedSymbol);
}
availableWidth -= textRunWidth;
@ -84,7 +84,7 @@ namespace Avalonia.Media.TextFormatting
//The whole run needs to fit into available space
if (drawableRun.Size.Width > availableWidth)
{
return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, properties.FlowDirection, shapedSymbol);
return TextCollapsingProperties.CreateCollapsedRuns(textLine, collapsedLength, shapedSymbol);
}
availableWidth -= drawableRun.Size.Width;

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

@ -59,8 +59,8 @@
var cultureInfo = textRun.Properties.CultureInfo;
var shaperOptions = new TextShaperOptions(glyphTypeface, textRun.Properties.FontFeatures,
fontRenderingEmSize, (sbyte)flowDirection, cultureInfo);
var shaperOptions = new TextShaperOptions(glyphTypeface, fontRenderingEmSize,
(sbyte)flowDirection, cultureInfo, 0, 0, textRun.Properties.FontFeatures);
var shapedBuffer = textShaper.ShapeText(textRun.Text, shaperOptions);

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

@ -290,9 +290,13 @@ namespace Avalonia.Media.TextFormatting
}
var shaperOptions = new TextShaperOptions(
properties.CachedGlyphTypeface, properties.FontFeatures,
properties.FontRenderingEmSize, shapeableRun.BidiLevel, properties.CultureInfo,
paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing);
properties.CachedGlyphTypeface,
properties.FontRenderingEmSize,
shapeableRun.BidiLevel,
properties.CultureInfo,
paragraphProperties.DefaultIncrementalTab,
paragraphProperties.LetterSpacing,
properties.FontFeatures);
ShapeTogether(groupedRuns, text, shaperOptions, textShaper, shapedRuns);

51
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@ -17,7 +17,6 @@ namespace Avalonia.Media.TextFormatting
private int _textSourceLength;
// TODO12: Remove in 12.0.0 and make fontFeatures parameter in main ctor optional
/// <summary>
/// Initializes a new instance of the <see cref="TextLayout" /> class.
/// </summary>
@ -35,54 +34,13 @@ namespace Avalonia.Media.TextFormatting
/// <param name="lineHeight">The height of each line of text.</param>
/// <param name="letterSpacing">The letter spacing that is applied to rendered glyphs.</param>
/// <param name="maxLines">The maximum number of text lines.</param>
/// <param name="textStyleOverrides">The text style overrides.</param>
public TextLayout(
string? text,
Typeface typeface,
double fontSize,
IBrush? foreground,
TextAlignment textAlignment = TextAlignment.Left,
TextWrapping textWrapping = TextWrapping.NoWrap,
TextTrimming? textTrimming = null,
TextDecorationCollection? textDecorations = null,
FlowDirection flowDirection = FlowDirection.LeftToRight,
double maxWidth = double.PositiveInfinity,
double maxHeight = double.PositiveInfinity,
double lineHeight = double.NaN,
double letterSpacing = 0,
int maxLines = 0,
IReadOnlyList<ValueSpan<TextRunProperties>>? textStyleOverrides = null)
: this(text, typeface, null, fontSize, foreground, textAlignment, textWrapping, textTrimming, textDecorations,
flowDirection, maxWidth, maxHeight, lineHeight, letterSpacing, maxLines, textStyleOverrides)
{
}
// TODO12:Change signature in 12.0.0
/// <summary>
/// Initializes a new instance of the <see cref="TextLayout" /> class.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="typeface">The typeface.</param>
/// <param name="fontSize">Size of the font.</param>
/// <param name="foreground">The foreground.</param>
/// <param name="textAlignment">The text alignment.</param>
/// <param name="textWrapping">The text wrapping.</param>
/// <param name="textTrimming">The text trimming.</param>
/// <param name="textDecorations">The text decorations.</param>
/// <param name="flowDirection">The text flow direction.</param>
/// <param name="maxWidth">The maximum width.</param>
/// <param name="maxHeight">The maximum height.</param>
/// <param name="lineHeight">The height of each line of text.</param>
/// <param name="letterSpacing">The letter spacing that is applied to rendered glyphs.</param>
/// <param name="maxLines">The maximum number of text lines.</param>
/// <param name="textStyleOverrides">The text style overrides.</param>
/// <param name="fontFeatures">Optional list of turned on/off features.</param>
/// <param name="textStyleOverrides">The text style overrides.</param>
public TextLayout(
string? text,
Typeface typeface,
FontFeatureCollection? fontFeatures,
double fontSize,
IBrush? foreground,
double fontSize = GenericTextRunProperties.DefaultFontRenderingEmSize,
IBrush? foreground = null,
TextAlignment textAlignment = TextAlignment.Left,
TextWrapping textWrapping = TextWrapping.NoWrap,
TextTrimming? textTrimming = null,
@ -93,6 +51,7 @@ namespace Avalonia.Media.TextFormatting
double lineHeight = double.NaN,
double letterSpacing = 0,
int maxLines = 0,
FontFeatureCollection? fontFeatures = null,
IReadOnlyList<ValueSpan<TextRunProperties>>? textStyleOverrides = null)
{
_paragraphProperties =
@ -534,7 +493,7 @@ namespace Avalonia.Media.TextFormatting
TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight,
double letterSpacing, FontFeatureCollection? features)
{
var textRunStyle = new GenericTextRunProperties(typeface, features, fontSize, textDecorations, foreground);
var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground, fontFeatures: features);
return new GenericTextParagraphProperties(flowDirection, textAlignment, true, false,
textRunStyle, textWrapping, lineHeight, 0, letterSpacing);

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

@ -107,8 +107,15 @@ namespace Avalonia.Media.TextFormatting
if (this is GenericTextRunProperties other && other.Typeface == typeface)
return this;
return new GenericTextRunProperties(typeface, FontFeatures, FontRenderingEmSize,
TextDecorations, ForegroundBrush, BackgroundBrush, BaselineAlignment);
return new GenericTextRunProperties(
typeface,
FontRenderingEmSize,
TextDecorations,
ForegroundBrush,
BackgroundBrush,
BaselineAlignment,
CultureInfo,
FontFeatures);
}
}
}

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

@ -8,27 +8,14 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
public readonly record struct TextShaperOptions
{
// TODO12: Remove in 12.0.0 and make fontFeatures parameter in main ctor optional
public TextShaperOptions(
GlyphTypeface typeface,
double fontRenderingEmSize = 12,
sbyte bidiLevel = 0,
CultureInfo? culture = null,
double incrementalTabWidth = 0,
double letterSpacing = 0)
: this(typeface, null, fontRenderingEmSize, bidiLevel, culture, incrementalTabWidth, letterSpacing)
{
}
// TODO12:Change signature in 12.0.0
public TextShaperOptions(
GlyphTypeface typeface,
IReadOnlyList<FontFeature>? fontFeatures,
double fontRenderingEmSize = 12,
double fontRenderingEmSize = GenericTextRunProperties.DefaultFontRenderingEmSize,
sbyte bidiLevel = 0,
CultureInfo? culture = null,
double incrementalTabWidth = 0,
double letterSpacing = 0)
double letterSpacing = 0,
IReadOnlyList<FontFeature>? fontFeatures = null)
{
GlyphTypeface = typeface;
FontRenderingEmSize = fontRenderingEmSize;

5
src/Avalonia.Base/Platform/ILockedFramebuffer.cs

@ -29,6 +29,9 @@ namespace Avalonia.Platform
/// </summary>
PixelFormat Format { get; }
//TODO12: Add AlphaFormat
/// <summary>
/// Gets the alpha format.
/// </summary>
AlphaFormat AlphaFormat { get; }
}
}

11
src/Avalonia.Base/Platform/IReadableBitmapImpl.cs

@ -2,15 +2,10 @@ using Avalonia.Metadata;
namespace Avalonia.Platform;
public interface IReadableBitmapImpl
[PrivateApi]
public interface IReadableBitmapImpl : IBitmapImpl
{
PixelFormat? Format { get; }
ILockedFramebuffer Lock();
}
//TODO12: Remove me once we can change IReadableBitmapImpl
[Unstable]
public interface IReadableBitmapWithAlphaImpl : IReadableBitmapImpl
{
AlphaFormat? AlphaFormat { get; }
ILockedFramebuffer Lock();
}

4
src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs

@ -5,8 +5,8 @@ namespace Avalonia.Platform
/// <summary>
/// Defines the platform-specific interface for a <see cref="Avalonia.Media.Imaging.WriteableBitmap"/>.
/// </summary>
[Unstable]
public interface IWriteableBitmapImpl : IBitmapImpl, IReadableBitmapWithAlphaImpl
[PrivateApi]
public interface IWriteableBitmapImpl : IBitmapImpl, IReadableBitmapImpl
{
}
}

4
src/Avalonia.Base/Platform/LockedFramebuffer.cs

@ -7,7 +7,7 @@ namespace Avalonia.Platform
private readonly Action? _onDispose;
public LockedFramebuffer(IntPtr address, PixelSize size, int rowBytes, Vector dpi, PixelFormat format,
Action? onDispose)
AlphaFormat alphaFormat, Action? onDispose)
{
_onDispose = onDispose;
Address = address;
@ -15,6 +15,7 @@ namespace Avalonia.Platform
RowBytes = rowBytes;
Dpi = dpi;
Format = format;
AlphaFormat = alphaFormat;
}
public IntPtr Address { get; }
@ -22,6 +23,7 @@ namespace Avalonia.Platform
public int RowBytes { get; }
public Vector Dpi { get; }
public PixelFormat Format { get; }
public AlphaFormat AlphaFormat { get; }
public void Dispose()
{

14
src/Avalonia.Base/Platform/RetainedFramebuffer.cs

@ -1,6 +1,4 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Metadata;
using Avalonia.Platform.Internal;
namespace Avalonia.Platform;
@ -10,6 +8,7 @@ internal class RetainedFramebuffer : IDisposable
public PixelSize Size { get; }
public int RowBytes { get; }
public PixelFormat Format { get; }
public AlphaFormat AlphaFormat { get; }
public IntPtr Address => _blob?.Address ?? throw new ObjectDisposedException(nameof(RetainedFramebuffer));
private UnmanagedBlob? _blob;
@ -17,13 +16,13 @@ internal class RetainedFramebuffer : IDisposable
? format
: throw new ArgumentOutOfRangeException(nameof(format));
public RetainedFramebuffer(PixelSize size, PixelFormat format) : this(size, ValidateKnownFormat(format),
format.BitsPerPixel / 8 * size.Width)
public RetainedFramebuffer(PixelSize size, PixelFormat format, AlphaFormat alphaFormat)
: this(size, ValidateKnownFormat(format), alphaFormat, format.BitsPerPixel / 8 * size.Width)
{
}
public RetainedFramebuffer(PixelSize size, PixelFormat format, int rowBytes)
public RetainedFramebuffer(PixelSize size, PixelFormat format, AlphaFormat alphaFormat, int rowBytes)
{
if (size.Width <= 0 || size.Height <= 0)
throw new ArgumentOutOfRangeException(nameof(size));
@ -32,6 +31,7 @@ internal class RetainedFramebuffer : IDisposable
Size = size;
RowBytes = rowBytes;
Format = format;
AlphaFormat = alphaFormat;
_blob = new UnmanagedBlob(RowBytes * size.Height);
}
@ -39,7 +39,7 @@ internal class RetainedFramebuffer : IDisposable
{
if (_blob == null)
throw new ObjectDisposedException(nameof(RetainedFramebuffer));
return new LockedFramebuffer(_blob.Address, Size, RowBytes, dpi, Format, () =>
return new LockedFramebuffer(_blob.Address, Size, RowBytes, dpi, Format, AlphaFormat, () =>
{
blit(this);
GC.KeepAlive(this);
@ -51,4 +51,4 @@ internal class RetainedFramebuffer : IDisposable
_blob?.Dispose();
_blob = null;
}
}
}

245
src/Avalonia.Base/Utilities/StringTokenizer.cs

@ -1,245 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using static System.Char;
namespace Avalonia.Utilities
{
// TODO12: Remove this struct in 12.0 (breaking change)
[Obsolete("This type has been superseded by SpanStringTokenizer.")]
#if !BUILDTASK
public
#endif
record struct StringTokenizer : IDisposable
{
private const char DefaultSeparatorChar = ',';
private readonly string _s;
private readonly int _length;
private readonly char _separator;
private readonly string? _exceptionMessage;
private readonly IFormatProvider _formatProvider;
private int _index;
private int _tokenIndex;
private int _tokenLength;
public StringTokenizer(string s, IFormatProvider formatProvider, string? exceptionMessage = null)
: this(s, GetSeparatorFromFormatProvider(formatProvider), exceptionMessage)
{
_formatProvider = formatProvider;
}
public StringTokenizer(string s, char separator = DefaultSeparatorChar, string? exceptionMessage = null)
{
_s = s ?? throw new ArgumentNullException(nameof(s));
_length = s?.Length ?? 0;
_separator = separator;
_exceptionMessage = exceptionMessage;
_formatProvider = CultureInfo.InvariantCulture;
_index = 0;
_tokenIndex = -1;
_tokenLength = 0;
while (_index < _length && IsWhiteSpace(_s, _index))
{
_index++;
}
}
public string? CurrentToken => _tokenIndex < 0 ? null : _s.Substring(_tokenIndex, _tokenLength);
public ReadOnlySpan<char> CurrentTokenSpan => _tokenIndex < 0 ? ReadOnlySpan<char>.Empty : _s.AsSpan().Slice(_tokenIndex, _tokenLength);
public void Dispose()
{
if (_index != _length)
{
throw GetFormatException();
}
}
public bool TryReadInt32(out Int32 result, char? separator = null)
{
if (TryReadSpan(out var stringResult, separator) &&
SpanHelpers.TryParseInt(stringResult, NumberStyles.Integer, _formatProvider, out result))
{
return true;
}
else
{
result = default;
return false;
}
}
public int ReadInt32(char? separator = null)
{
if (!TryReadInt32(out var result, separator))
{
throw GetFormatException();
}
return result;
}
public bool TryReadDouble(out double result, char? separator = null)
{
if (TryReadSpan(out var stringResult, separator) &&
SpanHelpers.TryParseDouble(stringResult, NumberStyles.Float, _formatProvider, out result))
{
return true;
}
else
{
result = default;
return false;
}
}
public double ReadDouble(char? separator = null)
{
if (!TryReadDouble(out var result, separator))
{
throw GetFormatException();
}
return result;
}
public bool TryReadString([NotNull] out string result, char? separator = null)
{
var success = TryReadToken(separator ?? _separator);
result = CurrentTokenSpan.ToString();
return success;
}
public string ReadString(char? separator = null)
{
if (!TryReadString(out var result, separator))
{
throw GetFormatException();
}
return result;
}
public bool TryReadSpan(out ReadOnlySpan<char> result, char? separator = null)
{
var success = TryReadToken(separator ?? _separator);
result = CurrentTokenSpan;
return success;
}
public ReadOnlySpan<char> ReadSpan(char? separator = null)
{
if (!TryReadSpan(out var result, separator))
{
throw GetFormatException();
}
return result;
}
private bool TryReadToken(char separator)
{
_tokenIndex = -1;
if (_index >= _length)
{
return false;
}
var c = _s[_index];
var index = _index;
var length = 0;
while (_index < _length)
{
c = _s[_index];
if (IsWhiteSpace(c) || c == separator)
{
break;
}
_index++;
length++;
}
SkipToNextToken(separator);
_tokenIndex = index;
_tokenLength = length;
if (_tokenLength < 1)
{
throw GetFormatException();
}
return true;
}
private void SkipToNextToken(char separator)
{
if (_index < _length)
{
var c = _s[_index];
if (c != separator && !IsWhiteSpace(c))
{
throw GetFormatException();
}
var length = 0;
while (_index < _length)
{
c = _s[_index];
if (c == separator)
{
length++;
_index++;
if (length > 1)
{
throw GetFormatException();
}
}
else
{
if (!IsWhiteSpace(c))
{
break;
}
_index++;
}
}
if (length > 0 && _index >= _length)
{
throw GetFormatException();
}
}
}
private FormatException GetFormatException() =>
_exceptionMessage != null ? new FormatException(_exceptionMessage) : new FormatException();
private static char GetSeparatorFromFormatProvider(IFormatProvider provider)
{
var c = DefaultSeparatorChar;
var formatInfo = NumberFormatInfo.GetInstance(provider);
if (formatInfo.NumberDecimalSeparator.Length > 0 && c == formatInfo.NumberDecimalSeparator[0])
{
c = ';';
}
return c;
}
}
}

3
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -62,9 +62,6 @@
<Compile Include="../Avalonia.Base/Utilities/IdentifierParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Base/Utilities/StringTokenizer.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Base/Utilities/MathUtilities.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>

4
src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

@ -32,7 +32,7 @@ namespace Avalonia.Build.Tasks
ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
new XamlCompilerDiagnosticsFilter(AnalyzerConfigFiles),
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
SkipXamlCompilation, DebuggerLaunch, VerboseExceptions);
SkipXamlCompilation, DebuggerLaunch, VerboseExceptions, CreateSourceInfo);
if (res.Success && !res.WrittenFile)
{
@ -99,6 +99,8 @@ namespace Avalonia.Build.Tasks
public bool DebuggerLaunch { get; set; }
public bool CreateSourceInfo { get; set; }
public bool VerboseExceptions { get; set; }
public ITaskItem[] AnalyzerConfigFiles { get; set; }

8
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -52,7 +52,7 @@ namespace Avalonia.Build.Tasks
string[] references, string projectDirectory,
bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance,
XamlCompilerDiagnosticsFilter diagnosticsFilter, string strongNameKey,
bool skipXamlCompilation, bool debuggerLaunch, bool verboseExceptions)
bool skipXamlCompilation, bool debuggerLaunch, bool verboseExceptions, bool createSourceInfo)
{
try
{
@ -67,7 +67,7 @@ namespace Avalonia.Build.Tasks
var compileRes = CompileCore(
engine, typeSystem, projectDirectory, verifyIl,
defaultCompileBindings, logImportance, diagnosticsFilter,
debuggerLaunch, verboseExceptions);
debuggerLaunch, verboseExceptions, createSourceInfo);
if (compileRes == null)
return new CompileResult(true);
if (compileRes == false)
@ -107,7 +107,8 @@ namespace Avalonia.Build.Tasks
MessageImportance logImportance,
XamlCompilerDiagnosticsFilter diagnosticsFilter,
bool debuggerLaunch,
bool verboseExceptions)
bool verboseExceptions,
bool createSourceInfo)
{
if (debuggerLaunch)
{
@ -210,6 +211,7 @@ namespace Avalonia.Build.Tasks
{
EnableIlVerification = verifyIl,
DefaultCompileBindings = defaultCompileBindings,
CreateSourceInfo = createSourceInfo,
DynamicSetterContainerProvider = new DefaultXamlDynamicSetterContainerProvider(dynamicSettersBuilder)
};

16
src/Avalonia.Controls/Design.cs

@ -127,22 +127,6 @@ namespace Avalonia.Controls
/// </summary>
public static readonly AttachedProperty<Control?> PreviewWithProperty = AvaloniaProperty
.RegisterAttached<AvaloniaObject, Control?>("PreviewWith", typeof (Design));
/// <summary>
/// Sets a preview template for the specified <see cref="AvaloniaObject"/> at design-time.
/// </summary>
/// <remarks>
/// This method allows you to specify a substitute control to be rendered in the previewer
/// for a given object.
/// </remarks>
/// <param name="target">The target object.</param>
/// <param name="control">The preview control.</param>
// TODO12: Remove this overload in Avalonia 12
[Obsolete("Use SetPreviewWith(AvaloniaObject, ITemplate<Control>) overload instead. Use <Template></Template> from XAML")]
public static void SetPreviewWith(AvaloniaObject target, Control? control)
{
s_previewWith[target] = control is not null ? new FuncTemplate<Control>(() => control) : null;
}
/// <summary>
/// Sets a preview template for the specified <see cref="AvaloniaObject"/> at design-time.

10
src/Avalonia.Controls/Documents/Inline.cs

@ -10,11 +10,10 @@ namespace Avalonia.Controls.Documents
/// </summary>
public abstract class Inline : TextElement
{
// TODO12: change the field type to an AttachedProperty for consistency (breaking change)
/// <summary>
/// AvaloniaProperty for <see cref="TextDecorations" /> property.
/// </summary>
public static readonly StyledProperty<TextDecorationCollection?> TextDecorationsProperty =
public static readonly AttachedProperty<TextDecorationCollection?> TextDecorationsProperty =
AvaloniaProperty.RegisterAttached<Inline, Inline, TextDecorationCollection?>(
nameof(TextDecorations),
inherits: true);
@ -82,12 +81,13 @@ namespace Avalonia.Controls.Documents
return new GenericTextRunProperties(
typeface,
FontFeatures,
FontSize,
TextDecorations,
TextDecorations,
Foreground,
parentOrSelfBackground,
BaselineAlignment);
BaselineAlignment,
null,
FontFeatures);
}
/// <summary>

37
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -346,13 +346,26 @@ namespace Avalonia.Controls.Presenters
private TextLayout CreateTextLayoutInternal(Size constraint, string? text, Typeface typeface,
IReadOnlyList<ValueSpan<TextRunProperties>>? textStyleOverrides)
{
var foreground = Foreground;
var maxWidth = MathUtilities.IsZero(constraint.Width) ? double.PositiveInfinity : constraint.Width;
var maxHeight = MathUtilities.IsZero(constraint.Height) ? double.PositiveInfinity : constraint.Height;
var textLayout = new TextLayout(text, typeface, FontFeatures, FontSize, foreground, TextAlignment,
TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides,
flowDirection: FlowDirection, lineHeight: LineHeight, letterSpacing: LetterSpacing);
var textLayout = new TextLayout(
text,
typeface,
FontSize,
Foreground,
TextAlignment,
TextWrapping,
null,
null,
FlowDirection,
maxWidth,
maxHeight,
LineHeight,
LetterSpacing,
0,
FontFeatures,
textStyleOverrides);
return textLayout;
}
@ -553,9 +566,12 @@ namespace Avalonia.Controls.Presenters
if (!string.IsNullOrEmpty(preeditText))
{
var preeditHighlight = new ValueSpan<TextRunProperties>(caretIndex, preeditText.Length,
new GenericTextRunProperties(typeface, FontFeatures, FontSize,
foregroundBrush: foreground,
textDecorations: TextDecorations.Underline));
new GenericTextRunProperties(
typeface,
FontSize,
TextDecorations.Underline,
foreground,
fontFeatures: FontFeatures));
textStyleOverrides = new[]
{
@ -569,8 +585,11 @@ namespace Avalonia.Controls.Presenters
textStyleOverrides = new[]
{
new ValueSpan<TextRunProperties>(start, length,
new GenericTextRunProperties(typeface, FontFeatures, FontSize,
foregroundBrush: SelectionForegroundBrush))
new GenericTextRunProperties(
typeface,
FontSize,
foregroundBrush: SelectionForegroundBrush,
fontFeatures: FontFeatures))
};
}
}

6
src/Avalonia.Controls/Primitives/TextSearch.cs

@ -24,22 +24,20 @@ namespace Avalonia.Controls.Primitives
public static readonly AttachedProperty<BindingBase?> TextBindingProperty
= AvaloniaProperty.RegisterAttached<Interactive, BindingBase?>("TextBinding", typeof(TextSearch));
// TODO12: Control should be Interactive to match the property definition.
/// <summary>
/// Sets the value of the <see cref="TextProperty"/> attached property to a given <see cref="Control"/>.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="text">The search text to set.</param>
public static void SetText(Control control, string? text)
public static void SetText(Interactive control, string? text)
=> control.SetValue(TextProperty, text);
// TODO12: Control should be Interactive to match the property definition.
/// <summary>
/// Gets the value of the <see cref="TextProperty"/> attached property from a given <see cref="Control"/>.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The search text.</returns>
public static string? GetText(Control control)
public static string? GetText(Interactive control)
=> control.GetValue(TextProperty);
/// <summary>

1
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs

@ -76,6 +76,7 @@ namespace Avalonia.Controls.Remote.Server
Stride,
new Vector(_dpi, _dpi),
new PlatformPixelFormat((PixelFormatEnum)Format),
Format == ProtocolPixelFormat.Rgb565 ? AlphaFormat.Opaque : AlphaFormat.Premul,
() =>
{
handle.Free();

15
src/Avalonia.Controls/SelectableTextBlock.cs

@ -186,10 +186,10 @@ namespace Avalonia.Controls
var defaultProperties = new GenericTextRunProperties(
typeface,
FontFeatures,
FontSize,
TextDecorations,
Foreground);
Foreground,
fontFeatures: FontFeatures);
var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false,
defaultProperties, TextWrapping, LineHeight, 0, LetterSpacing)
@ -235,9 +235,9 @@ namespace Avalonia.Controls
overlapLength,
new GenericTextRunProperties(
textRun.Properties?.Typeface ?? typeface,
textRun.Properties?.FontFeatures ?? FontFeatures,
FontSize,
foregroundBrush: SelectionForegroundBrush)));
foregroundBrush: SelectionForegroundBrush,
fontFeatures: textRun.Properties?.FontFeatures ?? FontFeatures)));
accumulatedLength += runLength;
}
@ -247,8 +247,11 @@ namespace Avalonia.Controls
textStyleOverrides =
[
new ValueSpan<TextRunProperties>(start, length,
new GenericTextRunProperties(typeface, FontFeatures, FontSize,
foregroundBrush: SelectionForegroundBrush))
new GenericTextRunProperties(
typeface,
FontSize,
foregroundBrush: SelectionForegroundBrush,
fontFeatures: FontFeatures))
];
}
}

4
src/Avalonia.Controls/TextBlock.cs

@ -657,10 +657,10 @@ namespace Avalonia.Controls
var defaultProperties = new GenericTextRunProperties(
typeface,
FontFeatures,
FontSize,
TextDecorations,
Foreground);
Foreground,
fontFeatures: FontFeatures);
var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, IsMeasureValid ? TextAlignment : TextAlignment.Left, true, false,
defaultProperties, TextWrapping, LineHeight, 0, LetterSpacing)

1
src/Avalonia.Native/DeferredFramebuffer.cs

@ -29,6 +29,7 @@ namespace Avalonia.Native
public int RowBytes { get; set; }
public Vector Dpi { get; set; }
public PixelFormat Format { get; set; }
public AlphaFormat AlphaFormat { get; set; }
public void Dispose()
{

2
src/Avalonia.X11/X11CursorFactory.cs

@ -133,7 +133,7 @@ namespace Avalonia.X11
return new LockedFramebuffer(
_blob.Address + Marshal.SizeOf<XcursorImage>(),
_pixelSize, _pixelSize.Width * 4,
new Vector(96, 96), PixelFormat.Bgra8888, null);
new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul, null);
}
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);

2
src/Avalonia.X11/X11FramebufferSurface.cs

@ -61,7 +61,7 @@ namespace Avalonia.X11
{
_fb?.Dispose();
_fb = null;
_fb = new RetainedFramebuffer(new PixelSize(width, height), PixelFormat.Bgra8888);
_fb = new RetainedFramebuffer(new PixelSize(width, height), PixelFormat.Bgra8888, AlphaFormat.Premul);
}
properties = new FramebufferLockProperties(framebufferValid);

2
src/Avalonia.X11/X11IconLoader.cs

@ -83,7 +83,7 @@ namespace Avalonia.X11
{
var h = GCHandle.Alloc(_bdata, GCHandleType.Pinned);
return new LockedFramebuffer(h.AddrOfPinnedObject(), new PixelSize(_width, _height), _width * 4,
new Vector(96, 96), PixelFormat.Bgra8888,
new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul,
() => h.Free());
}

4
src/Browser/Avalonia.Browser/Rendering/BrowserSoftwareRenderTarget.cs

@ -47,7 +47,7 @@ partial class BrowserSoftwareRenderTarget : BrowserRenderTarget, IFramebufferPla
{
_fb?.Dispose();
_fb = null;
_fb = new RetainedFramebuffer(size, PixelFormat.Rgba8888);
_fb = new RetainedFramebuffer(size, PixelFormat.Rgba8888, AlphaFormat.Premul);
}
return _fb.Lock(new Vector(scaling * 96, scaling * 96), _parent._blit);
@ -66,4 +66,4 @@ partial class BrowserSoftwareRenderTarget : BrowserRenderTarget, IFramebufferPla
{
PutPixelData(Js, fb.Address.ToInt32(), fb.Size.Width * fb.Size.Height * 4, fb.Size.Width, fb.Size.Height);
}
}
}

111
src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs

@ -1,10 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Browser.Interop;
@ -19,11 +16,14 @@ internal partial class RenderWorker
private static partial void InitializeRenderTargets();
internal static int WorkerThreadId;
// The worker task needs to be rooted otherwise the web worker will exit.
private static Task? s_workerTask;
public static Task InitializeAsync()
{
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var workerTask = JSWebWorkerClone.RunAsync(async () =>
s_workerTask = JSWebWorkerRunAsync(null, async () =>
{
try
{
@ -41,103 +41,18 @@ internal partial class RenderWorker
}
});
workerTask.ContinueWith(_ =>
s_workerTask.ContinueWith(_ =>
{
if (workerTask.IsFaulted)
tcs.TrySetException(workerTask.Exception);
if (s_workerTask.IsFaulted)
tcs.TrySetException(s_workerTask.Exception);
});
return tcs.Task;
}
public static class JSWebWorkerClone
{
private static readonly MethodInfo _setExtLoop;
private static readonly MethodInfo _intallInterop;
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "System.Runtime.InteropServices.JavaScript.JSSynchronizationContext",
"System.Runtime.InteropServices.JavaScript")]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "System.Runtime.InteropServices.JavaScript.JSHostImplementation",
"System.Runtime.InteropServices.JavaScript")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2036", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2111", Justification = "Private runtime API")]
static JSWebWorkerClone()
{
var syncContext = typeof(System.Runtime.InteropServices.JavaScript.JSHost)
.Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext")!;
var hostImpl = typeof(System.Runtime.InteropServices.JavaScript.JSHost)
.Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSHostImplementation")!;
_setExtLoop = hostImpl.GetMethod("SetHasExternalEventLoop")!;
_intallInterop = syncContext.GetMethod("InstallWebWorkerInterop")!;
}
public static Task RunAsync(Func<Task> run)
{
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var th = new Thread(_ =>
{
_intallInterop.Invoke(null, [false, CancellationToken.None]);
try
{
run().ContinueWith(t =>
{
if (t.IsFaulted)
tcs.TrySetException(t.Exception);
else if (t.IsCanceled)
tcs.TrySetCanceled();
else
tcs.TrySetResult();
});
}
catch(Exception e)
{
tcs.TrySetException(e);
}
})
{
Name = "Manual JS worker"
};
_setExtLoop.Invoke(null, [th]);
#pragma warning disable CA1416
th.Start();
#pragma warning restore CA1416
return tcs.Task;
}
}
// TODO: Use this class instead of JSWebWorkerClone once https://github.com/dotnet/runtime/issues/102010 is fixed
// TODO12: It was fixed in .NET 10
class JSWebWorkerWrapper
{
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker",
"System.Runtime.InteropServices.JavaScript")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2036", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2111", Justification = "Private runtime API")]
static JSWebWorkerWrapper()
{
var type = typeof(System.Runtime.InteropServices.JavaScript.JSHost)
.Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSWebWorker");
#pragma warning disable IL2075
var m = type!
.GetMethods(BindingFlags.Static | BindingFlags.Public
).First(m => m.Name == "RunAsync"
&& m.ReturnType == typeof(Task)
&& m.GetParameters() is { } parameters
&& parameters.Length == 1
&& parameters[0].ParameterType == typeof(Func<Task>));
#pragma warning restore IL2075
RunAsync = (Func<Func<Task>, Task>) Delegate.CreateDelegate(typeof(Func<Func<Task>, Task>), m);
}
public static Func<Func<Task>, Task> RunAsync { get; set; }
}
// Even though this API is public in the .NET code, it's not part of ref assemblies and is not a stable API.
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "RunAsync")]
private static extern Task JSWebWorkerRunAsync(
[UnsafeAccessorType("System.Runtime.InteropServices.JavaScript.JSWebWorker, System.Runtime.InteropServices.JavaScript")] object? instance,
Func<Task> body);
}

1
src/Headless/Avalonia.Headless/Avalonia.Headless.csproj

@ -6,6 +6,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\..\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\HarfBuzz\Avalonia.HarfBuzz\Avalonia.HarfBuzz.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\DevAnalyzers.props" />

6
src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -414,8 +414,8 @@ namespace Avalonia.Headless
public Vector Dpi { get; }
public PixelSize PixelSize { get; }
public PixelFormat? Format { get; }
public AlphaFormat? AlphaFormat { get; }
public PixelFormat? Format => PixelFormat.Rgba8888;
public AlphaFormat? AlphaFormat => Platform.AlphaFormat.Premul;
public int Version { get; set; }
public void Save(string fileName, int? quality = null)
@ -434,7 +434,7 @@ namespace Avalonia.Headless
Version++;
var mem = Marshal.AllocHGlobal(PixelSize.Width * PixelSize.Height * 4);
return new LockedFramebuffer(mem, PixelSize, PixelSize.Width * 4, Dpi, PixelFormat.Rgba8888,
() => Marshal.FreeHGlobal(mem));
Platform.AlphaFormat.Premul, () => Marshal.FreeHGlobal(mem));
}
}

4
src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs

@ -236,7 +236,9 @@ public sealed class HeadlessUnitTestSession : IDisposable, IAsyncDisposable
// If windowing subsystem wasn't initialized by user, force headless with default parameters.
if (appBuilder.WindowingSubsystemName != "Headless")
{
appBuilder = appBuilder.UseHeadless(new AvaloniaHeadlessPlatformOptions());
appBuilder = appBuilder
.UseHeadless(new AvaloniaHeadlessPlatformOptions())
.UseHarfBuzz();
}
// ReSharper disable once AccessToModifiedClosure

1
src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs

@ -200,6 +200,7 @@ namespace Avalonia.Headless
public int RowBytes => _fb.RowBytes;
public Vector Dpi => _fb.Dpi;
public PixelFormat Format => _fb.Format;
public AlphaFormat AlphaFormat => _fb.AlphaFormat;
}
public ILockedFramebuffer Lock()

11
src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs

@ -101,12 +101,17 @@ namespace Avalonia.LinuxFramebuffer.Output
public static LockedFramebuffer LockFb(IntPtr address, fb_var_screeninfo varInfo,
fb_fix_screeninfo fixedInfo, Vector dpi, Action? dispose)
{
var (format, alphaFormat) = varInfo switch
{
{ bits_per_pixel: 16 } => (PixelFormat.Rgb565, AlphaFormat.Opaque),
{ bits_per_pixel: 32, blue.offset: 16 } => (PixelFormat.Rgba8888, AlphaFormat.Premul),
_ => (PixelFormat.Bgra8888, AlphaFormat.Premul)
};
return new LockedFramebuffer(address,
new PixelSize((int)varInfo.xres, (int)varInfo.yres),
(int)fixedInfo.line_length, dpi,
varInfo.bits_per_pixel == 16 ? PixelFormat.Rgb565
: varInfo.blue.offset == 16 ? PixelFormat.Rgba8888
: PixelFormat.Bgra8888, dispose);
format, alphaFormat, dispose);
}
private void BlitToDevice()

7
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
@ -344,7 +344,8 @@ namespace Avalonia.Markup.Xaml.XamlIl
{
EnableIlVerification = true,
DefaultCompileBindings = configuration.UseCompiledBindingsByDefault,
IsDesignMode = configuration.DesignMode
IsDesignMode = configuration.DesignMode,
CreateSourceInfo = configuration.CreateSourceInfo,
};
var parsedDocuments = new List<XamlDocumentResource>();
@ -363,7 +364,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
}
var parsed = compiler.Parse(xaml, overrideType);
parsed.Document = "runtimexaml:" + parsedDocuments.Count;
parsed.Document = document.Document ?? ("runtimexaml" + parsedDocuments.Count);
compiler.Transform(parsed);
var xamlName = GetSafeUriIdentifier(document.BaseUri)

15
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Markup.Xaml.Loader.CompilerExtensions.Transformers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX;
@ -20,6 +21,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
private readonly IXamlType _contextType = null!;
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
private readonly AvaloniaXamlIlAddSourceInfoTransformer _addSourceInfoTransformer;
private readonly AvaloniaXamlResourceTransformer _resourceTransformer;
private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings)
: base(configuration, emitMappings, true)
@ -47,6 +50,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
new AvaloniaXamlIlTransformInstanceAttachedProperties(),
new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers());
InsertAfter<PropertyReferenceResolver>(
new AvaloniaXamlIlAvaloniaPropertyResolver(),
new AvaloniaXamlIlReorderClassesPropertiesTransformer(),
@ -85,7 +90,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
);
InsertBeforeMany(new [] { typeof(DeferredContentTransformer), typeof(AvaloniaXamlIlCompiledBindingsMetadataRemover) },
new AvaloniaXamlIlDeferredResourceTransformer());
_resourceTransformer = new AvaloniaXamlResourceTransformer());
InsertBefore<AvaloniaXamlIlTransformInstanceAttachedProperties>(new AvaloniaXamlIlTransformRoutedEvent());
@ -94,6 +99,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer());
Transformers.Add(new AvaloniaXamlIlRootObjectScope());
Transformers.Add(_addSourceInfoTransformer = new AvaloniaXamlIlAddSourceInfoTransformer());
Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter());
Emitters.Add(new AvaloniaXamlIlRootObjectScope.Emitter());
@ -122,6 +129,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
public const string PopulateName = "__AvaloniaXamlIlPopulate";
public const string BuildName = "__AvaloniaXamlIlBuild";
public bool CreateSourceInfo
{
get => _addSourceInfoTransformer.CreateSourceInfo || _resourceTransformer.CreateSourceInfo;
set => _addSourceInfoTransformer.CreateSourceInfo = _resourceTransformer.CreateSourceInfo = value;
}
public bool IsDesignMode
{
get => _designTransformer.IsDesignMode;

33
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs

@ -20,8 +20,12 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
{
public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node)
{
if (node is not XamlValueWithManipulationNode valueNode
|| valueNode.Value is not XamlAstNewClrObjectNode objectNode
// Filter object initialization nodes like:
// > XamlValueWithManipulationNode
// > > XamlAstNewClrObjectNode // StyleInclude or ResourceInclude, can be nested in another XamlValueWithManipulationNode
// > > XamlObjectInitializationNode
if (node is not XamlValueWithManipulationNode { Manipulation: XamlObjectInitializationNode initializationNode } valueNode
|| valueNode.UnwrapValue<XamlAstNewClrObjectNode>() is not { } objectNode
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
{
@ -36,11 +40,6 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
throw new InvalidOperationException($"\"{nodeTypeName}\".Loaded property is expected to be defined");
}
if (valueNode.Manipulation is not XamlObjectInitializationNode initializationNode)
{
throw new InvalidOperationException($"Invalid \"{nodeTypeName}\" node initialization.");
}
var additionalProperties = new List<IXamlAstManipulationNode>();
if (initializationNode.Manipulation is not XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty)
{
@ -176,9 +175,25 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
strictSourceValueType ? XamlDiagnosticSeverity.Error : XamlDiagnosticSeverity.Warning,
$"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri. This {nodeTypeName} will be resolved in runtime instead.",
node);
// We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`.
if (sourceProperty.Values.OfType<XamlAstNewClrObjectNode>().FirstOrDefault() is not { } sourceUriNode
if (sourceProperty.Values.Count != 1)
{
OnInvalidSource(sourceProperty);
return (null, null);
}
// `new Uri` can be wrapped in manipulation node if source info or another manipulation was applied.
var sourceUriNodeWrapped = sourceProperty.Values.Single();
var sourceUriNode = sourceUriNodeWrapped switch
{
XamlAstNewClrObjectNode newObj => newObj,
XamlValueWithManipulationNode manipulation => manipulation.UnwrapValue<XamlAstNewClrObjectNode>(),
_ => null
};
// Validate Uri type and constant arguments.
if (sourceUriNode is null
|| sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
|| sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath }
|| sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind })

69
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAddSourceInfoTransformer.cs

@ -0,0 +1,69 @@
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.Loader.CompilerExtensions.Transformers
{
/// <summary>
/// An XAMLIL AST transformer that injects <see cref="Avalonia.Markup.Xaml.Diagnostics.XamlSourceInfo"/> metadata into the generated XAML code.
/// </summary>
/// <remarks>
/// This transformer wraps object creation nodes with a manipulation node that adds source information.
/// This source information includes line number, position, and document name, which can be useful for debugging and diagnostics.
/// Note: ResourceDictionary source info is handled separately in <see cref="AvaloniaXamlResourceTransformer"/>.
/// </remarks>
internal class AvaloniaXamlIlAddSourceInfoTransformer : IXamlAstTransformer
{
/// <summary>
/// Gets or sets a value indicating whether source information should be generated
/// and injected into the compiled XAML output.
/// </summary>
public bool CreateSourceInfo { get; set; }
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (CreateSourceInfo
&& node is XamlAstNewClrObjectNode objNode
&& context.ParentNodes().FirstOrDefault() is not XamlValueWithManipulationNode { Manipulation: XamlSourceInfoValueManipulation }
&& !objNode.Type.GetClrType().IsValueType)
{
var avaloniaTypes = context.GetAvaloniaTypes();
return new XamlValueWithManipulationNode(
objNode, objNode,
new XamlSourceInfoValueManipulation(avaloniaTypes, objNode, context.Document));
}
return node;
}
private class XamlSourceInfoValueManipulation(
AvaloniaXamlIlWellKnownTypes avaloniaTypes,
XamlAstNewClrObjectNode objNode, string? document)
: XamlAstNode(objNode), IXamlAstManipulationNode, IXamlAstILEmitableNode
{
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
// Target object is already on stack.
// var info = new XamlSourceInfo(Line, Position, Document);
codeGen.Ldc_I4(Line);
codeGen.Ldc_I4(Position);
if (document is not null)
codeGen.Ldstr(document);
else
codeGen.Ldnull();
codeGen.Newobj(avaloniaTypes.XamlSourceInfoConstructor);
// Set the XamlSourceInfo property on the current object
// XamlSourceInfo.SetValue(@this, info);
codeGen.EmitCall(avaloniaTypes.XamlSourceInfoSetter);
return XamlILNodeEmitResult.Void(1);
}
}
}
}

191
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs

@ -1,191 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
internal class AvaloniaXamlIlDeferredResourceTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2)
return node;
var types = context.GetAvaloniaTypes();
if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content"
&& ShouldBeDeferred(pa.Values[1]))
{
IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared
? types.ResourceDictionaryNotSharedDeferredAdd
: types.ResourceDictionaryDeferredAdd;
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
pa.PossibleSetters = new List<IXamlPropertySetter>
{
new XamlDirectCallPropertySetter(addMethod),
};
}
else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true
&& ShouldBeDeferred(pa.Values[1]))
{
IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared
? types.ResourceDictionaryNotSharedDeferredAdd
: types.ResourceDictionaryDeferredAdd;
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
pa.PossibleSetters = new List<IXamlPropertySetter>
{
new AdderSetter(pa.Property.Getter, addMethod),
};
}
return node;
bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value)
{
value = default;
if (valueNode is XamlAstConstructableObjectNode co)
{
// Try find x:Share directive
if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective)
{
if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text)
{
if (bool.TryParse(text.Text, out var parseValue))
{
// If the parser succeeds, remove the x:Share directive
co.Children.Remove(sharedDirective);
return true;
}
else
{
context.ReportTransformError("Invalid argument type for x:Shared directive.", node);
}
}
else
{
context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node);
}
}
}
return false;
}
}
private static bool ShouldBeDeferred(IXamlAstValueNode node)
{
var clrType = node.Type.GetClrType();
// XAML compiler is currently strict about value types, allowing them to be created only through converters.
// At the moment it should be safe to not defer structs.
if (clrType.IsValueType)
{
return false;
}
// Never defer strings.
if (clrType.FullName == "System.String")
{
return false;
}
// Do not defer resources, if it has any x:Name registration, as it cannot be delayed.
// This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes.
// We set target scope level to 0, assuming that this resource node is a scope of itself.
var nameRegistrationsVisitor = new NameScopeRegistrationVisitor(
targetMetadataScopeLevel: 0);
node.Visit(nameRegistrationsVisitor);
if (nameRegistrationsVisitor.Count > 0)
{
return false;
}
return true;
}
class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable<AdderSetter>
{
private readonly IXamlMethod _getter;
private readonly IXamlMethod _adder;
public AdderSetter(IXamlMethod getter, IXamlMethod adder)
{
_getter = getter;
_adder = adder;
TargetType = getter.DeclaringType;
Parameters = adder.ParametersWithThis().Skip(1).ToList();
bool allowNull = Parameters.Last().AcceptsNull();
BinderParameters = new PropertySetterBinderParameters
{
AllowMultiple = true,
AllowXNull = allowNull,
AllowRuntimeNull = allowNull,
AllowAttributeSyntax = false,
};
}
public IXamlType TargetType { get; }
public PropertySetterBinderParameters BinderParameters { get; }
public IReadOnlyList<IXamlType> Parameters { get; }
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes => _adder.CustomAttributes;
public void Emit(IXamlILEmitter emitter)
{
var locals = new Stack<XamlLocalsPool.PooledLocal>();
// Save all "setter" parameters
for (var c = Parameters.Count - 1; c >= 0; c--)
{
var loc = emitter.LocalsPool.GetLocal(Parameters[c]);
locals.Push(loc);
emitter.Stloc(loc.Local);
}
emitter.EmitCall(_getter);
while (locals.Count>0)
using (var loc = locals.Pop())
emitter.Ldloc(loc.Local);
emitter.EmitCall(_adder, true);
}
public void EmitWithArguments(
XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter emitter,
IReadOnlyList<IXamlAstValueNode> arguments)
{
emitter.EmitCall(_getter);
for (var i = 0; i < arguments.Count; ++i)
context.Emit(arguments[i], emitter, Parameters[i]);
emitter.EmitCall(_adder, true);
}
public bool Equals(AdderSetter? other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return _getter.Equals(other._getter) && _adder.Equals(other._adder);
}
public override bool Equals(object? obj)
=> Equals(obj as AdderSetter);
public override int GetHashCode()
=> (_getter.GetHashCode() * 397) ^ _adder.GetHashCode();
}
}
}

343
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResourceTransformer.cs

@ -0,0 +1,343 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
/// <summary>
/// Transforms ResourceDictionary and IResourceDictionary property assignments
/// to use Add method calls with deferred content where applicable.
/// Additionally, handles x:Shared on assignments and injects XamlSourceInfo.
/// </summary>
internal class AvaloniaXamlResourceTransformer : IXamlAstTransformer
{
/// <summary>
/// Gets or sets a value indicating whether source information should be generated
/// and injected into the compiled XAML output.
/// </summary>
public bool CreateSourceInfo { get; set; } = true;
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2)
return node;
var types = context.GetAvaloniaTypes();
var document = context.Document;
if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content")
{
var value = pa.Values[1];
(var adder, value) = ResolveAdderAndValue(value);
pa.Values[1] = value;
pa.PossibleSetters = new List<IXamlPropertySetter>
{
new AdderSetter(adder, CreateSourceInfo, types, value.Line, value.Position, document),
};
}
else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true)
{
var value = pa.Values[1];
(var adder, value) = ResolveAdderAndValue(value);
pa.Values[1] = value;
pa.PossibleSetters = new List<IXamlPropertySetter>
{
new AdderSetter(pa.Property.Getter, adder, CreateSourceInfo, types, value.Line, value.Position, document),
};
}
return node;
(IXamlMethod adder, IXamlAstValueNode newValue) ResolveAdderAndValue(IXamlAstValueNode valueNode)
{
if (ShouldBeDeferred(valueNode))
{
var adder = TryGetSharedValue(valueNode, out var isShared) && !isShared
? types.ResourceDictionaryNotSharedDeferredAdd
: types.ResourceDictionaryDeferredAdd;
var deferredNode = new XamlDeferredContentNode(valueNode, types.XamlIlTypes.Object, context.Configuration);
return (adder, deferredNode);
}
else
{
var adder = XamlTransformHelpers.FindPossibleAdders(context, types.IResourceDictionary)
.FirstOrDefault() ?? throw new XamlTransformException("No suitable Add method found for IResourceDictionary.", node);
return (adder, valueNode);
}
}
bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value)
{
value = default;
if (valueNode is XamlAstConstructableObjectNode co)
{
// Try find x:Share directive
if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective)
{
if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text)
{
if (bool.TryParse(text.Text, out var parseValue))
{
// If the parser succeeds, remove the x:Share directive
co.Children.Remove(sharedDirective);
return true;
}
else
{
context.ReportTransformError("Invalid argument type for x:Shared directive.", node);
}
}
else
{
context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node);
}
}
}
return false;
}
}
private static bool ShouldBeDeferred(IXamlAstValueNode node)
{
var clrType = node.Type.GetClrType();
// XAML compiler is currently strict about value types, allowing them to be created only through converters.
// At the moment it should be safe to not defer structs.
if (clrType.IsValueType)
{
return false;
}
// Never defer strings.
if (clrType.FullName == "System.String")
{
return false;
}
// Do not defer resources, if it has any x:Name registration, as it cannot be delayed.
// This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes.
// We set target scope level to 0, assuming that this resource node is a scope of itself.
var nameRegistrationsVisitor = new NameScopeRegistrationVisitor(
targetMetadataScopeLevel: 0);
node.Visit(nameRegistrationsVisitor);
if (nameRegistrationsVisitor.Count > 0)
{
return false;
}
return true;
}
class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable<AdderSetter>
{
private readonly IXamlMethod? _getter;
private readonly IXamlMethod _adder;
private readonly bool _emitSourceInfo;
private readonly AvaloniaXamlIlWellKnownTypes _avaloniaTypes;
private readonly string? _document;
private readonly int _line, _position;
/// <summary>
/// Creates an adder-only setter. Target is assumed to be already on the stack before emit.
/// For example:
/// var resourceDictionary = ...
/// resourceDictionary.Add(key, value);
/// resourceDictionary.Add(key2, value2);
/// </summary>
public AdderSetter(
IXamlMethod adder,
bool emitSourceInfo,
AvaloniaXamlIlWellKnownTypes avaloniaTypes,
int line, int position, string? document)
{
_adder = adder;
_emitSourceInfo = emitSourceInfo;
_avaloniaTypes = avaloniaTypes;
_line = line;
_position = position;
_document = document;
TargetType = adder.ThisOrFirstParameter();
Parameters = adder.ParametersWithThis().Skip(1).ToList();
bool allowNull = Parameters.Last().AcceptsNull();
BinderParameters = new PropertySetterBinderParameters
{
AllowMultiple = true,
AllowXNull = allowNull,
AllowRuntimeNull = allowNull
};
}
/// <summary>
/// Explicit target getter - target will be obtained by calling the getter first.
///
/// </summary>
public AdderSetter(
IXamlMethod getter, IXamlMethod adder,
bool emitSourceInfo,
AvaloniaXamlIlWellKnownTypes avaloniaTypes,
int line, int position, string? document)
: this(adder, emitSourceInfo, avaloniaTypes, line, position, document)
{
_getter = getter;
TargetType = getter.DeclaringType;
BinderParameters.AllowMultiple = false;
BinderParameters.AllowAttributeSyntax = false;
}
public IXamlType TargetType { get; }
public PropertySetterBinderParameters BinderParameters { get; }
public IReadOnlyList<IXamlType> Parameters { get; }
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes => _adder.CustomAttributes;
/// <summary>
/// Emits the setter with arguments already on the stack.
/// </summary>
/// <remarks>
/// If _getter is null - assume target is already on the stack.
/// In this case, we can just call Emit. Unless _emitSourceInfo is true.
///
/// If _emitSourceInfo is true - we need to make sure that target and key are on the stack for XamlSourceInfo setting,
/// so we need to store parameters to locals first regardless.
/// </remarks>
public void Emit(IXamlILEmitter emitter)
{
using var keyLocal = emitter.LocalsPool.GetLocal(Parameters[0]);
if (_getter is not null || _emitSourceInfo)
{
var locals = new Stack<XamlLocalsPool.PooledLocal>();
// Save all "setter" parameters
for (var c = Parameters.Count - 1; c >= 0; c--)
{
var loc = emitter.LocalsPool.GetLocal(Parameters[c]);
locals.Push(loc);
emitter.Stloc(loc.Local);
if (c == 0 && _emitSourceInfo)
{
// Store the key argument for XamlSourceInfo later
emitter.Ldloc(loc.Local);
emitter.Stloc(keyLocal.Local);
}
}
if (_getter is not null)
{
emitter.EmitCall(_getter);
}
// Duplicate the target object on stack for setting XamlSourceInfo later
emitter.Dup();
while (locals.Count > 0)
using (var loc = locals.Pop())
emitter.Ldloc(loc.Local);
}
emitter.EmitCall(_adder, true);
if (_emitSourceInfo)
{
// Target is already on stack (dup)
// Load the key argument from local
emitter.Ldloc(keyLocal.Local);
EmitSetSourceInfo(emitter);
}
}
/// <summary>
/// Emits the setter with provided arguments that are not yet on the stack.
/// </summary>
/// <remarks>
/// If _getter is null - assume target is already on the stack.
/// If _emitSourceInfo is true - we need to make sure that target and key are on the stack for XamlSourceInfo setting.
/// </remarks>
public void EmitWithArguments(
XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter emitter,
IReadOnlyList<IXamlAstValueNode> arguments)
{
using var keyLocal = _emitSourceInfo ? emitter.LocalsPool.GetLocal(Parameters[0]) : null;
if (_getter is not null)
{
emitter.EmitCall(_getter);
}
if (_emitSourceInfo)
{
// Duplicate the target object on stack for setting XamlSourceInfo later
emitter.Dup();
}
for (var i = 0; i < arguments.Count; ++i)
{
context.Emit(arguments[i], emitter, Parameters[i]);
// Store the key argument for XamlSourceInfo later
if (i == 0 && _emitSourceInfo)
{
emitter.Stloc(keyLocal!.Local);
emitter.Ldloc(keyLocal.Local);
}
}
emitter.EmitCall(_adder, true);
if (_emitSourceInfo)
{
// Target is already on stack (dub)
// Load the key argument from local
emitter.Ldloc(keyLocal!.Local);
EmitSetSourceInfo(emitter);
}
}
private void EmitSetSourceInfo(IXamlILEmitter emitter)
{
// Assumes the target object and key are already on the stack
emitter.Ldc_I4(_line);
emitter.Ldc_I4(_position);
if (_document is not null)
emitter.Ldstr(_document);
else
emitter.Ldnull();
emitter.Newobj(_avaloniaTypes.XamlSourceInfoConstructor);
// Set the XamlSourceInfo property on the current object
// XamlSourceInfo.SetXamlSourceInfo(@this, key, info);
emitter.EmitCall(_avaloniaTypes.XamlSourceInfoDictionarySetter);
}
public bool Equals(AdderSetter? other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return _getter?.Equals(other._getter) == true && _adder.Equals(other._adder);
}
public override bool Equals(object? obj)
=> Equals(obj as AdderSetter);
public override int GetHashCode()
=> (_getter, _adder).GetHashCode();
}
}
}

12
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -134,6 +134,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ControlTemplate { get; }
public IXamlType EventHandlerT { get; }
public IXamlMethod GetClassProperty { get; }
public IXamlConstructor XamlSourceInfoConstructor { get; }
public IXamlMethod XamlSourceInfoSetter { get; }
public IXamlMethod XamlSourceInfoDictionarySetter { get; }
sealed internal class InteractivityWellKnownTypes
{
@ -343,6 +346,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
allowDowncast:false,
cfg.WellKnownTypes.String
);
var xamlSourceInfo = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Diagnostics.XamlSourceInfo");
XamlSourceInfoConstructor = xamlSourceInfo.GetConstructor([
XamlIlTypes.Int32, XamlIlTypes.Int32, XamlIlTypes.String
]);
XamlSourceInfoSetter =
xamlSourceInfo.GetMethod("SetXamlSourceInfo", XamlIlTypes.Void, false, XamlIlTypes.Object, xamlSourceInfo);
XamlSourceInfoDictionarySetter =
xamlSourceInfo.GetMethod("SetXamlSourceInfo", XamlIlTypes.Void, false, IResourceDictionary, XamlIlTypes.Object, xamlSourceInfo);
}
}

26
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlAstNewClrObjectHelper.cs

@ -0,0 +1,26 @@
using XamlX.Ast;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
internal static class XamlAstNewClrObjectHelper
{
/// <summary>
/// Tries to resolve the underlying value of a <see cref="XamlValueWithManipulationNode"/>,
/// unwrapping any nested <see cref="XamlValueWithManipulationNode"/> instances.
/// </summary>
public static TXamlAstValueNode? UnwrapValue<TXamlAstValueNode>(this XamlValueWithManipulationNode node)
where TXamlAstValueNode : class, IXamlAstValueNode
{
var current = node.Value;
while (current is XamlValueWithManipulationNode valueWithManipulation)
{
current = valueWithManipulation.Value;
if (current is TXamlAstValueNode typedValue)
{
return typedValue;
}
}
return current as TXamlAstValueNode;
}
}

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -38,6 +38,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RuntimeXamlLoaderConfiguration.cs" />
<Compile Include="RuntimeXamlLoaderDocument.cs" />
<Compile Include="Diagnostics\XamlSourceInfo.cs" />
<Compile Include="Styling\MergeResourceInclude.cs" />
<Compile Include="Styling\ResourceInclude.cs" />
<Compile Include="Styling\StyleInclude.cs" />

151
src/Markup/Avalonia.Markup.Xaml/Diagnostics/XamlSourceInfo.cs

@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
namespace Avalonia.Markup.Xaml.Diagnostics
{
/// <summary>
/// Represents source location information for an element within a XAML or code file.
/// </summary>
// ReSharper disable once ClassNeverInstantiated.Global //This class is instantiated through the XAML compiler.
public record XamlSourceInfo
{
private static readonly AttachedProperty<XamlSourceInfo?> s_xamlSourceInfo =
AvaloniaProperty.RegisterAttached<AvaloniaObject, XamlSourceInfo?>(
"XamlSourceInfo", typeof(XamlSourceInfo));
private static readonly ConditionalWeakTable<object, XamlSourceInfo?> s_sourceInfo = [];
private static readonly ConditionalWeakTable<IResourceDictionary, Dictionary<object, XamlSourceInfo?>> s_keyedSourceInfo = [];
/// <summary>
/// Gets the full path of the source file containing the element, or <c>null</c> if unavailable.
/// </summary>
public Uri? SourceUri { get; }
/// <summary>
/// Gets the 1-based line number in the source file where the element is defined.
/// </summary>
public int LineNumber { get; }
/// <summary>
/// Gets the 1-based column number in the source file where the element is defined.
/// </summary>
public int LinePosition { get; }
/// <summary>
/// Initializes a new instance of the <see cref="XamlSourceInfo"/> class
/// with a specified line, column, and file path.
/// </summary>
/// <param name="line">The line number of the source element.</param>
/// <param name="column">The column number of the source element.</param>
/// <param name="filePath">The full path of the source file.</param>
public XamlSourceInfo(int line, int column, string? filePath)
{
LineNumber = line;
LinePosition = column;
SourceUri = filePath is not null ? new UriBuilder("file", "") { Path = filePath }.Uri : null;
}
/// <summary>
/// Associates XAML source information with the specified object for debugging or diagnostic purposes.
/// </summary>
/// <remarks>This method is typically used to enable enhanced debugging or diagnostics by tracking
/// the origin of XAML elements at runtime. If the same object is passed multiple times, the most recent source
/// information will overwrite any previous value.</remarks>
/// <param name="obj">The object to associate with the XAML source information. Cannot be null.</param>
/// <param name="info">The XAML source information to associate with the object, or null to remove any existing association.</param>
public static void SetXamlSourceInfo(object obj, XamlSourceInfo? info)
{
if (obj is null)
throw new ArgumentNullException(nameof(obj));
if (obj is AvaloniaObject avaloniaObject)
{
avaloniaObject.SetValue(s_xamlSourceInfo, info);
}
else
{
s_sourceInfo.AddOrUpdate(obj, info);
}
}
/// <summary>
/// Associates XAML source information with the specified key in the given resource dictionary.
/// </summary>
/// <param name="dictionary"> The resource dictionary to associate with the XAML source information.</param>
/// <param name="key">The key associated with the source info.</param>
/// <param name="info">The XAML source information to associate with the object, or null to remove any existing association.</param>
public static void SetXamlSourceInfo(IResourceDictionary dictionary, object key, XamlSourceInfo? info)
{
if (dictionary is null)
throw new ArgumentNullException(nameof(dictionary));
var dict = s_keyedSourceInfo.GetOrCreateValue(dictionary);
if (info == null)
{
_ = dict.Remove(key);
}
else
{
dict[key] = info;
}
}
/// <summary>
/// Retrieves the XAML source information associated with the specified object, if available.
/// </summary>
/// <param name="obj">The object for which to obtain XAML source information. Cannot be null.</param>
/// <returns>A <see cref="XamlSourceInfo"/> instance containing the XAML source information for the specified object, or
/// <see langword="null"/> if no source information is available.</returns>
public static XamlSourceInfo? GetXamlSourceInfo(object obj)
{
if (obj is null)
throw new ArgumentNullException(nameof(obj));
if (obj is AvaloniaObject avaloniaObject)
{
return avaloniaObject.GetValue(s_xamlSourceInfo);
}
else
{
s_sourceInfo.TryGetValue(obj, out var info);
return info;
}
}
/// <summary>
/// Retrieves the XAML source information associated with the specified key in the given resource dictionary, if available.
/// </summary>
/// <param name="dictionary"> The resource dictionary associated with the XAML source information.</param>
/// <param name="key">The key associated with the source info.</param>
/// <returns>A <see cref="XamlSourceInfo"/> instance containing the XAML source information for the specified key, or
/// <see langword="null"/> if no source information is available.</returns>
public static XamlSourceInfo? GetXamlSourceInfo(IResourceDictionary dictionary, object key)
{
if (dictionary is null)
throw new ArgumentNullException(nameof(dictionary));
if (s_keyedSourceInfo.TryGetValue(dictionary, out var dict)
&& dict.TryGetValue(key, out var info))
{
return info;
}
return null;
}
/// <summary>
/// Returns a string that represents the current <see cref="XamlSourceInfo"/>.
/// </summary>
/// <returns>
/// A formatted string in the form <c>"FilePath:Line,Column"</c>,
/// or <c>"(unknown):Line,Column"</c> if the file path is not set.
/// </returns>
public override string ToString()
{
var filePath = SourceUri?.LocalPath ?? "(unknown)";
return $"{filePath}:{LineNumber},{LinePosition}";
}
}
}

6
src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs

@ -22,6 +22,12 @@ public class RuntimeXamlLoaderConfiguration
/// </summary>
public bool DesignMode { get; set; } = false;
/// <summary>
/// When enabled, the XAML compiler embeds SourceInfo metadata (file path, line, and column) into generated code.
/// Default is 'false'.
/// </summary>
public bool CreateSourceInfo { get; set; } = false;
/// <summary>
/// XAML diagnostics handler.
/// </summary>

5
src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs

@ -58,6 +58,11 @@ public class RuntimeXamlLoaderDocument
/// </summary>
public Uri? BaseUri { get; set; }
/// <summary>
/// Path to the XAML document being loaded.
/// </summary>
public string? Document { get; set; }
/// <summary>
/// The optional instance into which the XAML should be loaded.
/// </summary>

2
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@ -67,7 +67,7 @@ namespace Avalonia.Skia
var framebuffer = _renderTargetWithProperties?.Lock(out lockProperties) ?? _renderTarget.Lock();
var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height,
framebuffer.Format.ToSkColorType(),
framebuffer.Format == PixelFormat.Rgb565 ? SKAlphaType.Opaque : SKAlphaType.Premul);
framebuffer.AlphaFormat.ToSkAlphaType());
CreateSurface(framebufferImageInfo, framebuffer);
_hadConversionShim |= _conversionShim != null;

6
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@ -10,7 +10,7 @@ namespace Avalonia.Skia
/// <summary>
/// Immutable Skia bitmap.
/// </summary>
internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapWithAlphaImpl
internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapImpl
{
private readonly SKImage _image;
private readonly SKBitmap? _bitmap;
@ -195,7 +195,9 @@ namespace Avalonia.Skia
if (_bitmap.ColorType.ToAvalonia() is not { } format)
throw new NotSupportedException($"Unsupported format {_bitmap.ColorType}");
return new LockedFramebuffer(_bitmap.GetPixels(), PixelSize, _bitmap.RowBytes, Dpi, format, null);
var alphaFormat = _bitmap.AlphaType.ToAlphaFormat();
return new LockedFramebuffer(_bitmap.GetPixels(), PixelSize, _bitmap.RowBytes, Dpi, format, alphaFormat, null);
}
}
}

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

@ -238,6 +238,8 @@ namespace Avalonia.Skia
public Vector Dpi => _parent.Dpi;
/// <inheritdoc />
public PixelFormat Format => _bitmap.ColorType.ToPixelFormat();
public AlphaFormat AlphaFormat => _bitmap.AlphaType.ToAlphaFormat();
}
}
}

2
src/Windows/Avalonia.Win32/FramebufferManager.cs

@ -49,7 +49,7 @@ namespace Avalonia.Win32
return fb = new LockedFramebuffer(
framebufferData.Data.Address, framebufferData.Size, framebufferData.RowBytes,
GetCurrentDpi(), s_format, _onDisposeAction);
GetCurrentDpi(), s_format, AlphaFormat.Premul, _onDisposeAction);
}
finally
{

14
src/Windows/Avalonia.Win32/Interop/Win32Icon.cs

@ -96,14 +96,14 @@ internal class Win32Icon : IDisposable
static IntPtr CreateHBitmap(Bitmap source)
{
using var fb = AllocFramebuffer(source.PixelSize, PixelFormats.Bgra8888);
source.CopyPixels(fb, AlphaFormat.Unpremul);
using var fb = AllocFramebuffer(source.PixelSize, PixelFormats.Bgra8888, AlphaFormat.Unpremul);
source.CopyPixels(fb);
return UnmanagedMethods.CreateBitmap(source.PixelSize.Width, source.PixelSize.Height, 1, 32, fb.Address);
}
static unsafe IntPtr AlphaToMask(Bitmap source)
{
using var alphaMaskBuffer = AllocFramebuffer(source.PixelSize, PixelFormats.BlackWhite);
using var alphaMaskBuffer = AllocFramebuffer(source.PixelSize, PixelFormats.BlackWhite, AlphaFormat.Opaque);
var height = alphaMaskBuffer.Size.Height;
var width = alphaMaskBuffer.Size.Width;
@ -114,8 +114,8 @@ internal class Win32Icon : IDisposable
}
else
{
using var argbBuffer = AllocFramebuffer(source.PixelSize, PixelFormat.Bgra8888);
source.CopyPixels(argbBuffer, AlphaFormat.Unpremul);
using var argbBuffer = AllocFramebuffer(source.PixelSize, PixelFormat.Bgra8888, AlphaFormat.Unpremul);
source.CopyPixels(argbBuffer);
var pSource = (byte*)argbBuffer.Address;
var pDest = (byte*)alphaMaskBuffer.Address;
@ -140,7 +140,7 @@ internal class Win32Icon : IDisposable
return UnmanagedMethods.CreateBitmap(width, height, 1, 1, alphaMaskBuffer.Address);
}
static LockedFramebuffer AllocFramebuffer(PixelSize size, PixelFormat format)
static LockedFramebuffer AllocFramebuffer(PixelSize size, PixelFormat format, AlphaFormat alphaFormat)
{
if (size.Width < 1 || size.Height < 1)
throw new ArgumentOutOfRangeException();
@ -149,7 +149,7 @@ internal class Win32Icon : IDisposable
var data = Marshal.AllocHGlobal(size.Height * stride);
if (data == IntPtr.Zero)
throw new OutOfMemoryException();
return new LockedFramebuffer(data, size, stride, new Vector(96, 96), format,
return new LockedFramebuffer(data, size, stride, new Vector(96, 96), format, alphaFormat,
() => Marshal.FreeHGlobal(data));
}

81
tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs

@ -1,81 +0,0 @@
using System;
using Avalonia.Utilities;
using Xunit;
#pragma warning disable CS0618 // Type or member is obsolete
namespace Avalonia.Base.UnitTests.Utilities
{
public class StringTokenizerTests
{
[Fact]
public void ReadInt32_Reads_Values()
{
var target = new StringTokenizer("123,456");
Assert.Equal(123, target.ReadInt32());
Assert.Equal(456, target.ReadInt32());
Assert.Throws<FormatException>(() => target.ReadInt32());
}
[Fact]
public void ReadDouble_Reads_Values()
{
var target = new StringTokenizer("12.3,45.6");
Assert.Equal(12.3, target.ReadDouble());
Assert.Equal(45.6, target.ReadDouble());
Assert.Throws<FormatException>(() => target.ReadDouble());
}
[Fact]
public void TryReadInt32_Reads_Values()
{
var target = new StringTokenizer("123,456");
Assert.True(target.TryReadInt32(out var value));
Assert.Equal(123, value);
Assert.True(target.TryReadInt32(out value));
Assert.Equal(456, value);
Assert.False(target.TryReadInt32(out value));
}
[Fact]
public void TryReadInt32_Doesnt_Throw()
{
var target = new StringTokenizer("abc");
Assert.False(target.TryReadInt32(out var value));
}
[Fact]
public void TryReadDouble_Reads_Values()
{
var target = new StringTokenizer("12.3,45.6");
Assert.True(target.TryReadDouble(out var value));
Assert.Equal(12.3, value);
Assert.True(target.TryReadDouble(out value));
Assert.Equal(45.6, value);
Assert.False(target.TryReadDouble(out value));
}
[Fact]
public void TryReadDouble_Doesnt_Throw()
{
var target = new StringTokenizer("abc");
Assert.False(target.TryReadDouble(out var value));
}
[Fact]
public void ReadSpan_And_ReadString_Reads_Same()
{
var target1 = new StringTokenizer("abc,def");
var target2 = new StringTokenizer("abc,def");
Assert.Equal(target1.ReadString(), target2.ReadSpan().ToString());
Assert.True(target1.ReadSpan().SequenceEqual(target2.ReadString()));
}
}
}

10
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs

@ -12,8 +12,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{
public class ResourceIncludeTests : XamlTestBase
{
[Fact]
public void ResourceInclude_Loads_ResourceDictionary()
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ResourceInclude_Loads_ResourceDictionary(bool createSourceInfo)
{
var documents = new[]
{
@ -37,9 +39,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
</UserControl>")
};
var config = new RuntimeXamlLoaderConfiguration { CreateSourceInfo = createSourceInfo };
using (StartWithResources())
{
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents, config);
var userControl = Assert.IsType<UserControl>(compiled[1]);
var border = userControl.GetControl<Border>("border");

11
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs

@ -18,8 +18,10 @@ public class MergeResourceIncludeTests : XamlTestBase
RuntimeHelpers.RunClassConstructor(typeof(RelativeSource).TypeHandle);
}
[Fact]
public void MergeResourceInclude_Works_With_Single_Resource()
[Theory]
[InlineData(false)]
[InlineData(true)]
public void MergeResourceInclude_Works_With_Single_Resource(bool createSourceInfo)
{
var documents = new[]
{
@ -41,8 +43,9 @@ public class MergeResourceIncludeTests : XamlTestBase
</UserControl.Resources>
</UserControl>")
};
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var config = new RuntimeXamlLoaderConfiguration { CreateSourceInfo = createSourceInfo };
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents, config);
var contentControl = Assert.IsType<UserControl>(objects[1]);
var resources = Assert.IsType<ResourceDictionary>(contentControl.Resources);

4
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs

@ -265,6 +265,8 @@ public class StyleIncludeTests : XamlTestBase
[Fact]
public void StyleInclude_Should_Be_Replaced_With_Direct_Call()
{
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(@"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
@ -281,6 +283,8 @@ public class StyleIncludeTests : XamlTestBase
[Fact]
public void Style_Inside_Resources_Should_Produce_Warning()
{
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
var diagnostics = new List<RuntimeXamlDiagnostic>();
var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(@"
<ContentControl xmlns='https://github.com/avaloniaui'

583
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlSourceInfoTests.cs

@ -0,0 +1,583 @@
using System;
using System.Linq;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Markup.Xaml.Diagnostics;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Styling;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class XamlSourceInfoTests : XamlTestBase
{
private static readonly RuntimeXamlLoaderConfiguration s_configuration = new RuntimeXamlLoaderConfiguration
{
CreateSourceInfo = true
};
[Theory]
[InlineData(@"C:\TestFolder\TestFile.xaml")] // Windows-style path
[InlineData("/TestFolder/TestFile.xaml")] // Unix-style path
public void Root_UserControl_With_BaseUri_Gets_XamlSourceInfo_SourceUri_Set(string document)
{
var xamlDocument = new RuntimeXamlLoaderDocument(
"""
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
</UserControl>
""")
{
Document = document
};
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xamlDocument, s_configuration);
var sourceInfo = XamlSourceInfo.GetXamlSourceInfo(userControl);
Assert.NotNull(sourceInfo);
Assert.Equal("file", sourceInfo.SourceUri!.Scheme);
Assert.True(sourceInfo.SourceUri!.IsAbsoluteUri);
Assert.Equal(new UriBuilder("file", "") {Path = document}.Uri, sourceInfo.SourceUri);
}
[Fact]
public void Root_UserControl_Gets_XamlSourceInfo_Set()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var sourceInfo = XamlSourceInfo.GetXamlSourceInfo(userControl);
Assert.NotNull(sourceInfo);
}
[Fact]
public void Nested_Controls_All_Get_XamlSourceInfo_Set()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<StackPanel>
<Button />
<TextBlock />
</StackPanel>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var stackPanel = (StackPanel)userControl.Content!;
var button = (Button)stackPanel.Children[0];
var textblock = (TextBlock)stackPanel.Children[1];
var userControlSourceInfo = XamlSourceInfo.GetXamlSourceInfo(userControl);
Assert.NotNull(userControlSourceInfo);
var stackPanelSourceInfo = XamlSourceInfo.GetXamlSourceInfo(stackPanel);
Assert.NotNull(stackPanelSourceInfo);
var buttonSourceInfo = XamlSourceInfo.GetXamlSourceInfo(button);
Assert.NotNull(buttonSourceInfo);
var textblockSourceInfo = XamlSourceInfo.GetXamlSourceInfo(textblock);
Assert.NotNull(textblockSourceInfo);
}
[Fact]
public void Property_Elements_Get_XamlSourceInfo_Set()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Rectangle Fill=""Blue"" Width=""63"" Height=""41"">
<Rectangle.OpacityMask>
<LinearGradientBrush StartPoint=""0%,0%"" EndPoint=""100%,100%"">
<LinearGradientBrush.GradientStops>
<GradientStop Offset=""0"" Color=""Black""/>
<GradientStop Offset=""1"" Color=""Transparent""/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var rect = (Rectangle)userControl.Content!;
var gradient = (LinearGradientBrush)rect.OpacityMask!;
var stopOne = (GradientStop)gradient.GradientStops.First();
var stopTwo = (GradientStop)gradient.GradientStops.Last();
var rectSourceInfo = XamlSourceInfo.GetXamlSourceInfo(rect);
Assert.NotNull(rectSourceInfo);
var gradientSourceInfo = XamlSourceInfo.GetXamlSourceInfo(gradient);
Assert.NotNull(gradientSourceInfo);
var stopOneSourceInfo = XamlSourceInfo.GetXamlSourceInfo(stopOne);
Assert.NotNull(stopOneSourceInfo);
var stopTwoSourceInfo = XamlSourceInfo.GetXamlSourceInfo(stopTwo);
Assert.NotNull(stopTwoSourceInfo);
}
[Fact]
public void Shapes_Get_XamlSourceInfo_Set()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Canvas Name=""TheCanvas"" Background=""Yellow"" Width=""300"" Height=""400"">
<Ellipse Fill=""Green"" Width=""58"" Height=""58"" Canvas.Left=""88"" Canvas.Top=""100""/>
<Path Fill=""Orange"" Canvas.Left=""30"" Canvas.Top=""250""/>
<Path Fill=""OrangeRed"" Canvas.Left=""180"" Canvas.Top=""250"">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint=""0,0"" IsClosed=""True"">
<QuadraticBezierSegment Point1=""50,0"" Point2=""50,-50"" />
<QuadraticBezierSegment Point1=""100,-50"" Point2=""100,0"" />
<LineSegment Point=""50,0"" />
<LineSegment Point=""50,50"" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Line StartPoint=""120,185"" EndPoint=""30,115"" Stroke=""Red"" StrokeThickness=""2""/>
<Polygon Points=""75,0 120,120 0,45 150,45 30,120"" Stroke=""DarkBlue"" StrokeThickness=""1"" Fill=""Violet"" Canvas.Left=""150"" Canvas.Top=""31""/>
</Canvas>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var canvas = (Canvas)userControl.Content!;
var ellipse = (Ellipse)canvas.Children[0];
var path1 = (Path)canvas.Children[1];
var path2 = (Path)canvas.Children[2];
var geometry = (PathGeometry)path2.Data!;
var figure = (PathFigure)geometry.Figures![0];
var segment1 = figure.Segments![0];
var segment2 = figure.Segments![1];
var segment3 = figure.Segments![2];
var segment4 = figure.Segments![3];
var line = (Line)canvas.Children[3];
var polygon = (Polygon)canvas.Children[4];
var canvasSourceInfo = XamlSourceInfo.GetXamlSourceInfo(canvas);
Assert.NotNull(canvasSourceInfo);
var ellipseSourceInfo = XamlSourceInfo.GetXamlSourceInfo(ellipse);
Assert.NotNull(ellipseSourceInfo);
var path1SourceInfo = XamlSourceInfo.GetXamlSourceInfo(path1);
Assert.NotNull(path1SourceInfo);
var path2SourceInfo = XamlSourceInfo.GetXamlSourceInfo(path2);
Assert.NotNull(path2SourceInfo);
var geometrySourceInfo = XamlSourceInfo.GetXamlSourceInfo(geometry);
Assert.NotNull(geometrySourceInfo);
var figureSourceInfo = XamlSourceInfo.GetXamlSourceInfo(figure);
Assert.NotNull(figureSourceInfo);
var segment1SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment1);
Assert.NotNull(segment1SourceInfo);
var segment2SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment2);
Assert.NotNull(segment2SourceInfo);
var segment3SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment3);
Assert.NotNull(segment3SourceInfo);
var segment4SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment4);
Assert.NotNull(segment4SourceInfo);
var lineSourceInfo = XamlSourceInfo.GetXamlSourceInfo(line);
Assert.NotNull(lineSourceInfo);
var polygonSourceInfo = XamlSourceInfo.GetXamlSourceInfo(polygon);
Assert.NotNull(polygonSourceInfo);
}
[Fact]
public void Styles_Get_XamlSourceInfo_Set()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Styles>
<Style Selector=""Button"">
<Setter Property=""Margin"" Value=""5"" />
</Style>
<ContainerQuery Name=""container""
Query=""max-width:400"">
<Style Selector=""Button"">
<Setter Property=""Background""
Value=""Red""/>
</Style>
</ContainerQuery>
</UserControl.Styles>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var style = (Style)userControl.Styles[0];
var query = (ContainerQuery)userControl.Styles[1];
var styleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(style);
Assert.NotNull(styleSourceInfo);
var querySourceInfo = XamlSourceInfo.GetXamlSourceInfo(query);
Assert.NotNull(querySourceInfo);
}
[Fact]
public void Animations_Get_XamlSourceInfo_Set()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Styles>
<Style Selector=""Rectangle.red"">
<Setter Property=""Fill"" Value=""Red""/>
<Style.Animations>
<Animation Duration=""0:0:3"">
<KeyFrame Cue=""0%"">
<Setter Property=""Opacity"" Value=""0.0""/>
</KeyFrame>
<KeyFrame Cue=""100%"">
<Setter Property=""Opacity"" Value=""1.0""/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</UserControl.Styles>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var style = (Style)userControl.Styles[0];
var animation = (Animation.Animation)style.Animations[0];
var frame1 = animation.Children[0];
var frame2 = animation.Children[1];
var styleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(style);
Assert.NotNull(styleSourceInfo);
var animationSourceInfo = XamlSourceInfo.GetXamlSourceInfo(animation);
Assert.NotNull(animationSourceInfo);
var frameOneSourceInfo = XamlSourceInfo.GetXamlSourceInfo(frame1);
Assert.NotNull(frameOneSourceInfo);
var frameTwoSourceInfo = XamlSourceInfo.GetXamlSourceInfo(frame2);
Assert.NotNull(frameTwoSourceInfo);
}
[Fact]
public void DataTemplates_And_Deferred_Contents_Get_XamlSourceInfo_Set()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.Xaml'>
<UserControl.DataTemplates>
<DataTemplate DataType=""local:SourceInfoTestViewModel"">
<Border Background=""Red"" CornerRadius=""8"">
<TextBox Text=""{Binding Name}""/>
</Border>
</DataTemplate>
</UserControl.DataTemplates>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var datatemplate = (DataTemplate)userControl.DataTemplates[0];
Border border;
// The template and it's content is deferred as not used (yet)
if (datatemplate.Content is IDeferredContent deferredContent)
{
var templateResult = (ITemplateResult)deferredContent.Build(null)!;
border = (Border)templateResult.Result!;
}
else
{
border = (Border)datatemplate.Content!;
}
var textBox = (TextBox)border!.Child!;
var datatemplateSourceInfo = XamlSourceInfo.GetXamlSourceInfo(datatemplate);
Assert.NotNull(datatemplateSourceInfo);
var borderSourceInfo = XamlSourceInfo.GetXamlSourceInfo(border);
Assert.NotNull(borderSourceInfo);
var textBoxSourceInfo = XamlSourceInfo.GetXamlSourceInfo(textBox);
Assert.NotNull(textBoxSourceInfo);
}
[Fact]
public void Resources_Get_XamlSourceInfo_Set()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key='Light'>
<SolidColorBrush x:Key='BackgroundBrush' Color='White'/>
<SolidColorBrush x:Key='ForegroundBrush' Color='Black'/>
</ResourceDictionary>
<ResourceDictionary x:Key='Dark'>
<SolidColorBrush x:Key='BackgroundBrush' Color='Black'/>
<SolidColorBrush x:Key='ForegroundBrush' Color='White'/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<SolidColorBrush x:Key=""Background"" Color=""Yellow"" />
<SolidColorBrush x:Key='OtherBrush'>Black</SolidColorBrush>
</ResourceDictionary>
</UserControl.Resources>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var backgroundBrush = userControl.Resources["Background"];
var lightDictionary = (ResourceDictionary)userControl.Resources.ThemeDictionaries[ThemeVariant.Light];
var darkDictionary = (ResourceDictionary)userControl.Resources.ThemeDictionaries[ThemeVariant.Dark];
var lightForeground = lightDictionary["ForegroundBrush"];
var darkBackground = lightDictionary["BackgroundBrush"];
var otherBrush = userControl.Resources["OtherBrush"];
var backgroundBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(backgroundBrush!);
Assert.NotNull(backgroundBrushSourceInfo);
var lightDictionarySourceInfo = XamlSourceInfo.GetXamlSourceInfo(lightDictionary!);
Assert.NotNull(lightDictionarySourceInfo);
var darkDictionarySourceInfo = XamlSourceInfo.GetXamlSourceInfo(darkDictionary!);
Assert.NotNull(darkDictionarySourceInfo);
var lightForegroundSourceInfo = XamlSourceInfo.GetXamlSourceInfo(lightForeground!);
Assert.NotNull(lightForegroundSourceInfo);
var darkBackgroundSourceInfo = XamlSourceInfo.GetXamlSourceInfo(darkBackground!);
Assert.NotNull(darkBackgroundSourceInfo);
var otherBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(otherBrush!);
Assert.NotNull(otherBrushSourceInfo);
}
[Fact]
public void ResourceDictionary_Value_Types_Do_Not_Set_XamlSourceInfo()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<x:String x:Key='text'>foobar</x:String>
<x:Double x:Key=""A_Double"">123.3</x:Double>
<x:Int16 x:Key=""An_Int16"">123</x:Int16>
<x:Int32 x:Key=""An_Int32"">37434323</x:Int32>
<Thickness x:Key=""PreferredPadding"">10,20,10,0</Thickness>
</UserControl.Resources>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var foobarString = userControl.Resources["text"];
var aDouble = userControl.Resources["A_Double"];
var anInt16 = userControl.Resources["An_Int16"];
var anInt32 = userControl.Resources["An_Int32"];
var padding = userControl.Resources["PreferredPadding"];
// Value types shouldn't get source info
var foobarStringSourceInfo = XamlSourceInfo.GetXamlSourceInfo(foobarString!);
Assert.Null(foobarStringSourceInfo);
var aDoubleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(aDouble!);
Assert.Null(aDoubleSourceInfo);
var anInt16SourceInfo = XamlSourceInfo.GetXamlSourceInfo(anInt16!);
Assert.Null(anInt16SourceInfo);
var anInt32SourceInfo = XamlSourceInfo.GetXamlSourceInfo(anInt32!);
Assert.Null(anInt32SourceInfo);
var paddingSourceInfo = XamlSourceInfo.GetXamlSourceInfo(padding!);
Assert.Null(paddingSourceInfo);
}
[Fact]
public void ResourceDictionary_Set_Resource_Source_Info()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<x:String x:Key='text'>foobar</x:String>
<x:Double x:Key=""A_Double"">123.3</x:Double>
<x:Int16 x:Key=""An_Int16"">123</x:Int16>
<x:Int32 x:Key=""An_Int32"">37434323</x:Int32>
<Thickness x:Key=""PreferredPadding"">10,20,10,0</Thickness>
<x:Uri x:Key='homepage'>http://avaloniaui.net</x:Uri>
<SolidColorBrush x:Key='MyBrush' Color='Red'/>
</UserControl.Resources>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var resources = userControl.Resources;
var foobarStringSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "text");
Assert.NotNull(foobarStringSourceInfo);
var aDoubleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "A_Double");
Assert.NotNull(aDoubleSourceInfo);
var anInt16SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int16");
Assert.NotNull(anInt16SourceInfo);
var anInt32SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int32");
Assert.NotNull(anInt32SourceInfo);
var paddingSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "PreferredPadding");
Assert.NotNull(paddingSourceInfo);
var homepageSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "homepage");
Assert.NotNull(homepageSourceInfo);
var myBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "MyBrush");
Assert.NotNull(myBrushSourceInfo);
}
[Fact]
public void ResourceDictionary_Set_Resource_Source_Info_With_Nested_Dictionaries()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<ResourceDictionary>
<x:String x:Key='text'>foobar</x:String>
<x:Double x:Key=""A_Double"">123.3</x:Double>
<x:Int16 x:Key=""An_Int16"">123</x:Int16>
<x:Int32 x:Key=""An_Int32"">37434323</x:Int32>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<Thickness x:Key=""PreferredPadding"">10,20,10,0</Thickness>
<x:Uri x:Key='homepage'>http://avaloniaui.net</x:Uri>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key='Light'>
<SolidColorBrush x:Key='MyBrush' Color='Red'/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</UserControl.Resources>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var resources = userControl.Resources;
var innerResources = (IResourceDictionary)resources.MergedDictionaries[0];
var themeResources = (IResourceDictionary)resources.ThemeDictionaries[ThemeVariant.Light];
// Outer define source info
var foobarStringSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "text");
Assert.NotNull(foobarStringSourceInfo);
var aDoubleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "A_Double");
Assert.NotNull(aDoubleSourceInfo);
var anInt16SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int16");
Assert.NotNull(anInt16SourceInfo);
var anInt32SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int32");
Assert.NotNull(anInt32SourceInfo);
// Outer one should not have source info for inner resources
var paddingSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "PreferredPadding");
Assert.Null(paddingSourceInfo);
var homepageSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "homepage");
Assert.Null(homepageSourceInfo);
var myBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "MyBrush");
Assert.Null(myBrushSourceInfo);
// Inner defined source info
homepageSourceInfo = XamlSourceInfo.GetXamlSourceInfo(innerResources, "homepage");
Assert.NotNull(homepageSourceInfo);
myBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(themeResources, "MyBrush");
Assert.NotNull(myBrushSourceInfo);
// Non-value types should have source info themselves
var homepage = XamlSourceInfo.GetXamlSourceInfo(innerResources["homepage"]!);
Assert.NotNull(homepage);
var myBrush = XamlSourceInfo.GetXamlSourceInfo(themeResources["MyBrush"]!);
Assert.NotNull(myBrush);
}
[Fact]
public void Gestures_Get_XamlSourceInfo_Set()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll=""True""
CanVerticallyScroll=""True""/>
<PullGestureRecognizer PullDirection=""TopToBottom""/>
</UserControl.GestureRecognizers>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var scroll = (ScrollGestureRecognizer)userControl.GestureRecognizers.First();
var pull = (PullGestureRecognizer)userControl.GestureRecognizers.Last();
var scrollSourceInfo = XamlSourceInfo.GetXamlSourceInfo(scroll);
Assert.NotNull(scrollSourceInfo);
var pullSourceInfo = XamlSourceInfo.GetXamlSourceInfo(pull);
Assert.NotNull(pullSourceInfo);
}
[Fact]
public void Transitions_Get_XamlSourceInfo_Set()
{
var xaml = new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Transitions>
<Transitions>
<DoubleTransition Property=""Width"" Duration=""0:0:1.5""/>
<DoubleTransition Property=""Height"" Duration=""0:0:1.5""/>
</Transitions>
</UserControl.Transitions>
</UserControl>");
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
var width = (DoubleTransition)userControl.Transitions!.First();
var height = (DoubleTransition)userControl.Transitions!.Last();
var widthSourceInfo = XamlSourceInfo.GetXamlSourceInfo(width);
Assert.NotNull(widthSourceInfo);
var heightSourceInfo = XamlSourceInfo.GetXamlSourceInfo(height);
Assert.NotNull(heightSourceInfo);
}
}
public class SourceInfoTestViewModel
{
public string? Name { get; set; }
}
}

2
tests/Avalonia.RenderTests/Composition/DirectFbCompositionTests.cs

@ -83,7 +83,7 @@ public class DirectFbCompositionTests : TestBase
SKBitmap fb = new SKBitmap(200, 200, SKColorType.Rgba8888, SKAlphaType.Premul);
ILockedFramebuffer LockFb() => new LockedFramebuffer(fb.GetAddress(0, 0), new(fb.Width, fb.Height),
fb.RowBytes, new Vector(96, 96), PixelFormat.Rgba8888, null);
fb.RowBytes, new Vector(96, 96), PixelFormat.Rgba8888, AlphaFormat.Premul, null);
bool previousFrameIsRetained = false;
IFramebufferRenderTarget rt = advertised

7
tests/Avalonia.RenderTests/Media/BitmapTests.cs

@ -25,10 +25,11 @@ namespace Avalonia.Skia.RenderTests
class Framebuffer : ILockedFramebuffer, IFramebufferPlatformSurface
{
public Framebuffer(PixelFormat fmt, PixelSize size)
public Framebuffer(PixelFormat fmt, AlphaFormat alphaFormat, PixelSize size)
{
Format = fmt;
var bpp = fmt == PixelFormat.Rgb565 ? 2 : 4;
AlphaFormat = alphaFormat;
Size = size;
RowBytes = bpp * size.Width;
Address = Marshal.AllocHGlobal(size.Height * RowBytes);
@ -40,6 +41,8 @@ namespace Avalonia.Skia.RenderTests
public PixelFormat Format { get; }
public AlphaFormat AlphaFormat { get; }
public PixelSize Size { get; }
public int RowBytes { get; }
@ -64,7 +67,7 @@ namespace Avalonia.Skia.RenderTests
{
var fmt = new PixelFormat(fmte);
var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
var fb = new Framebuffer(fmt, new PixelSize(80, 80));
var fb = new Framebuffer(fmt, AlphaFormat.Premul, new PixelSize(80, 80));
var r = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
using(var cpuContext = r.CreateBackendContext(null))
using (var target = cpuContext.CreateRenderTarget(new object[] { fb }))

2
tests/Avalonia.UnitTests/CompositorTestServices.cs

@ -174,7 +174,7 @@ public class CompositorTestServices : IDisposable
{
var ptr = Marshal.AllocHGlobal(128);
return new LockedFramebuffer(ptr, new PixelSize(1, 1), 4, new Vector(96, 96),
PixelFormat.Rgba8888, () => Marshal.FreeHGlobal(ptr));
PixelFormat.Rgba8888, AlphaFormat.Premul, () => Marshal.FreeHGlobal(ptr));
}
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);

Loading…
Cancel
Save